-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Callback-based IPC #579
Callback-based IPC #579
Conversation
This addresses one aspect of segment-boneyard#493. The basic idea here is that Nightmare’s IPC objects gain two methods, each to be used in opposite processes. - `ipc.respondTo(name, responder)` Registers a function that can respond to calls from another process. The `name` is handle by which other processes call it. The responder is a function that takes any number of arguments, where the last argument is a callback. This callback should be called when the responder’s work is complete. Any arguments will be passed back to the caller in the other process. In addition, the callback has a method named `progress` that can be called to emit `data` events in the other process. - `ipc.call(name, [arg, [arg, [...]]], [callback])` Calls a responder with the given `name` in another process. Any arguments between the `name` and `callback` are passed as arguments to the responder. The callback will be called when the responder’s work is finished. In addition, `ipc.call` returns an event emitter than can be used to handle `data` events indicating progress from the responder or a single `end` event, which will be called with the same arguments as and immediately prior to `callback`.
I'm mixed on this, but I can think of applications where this might be useful. For example, a similar concept is used in the download manager to look at download state out-of-band.
My first off-the-cuff idea would be to wrap Also, while I have in interest in how plugins are implemented as I have authored a few, I don't think I'm the only author.
I'm struggling to come up with a usecase for more than one responder. I would think responding to a given event twice would constitute a runtime error.
Maybe. You could be explicit about unregistering a responder before registering a different responder for the same action, eg if you wanted your plugin implementation of More as I let this marinate and as others chime in. :) |
Oh, neat. I don’t think the progress mechanism here would simplify that use case, but it does make me wonder if there’s room for some sort of more abstract create-a-ipc-sub-channel mechanism. That would probably have some utility in the single-electron-process model described in #561. Probably out-of-scope here, though?
I feel like
I think I mistyped it on the issue thread, but I was originally thinking of replacing
So do you think it should throw an exception? Or maybe just log a warning? |
I figured the single process model would have to have another identifier in That said, it's probably out of scope, but it does feel like process management/window management is going to depend on this change. (Or at least borrow from it heavily.)
Fair, maybe a warning is too heavy-handed. I don't necessarily disagree, but it does seem like a raw
Ahhh, okay. I understand.
I want to say exception, but I'm not sure I can make a case. In my read of your implementation, I don't think you can cause execution problems as the results are executed with |
Definitely out of scope, but if function Nightmare(options) {
...
this.window = electronProcess.call('createWindow', function onEnded(reason) {
// mark instance as cleaned up and done
});
}
// in actions.js
exports.useragent = function(useragent, done) {
this.window.call('useragent', useragent, done);
};
At the very least, I should update the plugin example in the README. Totally forgot about that. Maybe (this is a bit hare-brained), rename Really I think it should be clear that this
True, but that kind of problem is actually prevented even earlier by the fact that the responder registry can only hold one responder per name. So registering a second one with the same name bumps out the old one, and there is only ever one function that even has the opportunity to call back. I think we established that that’s fine. The “should we throw?” question is more about whether it’s ok to bump out a previously registered responder.
|
I went ahead and added logging of duplicate calls to |
@Mr0grog Good. I'm considering opening a branch off of master for v3 parts. Would you be willing to complete this PR and put it against that branch? Thoughts on doing such a thing? |
Sure? What’s “completing” mean—just wrapping up the rest of the built-in actions?
Generally I’d flip it around and have |
Yeah, that's what I had in mind - rounding out the rest of the existing methods with the IPC safety is sufficient for now. (The beauty of source control: we can always change it later.)
I've seen this done a lot of different ways, and all seem to be about equally as effective. I had no real reason beyond releases being cut from the master branch and that PRs are probably going to continue to come to the master branch. There also seems to be artifacts of a historical precedence from the v2 stuff for Electron being done in a separate branch, but my research there is cursory at best. I don't have a problem with changing that, even if it adds near-term work for existing PRs/merges.
100% with you. I'm not compelled one way or another. If you've got a good reason, we can do it how you suggested. Or not. You know, whichever. :) |
@rosshinkley do you want a new PR pointing to another branch, or should this still be going to master? (per discussion the other day about whether this is v2.4 or v3 feature) |
I think we agreed that since this is non-breaking, this should go into 2.4.x. To that end, I think it should go into this PR => master. |
Cool, I think this is done if nothing looks out of place to you, then. |
Pretty dang awesome. The parent PR removed a ton of code, a dependency and made things clean! Synergize!!
This addresses one aspect of #493. It should make IPC between the user’s process and the Electron process a) “safe” in that simultaneous calls can’t cause incorrect results and b) simpler. It’s based on discussion with @rosshinkley in issue #493.
The main idea here is that Nightmare’s IPC objects gain two methods, each to be used in opposite processes:
ipc.respondTo(name, responder)
Registers a function that can respond to calls from another process. Thename
is a handle by which other processes call it. The responder takes any number of arguments, where the last one is a callback. This callback should be called when the responder’s work is complete. Any arguments will be passed back to the caller in the other process. In addition, the callback has a method namedprogress
that can be called to emitdata
events in the other process.ipc.call(name, [arg, [arg, [...]]], [callback])
Calls a responder with the givenname
in another process. Any arguments between thename
andcallback
are passed as arguments to the responder. The callback will be called when the responder’s work is finished. In addition, this returns an event emitter than can be used to handledata
events indicating progress from the responder or a singleend
event, which will be called with the same arguments as and immediately prior tocallback
.Under the hood this is accomplished by having three special IPC messages:
CALL
indicates that a responder should be called. Sent from the parent, to the child. It has at least 2 arguments:CALL_DATA_{ID}
indicates progress data related to a call is being sent. Sent from the child to the parent. The ID in the name is the unique ID that was sent with the initial call above.CALL_RESULT_{ID}
indicates that this is the final result of the call. Sent from the child to the parent. The ID in the name is the unique ID that was sent with the initial call above.The above set of messages can be used in either direction, but they are probably only relevant now in terms of calls from the parent to the child. Maybe worth noting: a very similar (but more heavily built out and robust) mechanism is used internally in Electron’s
remote
module. This one is more minimal and light-weight, but sacrifices a lot of nice features that one has (support for circular references, complex types like dates, functions, promises, errors, and placeholder objects instead of string names).Notes and open questions
evaluate_now
anduseragent
as demo cases. A later commit will have to do the rest.ipc.emit()
andipc.on()
methods. That means the specialCALL*
messages can be pretty easily observed. I’m not sure that’s a big deal, but we could call the appropriate methods on theprocess
instead to make intercepting things harder.--harmony
flag (it’s 👍 in v5.x), so I removed it in favor of lots ofapply()
calls.--harmony
is still mentioned in the README (only down at the bottom), but not actually required in practice on Node 4+. I think it’s good that it’s not required.apply()
noise (not all) could be avoided by just sending arguments as an array through IPC instead of spreading them into the message arguments.respondTo
is called with the samename
more than once.I’d love feedback from anybody on this. Ideally this is a safe, backwards-compatible change (see (3) above), but it’s still a major (and deep) API change. We should be more 👍 than ¯_(ツ)_/¯ in order to accept it.