[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: NSRunLoop Tidying
From: |
Richard Frith-Macdonald |
Subject: |
Re: NSRunLoop Tidying |
Date: |
Fri, 8 Oct 2010 17:14:03 +0100 |
On 8 Oct 2010, at 13:24, David Chisnall wrote:
> Hi Everyone,
>
> One of the things that has been on my to-do list for a long time is tidying
> up NSRunLoop. The current implementation predates modern event delivery
> mechanisms and tries to shoe-horn the UNIX approach everywhere.
>
> Modern systems include a unified mechanism for event delivery, and a lot of
> the hacks to work around the fact that older *NIX systems didn't have these
> are complicating the code a lot on these platforms.
>
> My main motivation for doing this is to support the KQueue APIs on *BSD.
> This provides a single function for waiting on every kind of event. Windows
> has had something similar forever, Linux can do the same thing with timerfd
> in recent versions, and Solaris has completion ports, which provide similar
> functionality.
>
> As I understand it, there are four kinds of things that a runloop has to
> handle:
>
> 1) Events on a file descriptor (read / write).
> 2) Timers expiring.
> 3) Timeout expiring (e.g. -runMode:beforeDate:)
> 4) Messages from threads.
>
> Of these, (3) is trivial. All wait mechanisms support this, so implementing
> it is trivial with any underlying mechanism. Similarly, (1) is the default
> case for pretty much all of the event-waiting calls, so it's simple.
>
> Currently, (2) is implemented in terms of (3). This is quite untidy. It
> means that we need to maintain an ordered list of timers in the runloop code,
> find the one closest to the present, and then use this as the timeout.
Actually the current implementation uses an unsorted list as depending on the
ordering introduced a subtle bug ... a timer can be present in more than one
run loop, and/or in the same loop in more than one mode. If it fires in one
loop and changes its next fire date (ie is a repeating timer), that
automatically breaks the ordering for the other loops/modes that it exists in.
Of course, what we really want is probably something more complex to keep track
of all the loops/modes the timer is in, and tell them that they need to
re-order their list of timers ... if we did that we could depend on the order
in the list.
The API demands that we keep track of the timers some way (so we can return the
correct value from the -limitDateForMode: method), but it doesn't have to be an
ordered list if a better alternative is available (though I can't really think
of one).
>
> This is required with traditional select() and poll(), but Win32 has
> SetTimer, Linux has timerfd(), and *BSD has kevent(), all of which allow you
> to schedule timer events and wait on them just like fd events.
I don't really see how that can help since the API explicitly separates timer
firing (done in -limitDateForMode:) and I/O event handling ... so having timers
and I/O events use the same mechanism in the operating system does not mean
that they can be done at the same place in the code or with a single system
call. So using things like SetTimer() and timerfd() may just add complexity.
> (4) is trivial on Windows, via PostThreadMessage(), which allows you to
> deliver a message to a specific thread. Kqueue has a EVFILT_USER, which
> allows you to deliver events to other threads, and Solaris 10 event ports
> have something similar.
>
> To properly support efficient native APIs, we should move the handling of all
> of these into the per-platform code and remove anything platform-specific
> from the general code in -base. There are lots of random #ifdefs scattered
> about the place currently.
>
> My overall plan is:
>
> - Move -addTimer:forMode: into the platform-specific code.
> - Tidy up the GSRunLoopCtx stuff so it isn't quite so full of #ifdefs.
> - Make the NSObject methods in NSThread.m call (private) runloop methods that
> delegate to the platform-specific code.
> - Remove classes like GSPerformHolder from the generic code.
> - Implement a kqueue back end for *BSD.
> - Make the win32 back end use SetTimer() and PostThreadMessage().
>
> Comments / suggestions?
I agree with "Dr. H. Nikolaus Schaller" <address@hidden> ... that it would make
most sense to start by implementing the CFRunLoop API, and then re-implement
NSRunLoop on top of it (it's quite hard to do the other way round).
Also, you need to put together *LOTS* to testcases in the testsuite to ensure
that the implementation behaves exactly like OSX does ... this is because
NSRunLoop is a really key part of so much of the system and very subtle/minor
changes in behavior can have really nasty effects on applications. It's really
not good enough to have an implementation which does what the documentation
says, we have to mimic actual OSX behavior pretty closely.
My guess is that your idea of re-implementing timer handling in a platform
specific way is actually a recipe for a less maintainable and more complex
codebase, since the timeout behavior is largely mandated by the API and I can't
currently see how platform specific APIs will help... but maybe you have some
ideas you didn't mention here. In general it's a good idea (for
maintainability) to keep platform specific code to a minimum, so for instance I
wouldn't use PostThreadMessage() as it's probably no more efficient than the
current code using SetEvent(), and would demand changes to NSThread to hold
additional thread data and a message queue.
On the other hand, the kqueue API is supposed to be significantly more
efficient than poll/select ... so using that on BSD and the similar epoll API
on Linux would be good.
So, what would be great is to:
1. Implement the CFRunLoop
2. Keep platform specific code to a minimum (though make use of it where it
really helps performance) for maintainability
3. Have testcases to check that it behaves like OSX on all platforms
4. Finally, re-implement NSRunLoop on top of CFRunLoop