-
Notifications
You must be signed in to change notification settings - Fork 29.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Several bug fixes related to MessageChannel/MessagePort #21540
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you a lot! :)
src/node_messaging.cc
Outdated
env_->isolate()->ThrowException( | ||
env_->domexception_function() | ||
->NewInstance(env_->context(), arraysize(argv), argv) | ||
.ToLocalChecked()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using .ToLocalChecked()
here is going to be problematic in the presence of worker.terminate()
. It should be fine to just return if NewInstance()
returns an empty handle
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, maybe add a CHECK(!env_->domexception_function().IsEmpty());
?
src/node_messaging.cc
Outdated
Local<Value> transfer_v) { | ||
auto isolate = env->isolate(); | ||
auto obj = object(isolate); | ||
auto context = obj->CreationContext(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you avoid auto
for all of these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But… auto
is too exciting!
For reals though, I can change it of course, but I personally thought the variable names were pretty self explanatory in terms of types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are, except maybe in the case of obj
… but I’d personally really prefer to keep auto
for cases where the exact type is hard to spell out/unknown – obviously, other people will have different experiences, but for me using auto
usually makes writing the code easier and reading it harder, even in these “trvial” cases…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think mostly this code is read a lot more than it is written - while I use auto
in my own code in Node.js I had zero (well ~2) C++ commits but I've reviewed at least a hundred PRs. So 👍 on spelling up the types.
src/node_messaging.cc
Outdated
|
||
// Check if the target port is posted to itself. | ||
if (data_->sibling_ != nullptr) { | ||
for (const auto& port : transferred_ports) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, using MessagePort* port : transferred_ports
is cleaner imo :)
src/node_messaging.cc
Outdated
env->isolate()->ThrowException( | ||
env->domexception_function() | ||
->NewInstance(context, arraysize(argv), argv) | ||
.ToLocalChecked()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here for .ToLocalChecked()
src/node_messaging.cc
Outdated
// is closed or detached. | ||
if (data_ == nullptr) { | ||
USE(msg.Serialize(env, context, message_v, transfer_v)); | ||
return Just(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to fulfill the Maybe<T>
contract for this function, we should return an empty Maybe if msg.Serialize()
returns an empty Maybe, right?
src/node_messaging.cc
Outdated
transferred_ports.push_back(port); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we integrate this with the loop in Message::Serialize
? There’s a lot of common code, and I’m not sure we really need to unpack the list twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually had that approach initially, but then I realized that I would need to access MessagePort
and/or MessagePortData
internals from Message
. I could of course add a friend
but I decided that object serialization should be independent of the channel communication. Also, aligning with the HTML Standard (whose algorithm is written in this style) would be good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize that it’s tricky, but I’d really like to merge these … if you want, I can try to figure out something myself?
I think it we can make it work if we added a MessagePort* source
argument to Serialize()
that we check against, and add a getter for Message::message_ports_
that we check after serialization?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, at least Firefox and Chrome invoke getters for transferList
only once, as an extra data point
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it we can make it work if we added a
MessagePort* source
argument toSerialize()
that we check against, and add a getter forMessage::message_ports_
that we check after serialization?
Sure, that would work. However, checking things after serialization would mean ArrayBuffer
s have already been neutered, etc.
Also, at least Firefox and Chrome invoke getters for transferList only once, as an extra data point
Yeah, browsers usually have an IDL conversion layer, and use native data types for all internal operations. We could do the same and convert transferList
to a std::vector
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it we can make it work if we added a
MessagePort* source
argument toSerialize()
that we check against, and add a getter forMessage::message_ports_
that we check after serialization?
Sure, that would work. However, checking things after serialization would mean
ArrayBuffer
s have already been neutered, etc.
To clarify, the first check (for source
) could still happen before the actual serialization, while still unpacking the transfer list. The second check would happen afterwards, but that’s also what this PR currently does (and intentionally, if I read the added test-worker-message-port-transfer-target.js
correctly?)
lib/internal/dom-exception.js
Outdated
super(); | ||
this[kMessage] = `${message}`; | ||
this[kName] = `${name}`; | ||
Error.captureStackTrace(this, DOMException); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this required for stuff that extends Error
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me double-check.
src/node_messaging.cc
Outdated
uint32_t length = transfer_list->Length(); | ||
for (uint32_t i = 0; i < length; ++i) { | ||
Local<Value> entry; | ||
if (!transfer_list->Get(context, i).ToLocal(&entry)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment for why this is needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, what do you mean? This is how to access array elements through the V8 API.
src/node_messaging.cc
Outdated
.IsNothing()) { | ||
return; | ||
|
||
// Per spec, we need to serialize the input message even if the MessagePort |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The MessagePort
/MessageChannel
API is pretty close to the Messaging part of the HTML spec, and not related to Workers per se.
At least I wrote it with that spec in mind and using it as a reference document.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While workers are a different matter, the channel messaging API is in my opinion simple enough so that almost* full compatibility with HTML is a worthwhile goal.
* EventEmitter
notwithstanding.
lib/internal/dom-exception.js
Outdated
@@ -0,0 +1,68 @@ | |||
'use strict'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we calling these DOM exceptions?
WebWorkers (assuming that's the behavior we want to mimic) says:
The term DOM is used to refer to the API set made available to scripts in Web applications, and does not necessarily imply the existence of an actual Document object or of any other Node objects as defined in the DOM Core specifications. [DOM]
I would prefer a different name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, we could of course use a different name, but then it would make us deviate from browsers in an area that I don't think we need to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to using DOMException. -1 to dash in filename
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@devsnek Fair enough.
|
||
assert.throws(common.mustCall(() => { | ||
port1.postMessage(null, [port1]); | ||
}), common.mustCall((err) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be an expectsError
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per spec, message
is an enumerable getter on the DOMException
prototype, rather than a non-enumerable own property on the DOMException
object, which expectsError
expects:
Lines 704 to 706 in a9d9d76
const descriptor = Object.getOwnPropertyDescriptor(error, 'message'); | |
assert.strictEqual(descriptor.enumerable, | |
false, 'The error message should be non-enumerable'); |
Good work and nice behaviour change cc @nodejs/workers |
@addaleax @benjamingr PTAL. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, nice work!
src/node_messaging.cc
Outdated
Local<Object> obj = object(isolate); | ||
Local<Context> context = obj->CreationContext(); | ||
|
||
std::vector<MessagePort*> transferred_ports; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this unused now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, indeed!
@addaleax In case you didn't see: I pushed another commit (1cb3f13bfa071f4a80a41841eb067a67e07d7984) fixing some more issues around the observable behaviors after |
|
||
const nameToCodeMap = new Map(); | ||
|
||
class DOMException extends Error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should there be anything added to the documentation about this new error type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure, but I'm inclined to say no, as it's pretty different from rest of errors.md
, and DOMException
is not exposed to userland by default.
Ping @addaleax. Would be great if you could take a look after #21540 (comment). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still LGTM :)
Currently, transferring the port on which postMessage is called causes a segmentation fault, and transferring the target port causes a subsequent port.onmessage setting to throw, or a deadlock if onmessage is set before the postMessage. Fix both of these behaviors and align the methods more closely with the normative definitions in the HTML Standard. Also, per spec postMessage must not throw just because the ports are disentangled. Implement that behavior.
b711b8d
to
46701ab
Compare
Rebased. New CI: https://ci.nodejs.org/job/node-test-pull-request/15720/ |
Landed in 602da64...f374d6a. |
PR-URL: #21540 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
Currently, transferring the port on which postMessage is called causes a segmentation fault, and transferring the target port causes a subsequent port.onmessage setting to throw, or a deadlock if onmessage is set before the postMessage. Fix both of these behaviors and align the methods more closely with the normative definitions in the HTML Standard. Also, per spec postMessage must not throw just because the ports are disentangled. Implement that behavior. PR-URL: #21540 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
PR-URL: #21540 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
Currently, transferring the port on which postMessage is called causes a segmentation fault, and transferring the target port causes a subsequent port.onmessage setting to throw, or a deadlock if onmessage is set before the postMessage. Fix both of these behaviors and align the methods more closely with the normative definitions in the HTML Standard. Also, per spec postMessage must not throw just because the ports are disentangled. Implement that behavior. PR-URL: #21540 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
Currently, transferring the port on which postMessage is called causes a
segmentation fault, and transferring the target port causes a subsequent
port.onmessage setting to throw, or a deadlock if onmessage is set
before the postMessage. Fix both of these behaviors and align the
methods more closely with the normative definitions in the HTML
Standard.
Also, per spec postMessage must not throw just because the ports are
disentangled. Implement that behavior.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes