|
From: | Richard Frith-Macdonald |
Subject: | Re: NSStreams and runloops and polling |
Date: | Tue, 25 Apr 2006 16:05:32 +0100 |
On 13 Apr 2006, at 20:24, Jeremy Bettis wrote:
Someone broke NSFileHandles pointing to pipes on mingw, so I am looking at the right way to fix it.
It's been broken a long time (assuming it ever worked) ... at least since the rewrite to use native windows event handling.
Ok, so here is the problem. On windows, a file handle to a pipe or a file (as opposed to a network socket) doesn't signal when you can read/write. This is very different from unix. For normal files you can use overlapped IO to read in the background, however for pipes you cannot. The only way to have a handle that you could add to the NSRunLoop you would have to:1) Create an event object (CreateEvent)2) Create a new thread, which will block reading/writing data on the pipe handle3) When the thread gets data signal the event object4) The main thread can wait on the event object using WaitForMultipleObjects.
Yes ... pipes on windows are fairly horrible ... but named pipes are not quite as bad. With a named pipe you can do overlapped I/O, which removes the need to create a separate thread. I implemented NSStream support for pipes using windows named pipes with unique hidden names for the pipes ... so you can treat them as anonymous pipe streams as far as the public API is concerned. The intention was to re-implement NSPipe on top of NSStream so that the complicated/messy low-level code would be localised (and later to reimplement other I/O code in terms of NSStream too).
This seems like it might be overkill. So I noticed that NSRunLoop has a ET_TRIGGER event type which the comments say is for the NSStream class. So I implemented NSFileHandle to use this, however, now if you call -[NSFileHandle readInBackgroundAndNotify] the program will go to 100% cpu usage.What is the right way to use this ET_TRIGGER mode?
The current SVN code (experimental) adds ET_TRIGGER as a watcher type which is triggered every time the loop is run ... so if an object of this type sits in the loop without doing anything else. you would expect the process to use all the free CPU. This allows you to add a watcher which will be triggered as soon as the loop is run and then remove itsself.
However, there is also a new method for watchers ... if the object responds to the -runLoopShouldBlock: selector, the loop will call it immediately before each iteration of the loop and override behavior as follows...
The argument to the method is used to return a BOOL value which tells the run loop whether the whether is to be considered to be in a 'triggered' state or not. If it is YES, the loop will call the callback method of the object once it has done its select/poll/sleep.
The return value of the method specifies whether the loop should block or not. If it is YES then the loop will add the resource of the watcher to the set of select/poll inputs it waits for, otherwise it will not.
If any watcher returns YES for both values (ie it is in a triggered state and the loop should not block for it), then the poll/select will have an instant timeout as it means there is already a triggered event available to process. This is the default behavior assumed for a watcher of type ET_TRIGGER which does not respond to - runLoopShouldBlock:
The fun thing about this is the ability for a watcher to dynamically modify the way the runloop handles it depending on its current state. It allows NSStream to set off an overlapped read/write and have the loop block waiting for completion when no other I/O is possible, but also have the loop repeatedly trigger as long as the client code has not consumed all the data read or provided anythign to write.
[Prev in Thread] | Current Thread | [Next in Thread] |