Closed Bug 996785 Opened 11 years ago Closed 11 years ago

Bidirectional CPOWs for add-on compat

Categories

(Firefox :: Extension Compatibility, defect)

defect
Not set
normal

Tracking

()

RESOLVED FIXED
Firefox 32

People

(Reporter: cpeterson, Assigned: billm)

References

Details

Attachments

(20 files, 1 obsolete file)

(deleted), patch
jonco
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
(deleted), patch
Felipe
: review+
Details | Diff | Splinter Review
(deleted), patch
mrbkap
: review+
Details | Diff | Splinter Review
      No description provided.
Blocks: e10s-m1
The basic idea here is to allow CPOWs in both directions (from the parent to the child and from the child to the parent). Currently we only allow parent->child CPOWs. The main use case for child->parent CPOWs is when an addon wants to attach an event listener to some object in the content process; in this case, it needs to pass a function object down to the child, and there's no way to do that now.

To do this, we'll make parent->child references weak and child->parent references strong. That way, the child is solely responsible for deciding what's garbage.

To make this work, I've factored most of the important code out of JavaScriptParent and JavaScript child into classes called WrapperOwner (which manages CPOWs) and WrapperAnswer (which answers CPOW queries). Then I made JavaScriptChild and JavaScriptParent both inherit from both of these classes. That way, all the functionality exists on both sides. The only stuff that's specialized is the tracing and finalization code, which works differently in each.
Attached patch finalize-callback (deleted) β€” β€” Splinter Review
This patch makes it possible to have multiple GC FinalizeCallbacks attached to a single runtime. We'll need that later on in the patch stack.
Attachment #8409225 - Flags: review?(jcoppeard)
Attached patch rename-static-methods (deleted) β€” β€” Splinter Review
We have a couple static methods that manage CPOWs. They're attached to JSParent right now, but pretty soon the distinction between JSParent and JSChild will mostly disappear. This patch just makes them functions.
Attachment #8409226 - Flags: review?(mrbkap)
Attached patch rename-maps (deleted) β€” β€” Splinter Review
We have two map types that map from JSObject* to CPOW id and from CPOW id to JSObject*. This patch renames them to ObjectToIdMap and IdToObjectMap to clarify their purpose.
Attachment #8409227 - Flags: review?(mrbkap)
Attached patch rename-find-object (deleted) β€” β€” Splinter Review
We have a single function, findObject, that looks up "objects" based on their ID. In JSParent, it returns CPOWs. In JSChild, it returns normal JSObjects that are referenced by CPOWs. In the future, both sides will need both kinds of mappings. This patch renames findObject to findProxyById (for the JSParent-side usecase) and findObjectById (for the JSChild-side usecase). Eventually we'll have two separate maps that implement this functionality, but this patch just distinguishes the callsites and still uses the same map for both.
Attachment #8409228 - Flags: review?(mrbkap)
Attached patch rename-from-variant (deleted) β€” β€” Splinter Review
Most conversion functions in JavaScript shared have names of the form toX/fromX. The exceptions are toVariant/toValue. This patch renames toValue to fromVariant.
Attachment #8409229 - Flags: review?(mrbkap)
Attached patch rename-to-from-id (deleted) β€” β€” Splinter Review
For some reason, the functions that convert between object IDs and JSObject*s are called makeId and unwrap. This patch renames them to toId and fromId.

Note that these functions still have different functionality in the parent and the child, so they're virtual. I'll fix that later on.
Attachment #8409231 - Flags: review?(mrbkap)
Attached patch create-proxies-map (deleted) β€” β€” Splinter Review
This patch creates a separate IdToObjectMap proxies_. It's used by findProxyById. This way, findObjectById and findProxyById now use separate maps.
Attachment #8409232 - Flags: review?(mrbkap)
Attached patch move-wrapper-code (deleted) β€” β€” Splinter Review
This patch moves all the CPOW-related code from JSParent to a WrapperOwner class. Most of the patch is just code movement. The tricky part is how the IPC works. The CPOW code needs to call functions like CallGetProperty, which are generated by the IPDL codegen and belong to PJavaScriptParent. Eventually, I'm going to shift the IPDL code so that messages like GetProperty are marked "both:" rather than just "child:". Unfortunately, that means that PJavaScriptParent and PJavaScriptChild will have completely separate implementations of CallGetProperty. Somehow, WrapperOwner needs to know which one is the right one to call.

I sorted this out using virtual methods and some templating. Hopefully it's not too ugly. Please let me know if you can think of a better way.
Attachment #8409239 - Flags: review?(mrbkap)
Attached patch move-answer-code (deleted) β€” β€” Splinter Review
This patch moves the JSChild code to a different class, WrapperAnswer. It's pretty similar to the previous patch. It has the additional problem that I want JavaScriptBase to inherit from both WrapperAnswer and WrapperOwner. However, both of these inherit from JavaScriptShared. To solve this, I used virtual inheritance. It actually works kind of okay.
Attachment #8409243 - Flags: review?(mrbkap)
Attached patch rename-object-variant (deleted) β€” β€” Splinter Review
This patch is more cleanup. Rather than pass raw uint64_ts around to denote object IDs, I created an ObjectVariant IPDL type. It's just a struct holding a uint64_t.
Attachment #8409244 - Flags: review?(mrbkap)
Attached patch local-remote-objs (deleted) β€” β€” Splinter Review
This patch makes it possible to refer to two kinds of objects over IPDL. When you send a message, you can talk about "local" objects or "remote" objects. These refer to objects that are local and remote *to the receiver*. So if the parent sends a message to the child referring to a local object, it means an object that lives in the child. If it refers to a remote object, it means a CPOW in the child that refers to a parent object.

I also renamed toId/fromId to toObjectVariant/fromObjectVariant.
Attachment #8409245 - Flags: review?(mrbkap)
Attached patch tables-both-sides (deleted) β€” β€” Splinter Review
This patch moves all the tables and memory management stuff so that it lives in JavaScriptShared. That way, both the parent and the child can manage CPOWs.

I also added trace methods to both JSParent and JSChild. This patch and the next patch make it so that all cross-process pointers are strong. A later patch will make parent->child pointers weak.
Attachment #8409251 - Flags: review?(mrbkap)
Attached patch bidirection-creation (deleted) β€” β€” Splinter Review
This patch moves toObjectVariant/fromObjectVariant to WrapperOwner, where it is shared between JSParent and JSChild. This patch actually permits the child to have CPOWs, since it now has a way to create them and send them over the wire.
Attachment #8409252 - Flags: review?(mrbkap)
Attached patch change-ownership (deleted) β€” β€” Splinter Review
This patch makes parent->child CPOWs use weak references.
Attachment #8409253 - Flags: review?(mrbkap)
Attached patch error-handling (deleted) β€” β€” Splinter Review
One problem with weak references is that it forces the child to handle the case where the parent references an object ID of an object that was just GCed. In that case, we want to have the parent throw an exception (similar to using a nuked object). This patch makes that happen (before we just crashed intentionally).
Attachment #8409254 - Flags: review?(mrbkap)
Attached patch tabchild-owns-cpows (obsolete) (deleted) β€” β€” Splinter Review
This patch is a little unrelated. I was having problems where the parent would get two different CPOWs for the same object. The problem was that we would send up a CPOW for the object, and then later send up a CPOW for the same object, but wrapped in a different compartment. I talked about this a little with Bobby, and he liked the idea of using the TabChildGlobal as a canonical "home" for CPOWs. So we always try to wrap the object in that global (if it belongs to a tab at all) before sending it up as a CPOW.

This isn't the greatest solution, and I guess it doesn't work for parent objects. I don't expect that to be as much of a problem though, but maybe I'm wrong.

The other possibility is to unwrap the object before sending it to the parent. However, I like the fact that CPOWs are always wrapping xrays. It seems a lot safer that way. Bobby and I also talked about implementing the xray stuff in the parent, but that would be a lot more work and I'm not entirely sure it's the right way to go.
Attachment #8409257 - Flags: review?(mrbkap)
Attached patch new-tests (deleted) β€” β€” Splinter Review
This patch adds some new tests for bidirectional CPOWs and for the lifetime issues.
Attachment #8409258 - Flags: review?(mrbkap)
Attached patch bidirectional (deleted) β€” β€” Splinter Review
This is a folded patch, based on top of e2db1c06a933.
Comment on attachment 8409225 [details] [diff] [review]
finalize-callback

Review of attachment 8409225 [details] [diff] [review]:
-----------------------------------------------------------------

Great, looks like a good tidyup too.
Attachment #8409225 - Flags: review?(jcoppeard) → review+
Attachment #8409226 - Flags: review?(mrbkap) → review+
Attachment #8409227 - Flags: review?(mrbkap) → review+
Attachment #8409229 - Flags: review?(mrbkap) → review+
Comment on attachment 8409228 [details] [diff] [review]
rename-find-object

Review of attachment 8409228 [details] [diff] [review]:
-----------------------------------------------------------------

::: js/ipc/JavaScriptParent.cpp
@@ +42,5 @@
>      Value v = GetProxyExtra(obj, 1);
>      MOZ_ASSERT(v.isDouble());
>  
>      ObjectId objId = BitwiseCast<uint64_t>(v.toDouble());
> +    MOZ_ASSERT(findProxyById(objId) == obj);

Just a note that we decided to replace Proxy with CPOW.
Attachment #8409228 - Flags: review?(mrbkap) → review+
Attachment #8409231 - Flags: review?(mrbkap) → review+
Attachment #8409232 - Flags: review?(mrbkap) → review+
Comment on attachment 8409239 [details] [diff] [review]
move-wrapper-code

Review of attachment 8409239 [details] [diff] [review]:
-----------------------------------------------------------------

Per our in-person meeting, virtual functions were the way to go.
Attachment #8409239 - Flags: review?(mrbkap) → review+
Attachment #8409243 - Flags: review?(mrbkap) → review+
Attachment #8409244 - Flags: review?(mrbkap) → review+
Attachment #8409245 - Flags: review?(mrbkap) → review+
Comment on attachment 8409251 [details] [diff] [review]
tables-both-sides

Review of attachment 8409251 [details] [diff] [review]:
-----------------------------------------------------------------

::: js/ipc/JavaScriptChild.cpp
@@ +60,5 @@
> +    ObjectId objId = idOf(obj);
> +
> +    proxies_.remove(objId);
> +    if (active() && !SendDropObject(objId))
> +        (void)0;

if (active())
    SendDropObject(objId);
?

::: js/ipc/JavaScriptShared.cpp
@@ +153,5 @@
> +JavaScriptShared::decref()
> +{
> +    refcount_--;
> +    if (!refcount_)
> +        delete this;

Should we stabilize the refcount during destruction?

::: js/src/jsapi.cpp
@@ -1597,5 @@
>  
>  JS_PUBLIC_API(void)
>  JS_RemoveExtraGCRootsTracer(JSRuntime *rt, JSTraceDataOp traceOp, void *data)
>  {
> -    AssertHeapIsIdle(rt);

Why remove this? Could we assert idle or collecting? It seems like it'd be bad to remove a roots tracer during tracing?
Attachment #8409251 - Flags: review?(mrbkap) → review+
Attachment #8409252 - Flags: review?(mrbkap) → review+
Attachment #8409253 - Flags: review?(mrbkap) → review+
Attachment #8409254 - Flags: review?(mrbkap) → review+
Comment on attachment 8409257 [details] [diff] [review]
tabchild-owns-cpows

Review of attachment 8409257 [details] [diff] [review]:
-----------------------------------------------------------------

We should also make sure we either preserve DOM wrappers or make sure that we don't have to.
Attachment #8409257 - Flags: review?(mrbkap) → review+
Comment on attachment 8409257 [details] [diff] [review]
tabchild-owns-cpows

Review of attachment 8409257 [details] [diff] [review]:
-----------------------------------------------------------------

(Or investigate using the junk scope here)
Comment on attachment 8409258 [details] [diff] [review]
new-tests

Review of attachment 8409258 [details] [diff] [review]:
-----------------------------------------------------------------

::: content/base/test/chrome/cpows_child.js
@@ +8,3 @@
>  (function start() {
> +    [is_remote] = sendSyncMessage("cpows:is_remote");
> +    dump("IS REMOTE: " + is_remote + "\n");

Should probably nuke all of the dumps in these tests.

@@ +15,5 @@
>      nested_sync_test();
>      // The sync-ness of this call is important, because otherwise
>      // we tear down the child's document while we are
>      // still in the async test in the parent.
> +    lifetime_test(function() {

Please comment here and below that these two tests "race" to be the final test.

::: content/base/test/chrome/cpows_parent.xul
@@ +172,5 @@
> +        savedWilldieObj.f;
> +      } catch (e) {
> +        threw = true;
> +      }
> +      ok(threw, "limited-lifetime CPOW stopped working");

Worth checking to make sure we got the right exception?
Attachment #8409258 - Flags: review?(mrbkap) → review+
Attached patch tabchild-owns-cpows v2 (deleted) β€” β€” Splinter Review
I ran into some further problems with reachability. Blake already pointed out that we need to call PreserveWrapper on anything we create a CPOW to. However, there's a further problem, which is that we have to avoid creating CPOWs to CCWs. It's possible for the CCW to be collected while the target is still alive. In that case, using the CPOW would report a dead object, even though it's still alive.

So I settled on a setup where we always unwrap an object before creating a CPOW to it. However, when handling a CPOW request, we wrap all the objects we're operating on in either the TabChildGlobal or the junk scope. That way we always use Xrays.

I also added a PreserveWrapper call when creating a CPOW to an object. Unfortunately, I don't see how to do PreserveWrapper on XPCWrappedNatives. So right now it just works on DOM objects. We really need it for WNs though, so I'm hoping you can tell me how to fix this, Blake :-).

I'll write a test for the reachability behavior once we figure out this PreserveWrapper issue.
Attachment #8409257 - Attachment is obsolete: true
Attachment #8419842 - Flags: review?(mrbkap)
Just talked to Bobby and apparently we have no way to PreserveWrapper a WN. The one place where I saw we needed this so far was an nsSHistory object. That one is pretty easy to work around. I'll try to see if there are any other places; it's hard to tell how big of an issue this might be.
One idea to fix this generally is to handle WNs differently than other objects. They would be stored in the local object map using a strong reference but they would always be cleared out at the end of each event loop turn. That would at least give more predictable behavior.
Attached patch fix-existing-cpows (deleted) β€” β€” Splinter Review
Hi Felipe. Just as some background, the main goal of this patch is to allow CPOWs to point both from the parent to the child and from the child to the parent. In order to avoid problems with uncollectable cycles between the two, we've decided to make the parent->child references be weak. That turns out to cause a few issues with our use of CPOWs in the browser. Specifically, our CPOWs are getting collected too soon.

There were two problems. The first one is with the CPOW handler we use for getting the focus element. Normally it should live as long as the content script it lives in, so there should be no problem. However, it turns out the name "SyncHandler" is used in two places: 

http://mxr.mozilla.org/mozilla-central/source/toolkit/content/browser-child.js#14
http://mxr.mozilla.org/mozilla-central/source/browser/components/sessionstore/content/content-sessionStore.js#172

Because bug 673569 still isn't fixed, one overwrites the other and so the focus handler gets collected before we expect it to. I've fixed the problem by renaming the object.

The other problem is with a CPOW to an nsSHistory object. Normally we would expect that object to stay alive because it's pointed to by a live docshell. However, we're *actually* creating a CPOW to the JS wrapper around the nsSHistory object. The JS object is allowed to die before the nsSHistory object dies. (We normally have a scheme called "wrapper preservation" that can prevent the JS object from dying before the nsSHistory object, but that doesn't work here because nsSHistory is an old-fashioned XPCOM object that apparently no one cares about. For the same reason, you can't use an nsSHistory object as a key in a WeakMap.) Anyway, I fixed this problem by strongly rooting the JS object from the content script.

The latter problem might be an issue for add-ons, but maybe not. I wouldn't expect add-ons to use too many old-fashioned XPCOM objects in the content process. Usually they'll just be using regular DOM objects, which don't have the wrapper preservation problem. If it does turn out to be an issue, I think we can probably work around it on a case by case basis.
Attachment #8420351 - Flags: review?(felipc)
> Should we stabilize the refcount during destruction?

I forgot to ask: what does this mean?
Attachment #8420351 - Flags: review?(felipc) → review+
Comment on attachment 8419842 [details] [diff] [review]
tabchild-owns-cpows v2

Review of attachment 8419842 [details] [diff] [review]:
-----------------------------------------------------------------

::: js/ipc/JavaScriptShared.cpp
@@ +366,5 @@
> +        return nullptr;
> +    }
> +
> +    // Objects are stored in objects_ unwrapped. We want to wrap the object
> +    // before returning it so that all operations happen on Xray wrappers. If

Do we have tests anywhere that we actually do get Xray behavior for CPOWs? I can't find any.
Attachment #8419842 - Flags: review?(mrbkap) → review+
Attached patch dom-xray-test (deleted) β€” β€” Splinter Review
This tests the preserve wrapper behavior for DOM objects as well as the Xray behavior. I verified that disabling the preserve wrapper code and the extra TabChildGlobal/junk scope wrapping behavior makes these tests fail.
Attachment #8423537 - Flags: review?(mrbkap)
Attachment #8423537 - Flags: review?(mrbkap) → review+
Depends on: 1180991
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: