Skip to content
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

Closed
wants to merge 2 commits into from

Conversation

TimothyGu
Copy link
Member

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), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. labels Jun 26, 2018
@TimothyGu TimothyGu requested a review from addaleax June 26, 2018 04:47
@TimothyGu TimothyGu added the worker Issues and PRs related to Worker support. label Jun 26, 2018
Copy link
Member

@addaleax addaleax left a 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! :)

env_->isolate()->ThrowException(
env_->domexception_function()
->NewInstance(env_->context(), arraysize(argv), argv)
.ToLocalChecked());
Copy link
Member

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

Copy link
Member

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());?

Local<Value> transfer_v) {
auto isolate = env->isolate();
auto obj = object(isolate);
auto context = obj->CreationContext();
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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…

Copy link
Member

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.


// Check if the target port is posted to itself.
if (data_->sibling_ != nullptr) {
for (const auto& port : transferred_ports) {
Copy link
Member

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 :)

env->isolate()->ThrowException(
env->domexception_function()
->NewInstance(context, arraysize(argv), argv)
.ToLocalChecked());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here for .ToLocalChecked()

// is closed or detached.
if (data_ == nullptr) {
USE(msg.Serialize(env, context, message_v, transfer_v));
return Just(true);
Copy link
Member

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?

transferred_ports.push_back(port);
}
}
}
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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?

Copy link
Member

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@addaleax

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?

Sure, that would work. However, checking things after serialization would mean ArrayBuffers 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.

Copy link
Member

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 to Serialize() that we check against, and add a getter for Message::message_ports_ that we check after serialization?

Sure, that would work. However, checking things after serialization would mean ArrayBuffers 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?)

super();
this[kMessage] = `${message}`;
this[kName] = `${name}`;
Error.captureStackTrace(this, DOMException);
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me double-check.

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))
Copy link
Member

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?

Copy link
Member Author

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.

.IsNothing()) {
return;

// Per spec, we need to serialize the input message even if the MessagePort

This comment was marked as resolved.

Copy link
Member

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.

Copy link
Member Author

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.

@@ -0,0 +1,68 @@
'use strict';
Copy link
Member

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?

Copy link
Member Author

@TimothyGu TimothyGu Jun 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec for MessagePort calls it DOMException. The interface is actually specified in Web IDL, which is a spec we want to follow as well. So the name isn't up to us to change.

Copy link
Member Author

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.

Copy link
Member

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

Copy link
Member Author

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) => {
Copy link
Member

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?

Copy link
Member Author

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:

node/test/common/index.js

Lines 704 to 706 in a9d9d76

const descriptor = Object.getOwnPropertyDescriptor(error, 'message');
assert.strictEqual(descriptor.enumerable,
false, 'The error message should be non-enumerable');

@benjamingr
Copy link
Member

Good work and nice behaviour change cc @nodejs/workers

@TimothyGu
Copy link
Member Author

@addaleax @benjamingr PTAL.

Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, nice work!

Local<Object> obj = object(isolate);
Local<Context> context = obj->CreationContext();

std::vector<MessagePort*> transferred_ports;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this unused now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, indeed!

@TimothyGu
Copy link
Member Author

@addaleax In case you didn't see: I pushed another commit (1cb3f13bfa071f4a80a41841eb067a67e07d7984) fixing some more issues around the observable behaviors after port.close() has been called, but uv_close() not yet finished (so the MessagePort still has an intact MessagePortData pointer.)


const nameToCodeMap = new Map();

class DOMException extends Error {
Copy link
Member

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?

Copy link
Member Author

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.

@TimothyGu
Copy link
Member Author

TimothyGu commented Jul 3, 2018

Ping @addaleax. Would be great if you could take a look after #21540 (comment).

Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still LGTM :)

TimothyGu added 2 commits July 3, 2018 21:26
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.
@TimothyGu TimothyGu force-pushed the messaging-bug-fixes branch from b711b8d to 46701ab Compare July 4, 2018 01:27
@TimothyGu
Copy link
Member Author

@TimothyGu
Copy link
Member Author

Landed in 602da64...f374d6a.

@TimothyGu TimothyGu closed this Jul 4, 2018
@TimothyGu TimothyGu deleted the messaging-bug-fixes branch July 4, 2018 02:54
TimothyGu added a commit that referenced this pull request Jul 4, 2018
PR-URL: #21540
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
TimothyGu added a commit that referenced this pull request Jul 4, 2018
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>
@Trott
Copy link
Member

Trott commented Jul 4, 2018

Looks like a subsystem was made up for this one. ಠ_ಠ

This probably should have been lib,src: and not messaging:.

(Tooling flags messaging: as a problem. Are you landing commits manually? Get with the node-core-utils awesomeness!)

screen shot 2018-07-03 at 10 13 43 pm

targos pushed a commit that referenced this pull request Jul 4, 2018
PR-URL: #21540
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
targos pushed a commit that referenced this pull request Jul 4, 2018
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>
@targos targos mentioned this pull request Jul 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. worker Issues and PRs related to Worker support.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants