I'd like to solicit some design feedback for a new feature that I'd like to contribute. I apologize for the length of this message, but hopefully it contains all of the details needed to get the conversation started.
First, some quick background:
Over the past few years I've written a few modules[1] that provide access to a number of technologies that are either part of Darwin or one of Apple's Darwin-based OSes (OSX, iOS, etc). A common feature that keeps occurring is their reliance on an active run loop. Normally, this is either NSRunloop or CFRunLoop (they are distinct APIs, but they share a common set of event sources.) In the past, I've resorted to adding a persistent call-out to the default back end, which is similar to the approach taken by the GTK2 module. This is normally good enough for sitatuions where events are relatively infrequent, such as GUI-input work.
However, it's not always suitable for higher interrupt volumes and it also has the side effect of presenting a continual, though low, load on the CPU. This isn't a huge problem, but it's unclean and if one were to increase the frequency of the poll-out to the CF/NS run loop, the load becomes higher, and thus harder to ignore.
A Solution:
After doing some research on better ways to approach the problem, I discovered that CF/NS run loops use kqueue at their core. As a result, a kqueue can be added to the run loop and polled for events. That's convenient, because pike uses kqueue on Darwin in its default backend, Pike.PollDeviceBackend. Swapping out the direct call to kevent() with one that calls the run loop results in "native" Pike I/O working the way it should, plus Darwin layered functionality working properly as well (and low CPU utilization, too).
I think that this is a useful new feature to add to Pike, but I've got some concerns about how best to include it. Right now, I've created a branch called hww3/cfrunloop_backend[2] that includes the functionality and swaps out the existing implementation under Darwin. That is, of course, the simplest approach, but I think is probably not the best idea:
Pros:
- Uses kqueue() at its core, so the existing backend will fire events in the same way.
- Allows a number of technologies to function normally when used from with Pike processes, without the use of hacks.
Cons:
- There is exactly one Run Loop per thread, which, if used universally, could present issues because this means behavior would differ from other Backends.
- There is slightly more overhead per trip through the backend due to events being single-shot. This means that the kqueue has to be re-added to the runloop each loop. I don't think it's a major overhead, it still exists.
Alternate Implementations:
A second approach would be to provide an additional backend implementation, say Pike.CFBackend, that's available on Darwin based systems. I see one or two problems with this, though:
- There would need to be a way to activiate this selectively at runtime, something like set_default_backend(Pike.CFBackend()), which probably doesn't exist right now, right?
- Because 90% of the implementation would be shared with PollDeviceBackend, I think it would involve a fair amount of code duplication inside of backend.cmod, which I think would make maintenance more difficult.
A third (and right now, I believe the best) approach would be to add one or two methods to Pike.Backend that could be implemented by PollDeviceBackend on Darwin and be used to activate this alternate mode.
For example:
int(0..1) has_client_mode()
which would return true if there were a client optimization (I suggest client because CFRunLoop would be more useful for client apps than traditional server based apps, though that's certainly not an either/or situaiton) and:
void enable_client_mode(int(0..1))
that would trigger calls to CFRunLoopRunInMode() instead of kevent() directly.
Input Sought:
I think either of the two alternative approaches would be just fine, assuming that the concerns could be addressed. I'm just not sure which would be best from a design and implementation standpoint (I sort of think the last option would be simplest for _me_ to get working, but if someone has desire to help tackle it, I'd be game to play along).
The upshot of allowing the proposed backend changes for other folks using Pike are that:
a) SDL and GL becomes usable out of the box on OSX b) I am prepared to contribute a few pieces of code, namely: - FSEvents module - Updated Filesystem.Monitor that uses FSEvents on Darwin and inotify on Linux to eliminate polling - DNS_SD querying support on OSX - ObjectiveC bridge (possibly)
Hopefully we can have a bit of discussion about the options and approaches and come up with a solution that works without being terribly inconvenient. Please do drop me a note if anything about this proposal is unclear.
Best,
Bill
[1] Public.ObjectiveC, Public.System.FSEvents, Public.IO.IOWarror, additions to DNS-SD; SDL and GL on OSX also share this requirement. [2] http://hg.welliver.org/pike/changesets, not yet pushed to the pike master repo.