-
Notifications
You must be signed in to change notification settings - Fork 123
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
Integrate with node's event loop #2
Comments
Doing this right -- having access to a single tick in libuv -- is probably not that hard to add to libuv |
What about the inverse? Apple doesn't offer a I'm currently playing around with some workaround code like: function tick () {
while ($.CFRunLoopRunInMode($.kCFRunLoopDefaultMode, 0.02, 1) == $.kCFRunLoopRunHandledSource);
process.nextTick(tick)
}
// Begin the ghetto event loop
tick(); It actually seems like it's working! I'm going to do some more experimenting. The only downside with a technique like this is that it would require some NodObjC-specific way of starting the event loop, like I would also like to experiment with the inverse, like you're talking about @aredridel, but that looks like it will be a little bit tougher, especially if I want to support node <= 0.6.x (node 0.7.x already has |
That makes sense. You could special-case that API and do it behind the scenes... Or just document that NodObjC needs a special main loop. |
Maybe this could be useful for someone else using Cocoa: If you don't want to block libuv's loop with app('finishLaunching');
var userInfo = $.NSDictionary('dictionaryWithObject', $(1),
'forKey', $('NSApplicationLaunchIsDefaultLaunchKey'));
var notifCenter = $.NSNotificationCenter('defaultCenter');
notifCenter('postNotificationName', $.NSApplicationDidFinishLaunchingNotification,
'object', app, 'userInfo', userInfo);
function tick () {
var ev;
while(ev = app('nextEventMatchingMask', 4294967295,
//$.NSAnyEventMask is losing precision somewhere
'untilDate', null, // don't wait if there is no event
'inMode', $.NSDefaultRunLoopMode,
'dequeue', 1)) {
app('sendEvent', ev);
}
app('updateWindows');
if(shouldKeepRunning) {
process.nextTick(tick);
}
}
tick(); It's based on Demystifying NSApplication by recreating it Thanks for making NodObjC. It's awesome! |
@JeanSebTr Wow nice code there! That definitely looks like a nice solution for the time being. Thanks! |
Doing this should be more "official" using the new Hopefully I'll get a chance to look into that soon! |
This might be a good example to look at https://github.com/philips/eventloops (I also noticed uv_backend_fd is not in node 0.8.x) |
Nice find @sandeepmistry! I'll take a look into that.
Yes that's correct, it's a relatively new API. It should be in some of the later v0.9.x releases and newer. |
For those who found this issue by Google, here is one solution:
var $ = require('NodObjC')
require('uvcf').ref() // ← register libuv's event loop in CF's event loop |
For those who come by and see this: |
I've been working on a project integrating node/osx, i've successfully merged the two loops that pass all of node's unit tests and have the same benchmark performance as node. You can see the code at the link below, perhaps there's a way of porting it into a .mm and including it in nodobjc with node-gyp. https://github.com/trueinteractions/tint2/blob/master/modules/Runtime/Main_mac.mm |
Nice Trevor! So attempts to port the code directly to NodObjC JS didn't quite work correctly, IIRC? |
@TooTallNate Yeah, there doesn't seem to be a way to instruct uv to run its event loop from JS. uv wants to kick up v8 callbacks, but the v8 isolate is locked every time UV tries because its being called from a javascript stack with an already running isolate. Unless i'm mistaken that seems to be a core issue that's unavoidable, unless you can figure out a way to create and release a v8::isolate while in javascript. And if you can, i'd be impressed :). |
BTW, only the code on lines 50-76 and lines 99 & 100 in https://github.com/trueinteractions/tint2/blob/9558d160e9a18316f76183b0d23afa116c9cb486/modules/Runtime/Main_mac.mm is relevant to the conversation, it could be as easy as doing: static int embed_closed;
static uv_sem_t embed_sem;
static uv_thread_t embed_thread;
static void uv_event(void *info) {
int r;
struct kevent errors[1];
while (!embed_closed) {
uv_loop_t* loop = uv_default_loop();
int timeout = uv_backend_timeout(loop);
int fd = uv_backend_fd(loop);
do {
struct timespec ts;
ts.tv_sec = timeout / 1000;
ts.tv_nsec = (timeout % 1000) * 1000000;
r = kevent(fd, NULL, 0, errors, 1, timeout < 0 ? NULL : &ts);
} while (r == -1 && errno == EINTR);
// Do not block, but place a function on the main queue, run the
// node block then re-post the semaphore to unlock this loop.
dispatch_async(dispatch_get_main_queue(), ^{
uv_run(uv_default_loop(), UV_RUN_NOWAIT);
uv_sem_post(&embed_sem);
});
// Wait for the main loop to deal with events.
uv_sem_wait(&embed_sem);
}
}
void SomeNodeFunctionThatRunsUV(v8::Arguments ... ) {
embed_closed = 0;
uv_sem_init(&embed_sem, 0);
uv_thread_create(&embed_thread, uv_event, NULL);
}
void SomeNodeFunctionThatStopsUV(v8::Arguments ... ) {
embed_closed = 1;
uv_thread_join(&embed_thread);
} into a node callback function, the only other issue I can see is this would need to be kicked off when NSapp's delegate runs applicationDidFinishLaunching:. I haven't tested it in other contexts. I'm knee deep in some other things right now, but if I have some spare time i'll through this into a node module and see if I can get it working. Unless, does someone else want to ? |
Presumably this would work also in an XPC service's main run loop (if configured with one) or another thread's with its CF run loop set up accordingly?
|
@mz2 Hypothetically yes, just as long as the uv_event gets its own thread (otherwise it'll block the current one). I should mention if your CFEventLoop isn't inside the main thread (which i'd be baffled as to why/how) you'll find yourself with a whole other issue. Curiously, I thought XPC was mainly used as a replacement for IPC and was mostly related to process communications. |
XPC is indeed an IPC mechanism, but the reason it exists really is for building "XPC services", which are launchd managed processes created by applications on OSX (and actually on iOS too though the APIs for it are private). XPC services are helpful for a few reasons when architecting software on OSX:
Node would make a lot of sense on OSX in writing self contained services which a larger app would call onto. An XPC service can be configured either to emply a 'regular' run loop or simply by GCD based threads (default, if I remember correctly). There's more info on creating XPC services here: |
mz2, hm i'm not too familiar with XPC to comment if its useful, however if the spun up XPC "service" is intended to run on anything other than the main thread you'd have an issue. I believe UV/node are fine on a different thread they just need to be consistant. You'd have to make sure the uv_run is placed on the correct thread, for the code i submitted it just plops it on the main thread. |
It can indeed be configured to have a main thread with a similar run loop to a regular Cocoa app. Sounds promising :) |
One last note to leave here, if the uv event loop exits due to uv_stop but the process doesn't the uv_event thread will spin up to 100% cpu (since the backend fd is dead). How this should be dealt with varies based on the use case you have. My use case required uv to spin endlessly, or never run uv_stop. To deal with this I attached an asynchronous dummy event that will never fire to prevent uv_stop from ever being executed (and simply exit when i want too). There may be other ways of listening for uv_stop to terminate the threads, timeout seems to get set to 0 if the uv loop should be stopped, and -1 is returned/set to timeout if uv is trying to say "i have no idea what the timeout should be, there's still events to be processed, but no events left on the schedule, and we're still waiting but who knows how long". |
Some non-UI NSMetadataSearch example code leveraging the working event loop made possible by pull request 56. The example monkey patches in the absence of the pull request being applied. |
@shanewholloway Have you figured out a way to make this EventLoop class replace |
Sure. Updated pull request #56 with revised |
Thanks for the high-praise! I've just been here before in a cross-platform C/C++ library with bindings for Node and Python. ;) Most of the work is in learning Node's |
@shanewholloway VERY impressive. Your'e the type of guy who if he has a few hours of free time runs a 100 mile marathon, saves a bald eagle... from a shark. Well done. +1 for merge. |
@shanewholloway I'm curious as to why on your gist example, the "applicationDidFinishLaunching" callback never gets invoked. Any thoughts there? |
Apologies for the oversight. Forgot to add the call to 2015-01-14 21:05 PST: Edit confirmed to be working on my build. |
Closing. Thanks again @shanewholloway :) |
I played with Shane's gist of Jan 14, replaced setInterval by setTimeout to just verify that it works, but not add more load. I observed that helloworld2.js sitting there waiting consumes about 42% of my CPU (Core i7 mac mini) using the EventLoop appraoch. |
Yes, a CPU-intensive event loop is the expected behavior of this implementation example. The specific combination I put together for the sample is written to go "flat-out", running the Cocoa event loop non-blocking. This will return control of the process to NodeJS for additional javascript-land processing. A real GUI app will want to tweak This balance is why I put EventLoop implementation forward as an example, but not yet as a package. The code is good enough, hopefully, to be forked and improved upon. But I wasn't able to put enough care and thought into the API to provide control over that balance yet. (That pesky day job…) And there are other concerns as well – for instance, I'd suggest looking into cluster module style forking approach to separate GUI and server processes. |
Ideally we would be able to have libuv poll the Cocoa event loop fd and be notified by a JavaScript event whenever there's Cocoa events to process. This would give the optimal CPU usage / responsiveness. @shanewholloway Do you know if there's any possible way to poll the event loop file descriptor, or something equivalent to that? |
Possibly related: http://www.cocoabuilder.com/archive/cocoa/224547-questions-about-nsapplication-run.html#224616
|
@TooTallNate I do not know. I would suspect a collection of descriptors. |
@shanewholloway I played around with untilDate but did not find a great balance between CPU usage (let's say 1 digit percentage) and GUI responsiveness. |
I know this is old but for anyone else wondering how to integrate node/xxx event loop with cocoa:
It took me a while but it actually works perfectly and it has very low CPU overhead. BTW: you need to call event handling from the main thread (that's why you need that |
This is a tough one... But basically since node maintains it's own event loop, it makes it rather hard to make Objective-C's
NSRunLoop
orCFRunLoop
integrate with it.Currently, some code as simple as this will crash your terminal tab, since node repl runs in raw mode and we're subsequently starting a new event loop on the main thread, even Ctrl+C won't get you out:
There is only one solution that I can think of: Write a Cocoa backend for libev (or libuv) so that it's underlying event loop is
NSRunLoop
. So far I have not looked into what that would entail, but I'm assuming it's not a trivial task...Any additional insight on the subject would be appreciated in the meantime.
/cc @ry
The text was updated successfully, but these errors were encountered: