Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upUse a structured clone holder to store rooted clones #21218
Conversation
2a6ad46
to
f5b1c03
|
Wonders! It's not crashing anymore... problem was doing The structured cloning of a blob test is also still passing, so I guess this one is ready for review... |
|
So, the StructuredCloneHolder right now would only grow in size, since the caller of StructuredClone::read doesn't actually pop the element off of the holder. I imagine that this is the intended behavior instead? |
| if tag == StructuredCloneTags::DomBlob as u32 { | ||
| return read_blob(cx, r) | ||
| let (rooted_blob, js_object) = read_blob(cx, r); | ||
| holder.blobs.borrow_mut().push(rooted_blob); |
This comment has been minimized.
This comment has been minimized.
KiChjang
Jul 23, 2018
Member
We need to be very careful here, because if holder.blob is being borrowed mutably or immutably elsewhere, this will panic. Specifically, there may be a chance that some code elsewhere borrows it immutably for reading, and then during the read, it performs yet another StructuredCloneRead, which would then run into a panic.
This comment has been minimized.
This comment has been minimized.
gterzian
Jul 24, 2018
•
Author
Member
Should we use try_borrow_mut and do some error handling at this point? What to do if that returns an error? Should we abort the reading and return a ptr::null_mut()?
there may be a chance that some code elsewhere borrows it immutably for reading, and then during the read, it performs yet another StructuredCloneRead
The way I look at this, is that JS_ReadStructuredClone is basically a function call, and the 'callbacks' are a bit of a misnomer since they are simply called at the end of this function call, in case the engine encounters a StructuredCloneTags that doesn't match any of the tags it can handle itself, it then calls the read callback to see if it can handle it.
If my understanding is correct, it means it would be impossible to during the read, perform yet another StructuredCloneRead. Basically, one call to JS_ReadStructuredClone would need to return, which includes having called the 'callbacks', before another one could be executed.
In any case, I think it might still a good idea to use try_borrow_mut and return a ptr::null_mut() if that fails, 'just to be sure'.
What do you think?
This comment has been minimized.
This comment has been minimized.
| @@ -78,6 +79,10 @@ pub struct GlobalScope { | |||
| crypto: MutNullableDom<Crypto>, | |||
| next_worker_id: Cell<WorkerId>, | |||
|
|
|||
| /// A place to keep the clones read into this realm rooted. | |||
| #[ignore_malloc_size_of = "Rc<T> is hard"] | |||
| structured_clone_holder: Rc<StructuredCloneHolder>, | |||
This comment has been minimized.
This comment has been minimized.
KiChjang
Jul 23, 2018
•
Member
I'm actually more interested to know why this has to be an Rc<StructuredCloneHolder> here instead of just DomRefCell<Vec<DomRoot<Blob>>>. In the read_callback function under structuredclone.rs, you can get a reference to the global by simply calling GlobalScope::from_context(cx), so passing in a raw Rc pointer seems unnecessary to me.
This comment has been minimized.
This comment has been minimized.
gterzian
Jul 24, 2018
•
Author
Member
My answer to 'why aRc' would be: 'it was easy to get a raw pointer out of it and cast it to *mut raw::c_void, and then build one back from the raw pointer on the other side'.
Would that be doable with a DomRefCell? (I see that a RefCell has a as_ptr method, and it doesn't seem to be available on the DomRefCell variant.).
Assuming we can get a raw pointer using as_ptr, how would we build it back on the read end from this pointer? I don't see the equivalent of Rc::from_raw on RefCell...
Any ideas?
This comment has been minimized.
This comment has been minimized.
KiChjang
Jul 24, 2018
Member
Going back to what I suggested -- you already have a *mut JSContext, so you can make use of that to get a GlobalScope out of it, and in your read_callback function, you can simply call methods on GlobalScope to enqueue the DomRoot<Blob> into its data holder; there's no need to pass in a raw Rc pointer just to get a reference to the data holder.
This comment has been minimized.
This comment has been minimized.
gterzian
Jul 24, 2018
•
Author
Member
Awesome, I'll give it a try! (sorry I actually missed your suggestion above on the first read...)
It depends on the problem we're trying to solve, which is not entirely clear to me by the way. I am under the impression that the problem is that previously a Yes it would only grow in size, and I am not sure when would be a good 'time' to potential pop it off and let it drop. My intuition would be 'when the JSobject is garbage collected', so perhaps via an impl of |
Do you mean that we could just pop it off and drop it once the read operation has completed? I was under the impression that we might need to keep the Blob rooted while the JsObject might still be used by the JS code after the read operation has returned... |
70dfe55
to
931e632
|
@KiChjang Please take another look, I guess the last question is whether we want to pop the blob off of the vec at some point or not... |
44a8c14
to
cce12db
|
|
|
@gterzian So, we definitely need to pop the |
|
Huh, I left a detailed code review on this OR, but I can't find any trace of it now. |
|
@KiChjang You're right, and using this new How about adding a @jdm That's a real bummer, sorry to hear you lost that work... |
|
@gterzian I'm not sure what you mean by |
| @@ -111,7 +111,12 @@ unsafe fn read_blob(cx: *mut JSContext, | |||
| let type_str = structured_reader.read_str(); | |||
| let target_global = GlobalScope::from_context(cx); | |||
| let blob = Blob::new(&target_global, BlobImpl::new_from_bytes(blob_buffer), type_str); | |||
| return blob.reflector().get_jsobject().get() | |||
| let js_object = blob.reflector().get_jsobject().get(); | |||
| if let Some(mut holder) = target_global.structured_clone_holder() { | |||
This comment has been minimized.
This comment has been minimized.
jdm
Aug 2, 2018
Member
As pointed out, this change means we leak any blob that is sent through the structured clone algorithm because it is now always reachable from the global. I propose we return to your original design, with an important modification: there's no need to use Rc; we should be able to create the holder on the stack in read_clone, since the holder does not need to exist beyond that stack frame when the cloned values have been read into the rooted rval value.
This comment has been minimized.
This comment has been minimized.
|
Yes, that is an accurate summary :) |
2a91e35
to
5040142
|
@jdm ready for review |
4621900
to
155df0c
|
LGTM |
| @@ -104,14 +104,21 @@ impl StructuredCloneReader { | |||
| } | |||
|
|
|||
| unsafe fn read_blob(cx: *mut JSContext, | |||
| r: *mut JSStructuredCloneReader) | |||
| r: *mut JSStructuredCloneReader, | |||
| closure: *mut raw::c_void) | |||
This comment has been minimized.
This comment has been minimized.
jdm
Aug 7, 2018
Member
Let's make this take &mut StructuredCloneHolder instead. Then we don't need with_structured_clone_holder and can use holder.blob = Some(blob) directly.
| -> *mut JSObject { | ||
| assert!(tag < StructuredCloneTags::Max as u32, "tag should be lower than StructuredCloneTags::Max"); | ||
| assert!(tag > StructuredCloneTags::Min as u32, "tag should be higher than StructuredCloneTags::Min"); | ||
| if tag == StructuredCloneTags::DomBlob as u32 { | ||
| return read_blob(cx, r) | ||
| return read_blob(cx, r, closure) |
This comment has been minimized.
This comment has been minimized.
|
@jdm Ok, comments addressed... |
|
@bors-servo r+ |
|
|
Use a structured clone holder to store rooted clones <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [ ] `./mach build -d` does not report any errors - [ ] `./mach test-tidy` does not report any errors - [ ] These changes fix #21164 (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/21218) <!-- Reviewable:end -->
|
|
gterzian commentedJul 20, 2018
•
edited by SimonSapin
./mach build -ddoes not report any errors./mach test-tidydoes not report any errorsThis change is