I just subscribed to this list a little while ago and wanted to respond to a few things that I saw in the archives. I probably won't be able to attribute things too accurately, and I'm sure that I've missed parts of the discussion and may be replying to some things out of context.
To the best of my knowledge, it's effectively the case that only one thread can fetch events from/communcate directly with the window server. Prior to Panther, this was the thread whose CFRunloop was the "main" CFRunloop (there's still some code in OpenMCL's Cocoa demo that tries to play around with this, and it used to be the case that something called an undocumented function - something like "_CFRunLoopSetMain" - to try to get around this. CF's "main" runloop ordinarily got created when CF was initialized, and it was associated with the thread that did that initialization.)
Panther put an end to all this: the thread that's allowed to talk to the Window Server is the application's initial thread (the one that started out by calling main(), way back when) and none other. (Any other thread can certainly try to call NSApplication's nextEventMatchingMaskWhateverTheOtherArgsAre, but it'll get a NULL pointer back.)
The basic model is that NSApplication's -run method loops around, getting events from the window server and propagating them through the responder chain. From what I understand, lots of other things go on between the time that the -run method asks for an event and the time that it's delivered: raw window server events arrive; some of them are passed to Carbon event handlers (which might in some cases call Cocoa methods), some are related to keeping the window server's model of what's on screen in synch with the application's (drawRect: methods get called at this time), etc. All of this is (by default) happening on the distinguished, blessed event thread.
Some of the time of course, an NSEvent will eventually get returned to the caller. Tim Moore and I tried some experiments about a year ago to see if we could intercept window-specific events and dispatch them to handlers that ran in window-specific threads (the thinking was that this was a lot closer to McCLIM's CLX backend's behavior.) The idea sort of worked (at least halfway). One way in which I remember it failing was when clicking on a window close button: the (blessed) event thread was waiting for the window-specific thread to unlock the view focus so it could ... do whatever it does, and the window-specific thread was waiting for a mouse-up event that never came. It might have been possible to think a little harder and avoid this particular deadlock situation, but I got the strong impression that there were lots more deadlock situations waiting to happen.
Apple had a Tech Note or similar document whose title was something like "AppKit is too thread-safe! Stop saying that it isn't !". That document enumerated a small number of reasonable-sounding guidelines; I don't think that it's too hard to follow them carefully and still see totally unreasonable behavior. I was trying a few months ago to build an NSTextView "manually" in some random (non-blessed) thread and it kept crashing; there could have been any number of reasons for that failure, but I eventually determined that as soon as I'd created an NSLayoutManager the blessed (if you'll pardon the expression) thread decided that that'd be a good time to do background glyph layout. (It wasn't ...). I couldn't find a reliable way of preventing that from happening; in Cocoa's implicit view of the world, an NSTextView could only be constructed in the blessed event thread by some event handler (why on earth would a compute-bound thread want to create an NSTextView or an NSLayoutManager ?) IIRC, I eventually wound up PROCESS-INTERRUPTing the event thread and asked it to make the NSTextView for me; that seemed to work fine.
I'm not sure that I'm not just violently agreeing with points that other people have made; if people are thinking about bouncing events around between multiple lisp threads, I hope that they're aware of these issues and have thought of things that I'm missing. The conclusion I came to is that Cocoa is really designed to have just about all of the interesting UI stuff happen in its blessed thread, and if any other approach is possible it may go so far against the grain as to be impractical.
Gary Byers gb@clozure.com
On Feb 5, 2004, at 1:47 PM, Gary Byers wrote:
I just subscribed to this list a little while ago and wanted to respond to a few things that I saw in the archives. I probably won't be able to attribute things too accurately, and I'm sure that I've missed parts of the discussion and may be replying to some things out of context.
To the best of my knowledge, it's effectively the case that only one thread can fetch events from/communcate directly with the window server. Prior to Panther, this was the thread whose CFRunloop was the "main" CFRunloop (there's still some code in OpenMCL's Cocoa demo that tries to play around with this, and it used to be the case that something called an undocumented function - something like "_CFRunLoopSetMain" - to try to get around this. CF's "main" runloop ordinarily got created when CF was initialized, and it was associated with the thread that did that initialization.)
Panther put an end to all this: the thread that's allowed to talk to the Window Server is the application's initial thread (the one that started out by calling main(), way back when) and none other. (Any other thread can certainly try to call NSApplication's nextEventMatchingMaskWhateverTheOtherArgsAre, but it'll get a NULL pointer back.)
The basic model is that NSApplication's -run method loops around, getting events from the window server and propagating them through the responder chain. From what I understand, lots of other things go on between the time that the -run method asks for an event and the time that it's delivered: raw window server events arrive; some of them are passed to Carbon event handlers (which might in some cases call Cocoa methods), some are related to keeping the window server's model of what's on screen in synch with the application's (drawRect: methods get called at this time), etc. All of this is (by default) happening on the distinguished, blessed event thread.
Some of the time of course, an NSEvent will eventually get returned to the caller. Tim Moore and I tried some experiments about a year ago to see if we could intercept window-specific events and dispatch them to handlers that ran in window-specific threads (the thinking was that this was a lot closer to McCLIM's CLX backend's behavior.) The idea sort of worked (at least halfway). One way in which I remember it failing was when clicking on a window close button: the (blessed) event thread was waiting for the window-specific thread to unlock the view focus so it could ... do whatever it does, and the window-specific thread was waiting for a mouse-up event that never came. It might have been possible to think a little harder and avoid this particular deadlock situation, but I got the strong impression that there were lots more deadlock situations waiting to happen.
Apple had a Tech Note or similar document whose title was something like "AppKit is too thread-safe! Stop saying that it isn't !". That document enumerated a small number of reasonable-sounding guidelines; I don't think that it's too hard to follow them carefully and still see totally unreasonable behavior. I was trying a few months ago to build an NSTextView "manually" in some random (non-blessed) thread and it kept crashing; there could have been any number of reasons for that failure, but I eventually determined that as soon as I'd created an NSLayoutManager the blessed (if you'll pardon the expression) thread decided that that'd be a good time to do background glyph layout. (It wasn't ...). I couldn't find a reliable way of preventing that from happening; in Cocoa's implicit view of the world, an NSTextView could only be constructed in the blessed event thread by some event handler (why on earth would a compute-bound thread want to create an NSTextView or an NSLayoutManager ?) IIRC, I eventually wound up PROCESS-INTERRUPTing the event thread and asked it to make the NSTextView for me; that seemed to work fine.
I'm not sure that I'm not just violently agreeing with points that other people have made; if people are thinking about bouncing events around between multiple lisp threads, I hope that they're aware of these issues and have thought of things that I'm missing. The conclusion I came to is that Cocoa is really designed to have just about all of the interesting UI stuff happen in its blessed thread, and if any other approach is possible it may go so far against the grain as to be impractical.
This all agrees with my understanding, except that you know more about the details than I do. My embryonic McCLIM back-end builds a normal OSX application bundle and starts a blessed event loop in the normal way; my theory is that the event loop can hand events to windows and views in the normal-for-Cocoa way and the event handlers on the windows and views can trigger McCLIM functions, marshalling data as needed.
Duncan says he has some things sort of working by grabbing events himself, but it's possible he's living in the sort of twilight world that you describe above; I'm sure he'll weigh in on that.
He and I have been discussing a merge; if debugging event-handling is holding him up, and it turns out to be the same sort of problem you described, then it's possible that in the forthcoming merge he and I can fix things by switching to the Cocoa-event-loop-centric model.
--me
On Thursday, February 5, 2004, at 10:00 PM, mikel evins wrote:
On Feb 5, 2004, at 1:47 PM, Gary Byers wrote:
I just subscribed to this list a little while ago and wanted to respond to a few things that I saw in the archives. I probably won't be able to attribute things too accurately, and I'm sure that I've missed parts of the discussion and may be replying to some things out of context.
To the best of my knowledge, it's effectively the case that only one thread can fetch events from/communcate directly with the window server. Prior to Panther, this was the thread whose CFRunloop was the "main" CFRunloop (there's still some code in OpenMCL's Cocoa demo that tries to play around with this, and it used to be the case that something called an undocumented function - something like "_CFRunLoopSetMain" - to try to get around this. CF's "main" runloop ordinarily got created when CF was initialized, and it was associated with the thread that did that initialization.)
Panther put an end to all this: the thread that's allowed to talk to the Window Server is the application's initial thread (the one that started out by calling main(), way back when) and none other. (Any other thread can certainly try to call NSApplication's nextEventMatchingMaskWhateverTheOtherArgsAre, but it'll get a NULL pointer back.)
The basic model is that NSApplication's -run method loops around, getting events from the window server and propagating them through the responder chain. From what I understand, lots of other things go on between the time that the -run method asks for an event and the time that it's delivered: raw window server events arrive; some of them are passed to Carbon event handlers (which might in some cases call Cocoa methods), some are related to keeping the window server's model of what's on screen in synch with the application's (drawRect: methods get called at this time), etc. All of this is (by default) happening on the distinguished, blessed event thread.
Some of the time of course, an NSEvent will eventually get returned to the caller. Tim Moore and I tried some experiments about a year ago to see if we could intercept window-specific events and dispatch them to handlers that ran in window-specific threads (the thinking was that this was a lot closer to McCLIM's CLX backend's behavior.) The idea sort of worked (at least halfway). One way in which I remember it failing was when clicking on a window close button: the (blessed) event thread was waiting for the window-specific thread to unlock the view focus so it could ... do whatever it does, and the window-specific thread was waiting for a mouse-up event that never came. It might have been possible to think a little harder and avoid this particular deadlock situation, but I got the strong impression that there were lots more deadlock situations waiting to happen.
Apple had a Tech Note or similar document whose title was something like "AppKit is too thread-safe! Stop saying that it isn't !". That document enumerated a small number of reasonable-sounding guidelines; I don't think that it's too hard to follow them carefully and still see totally unreasonable behavior. I was trying a few months ago to build an NSTextView "manually" in some random (non-blessed) thread and it kept crashing; there could have been any number of reasons for that failure, but I eventually determined that as soon as I'd created an NSLayoutManager the blessed (if you'll pardon the expression) thread decided that that'd be a good time to do background glyph layout. (It wasn't ...). I couldn't find a reliable way of preventing that from happening; in Cocoa's implicit view of the world, an NSTextView could only be constructed in the blessed event thread by some event handler (why on earth would a compute-bound thread want to create an NSTextView or an NSLayoutManager ?) IIRC, I eventually wound up PROCESS-INTERRUPTing the event thread and asked it to make the NSTextView for me; that seemed to work fine.
I'm not sure that I'm not just violently agreeing with points that other people have made; if people are thinking about bouncing events around between multiple lisp threads, I hope that they're aware of these issues and have thought of things that I'm missing. The conclusion I came to is that Cocoa is really designed to have just about all of the interesting UI stuff happen in its blessed thread, and if any other approach is possible it may go so far against the grain as to be impractical.
This all agrees with my understanding, except that you know more about the details than I do. My embryonic McCLIM back-end builds a normal OSX application bundle and starts a blessed event loop in the normal way; my theory is that the event loop can hand events to windows and views in the normal-for-Cocoa way and the event handlers on the windows and views can trigger McCLIM functions, marshalling data as needed.
Duncan says he has some things sort of working by grabbing events himself, but it's possible he's living in the sort of twilight world that you describe above; I'm sure he'll weigh in on that.
Indeed - I went off on a bit of a tangent for a couple of weeks 8-) investigating events and run-loops and all that stuff. The only way I got it to work in the end was to get rid of all that stuff and implement a simple solution. Basically what I do is subclass NSView with Lisp objects using def-objc-class and override the event methods. Each of these event methods looks like:
(ccl::define-objc-method ((:void :mouse-up event) lisp-view) (add-event-to-queue self event))
The add-event-to-queue method converts the Cocoa event into a CLIM event and appends it to a list; the McCLIM event processing pops events off the list when it wants them and does its stuff. This provides a mechanism allowing Cocoa to force events on the back end code, and for CLIM to request them as needed.
It might be easier to implement this in send-event rather than implementing each event method individually, but I'm only at pre-pre-alpha stage currently.
There is definitely mention in the Apple docs that all threads have a run-loop created for them, and that any thread that *starts* the run-loop can process events. So I don't think all is lost for handling events safely in a multi-threaded application. It may be the case that multiple run-loops can only be run if no inputs are shared; I didn't get far enough in my experimentation to say either way.
That said, I still get the occasional Cocoa error, "Unable to unlock topmost reader" but that may be the way the back end is structured at the moment. Because CLIM keeps track of what's been drawn and what needs redrawing, I don't make use of the draw-rect method at all - so every drawing operation currently does a lock-focus and a focus release. This means (for drawing something like a scroll-pane) there are dozens of lock - draw - unlock operations in a very short period of time, which I suspect may cause the problems (it seems to happen mostly when there's lots of debug being output on a separate window). Hopefully this won't be (such) a problem when native panes are implemented (and I suspect there are ways around the problem anyway if necessary - but it's not a priority at the moment).
So I'm not sure at the moment whether I have problems with events or not - there's still quite a way to go however.
He and I have been discussing a merge; if debugging event-handling is holding him up, and it turns out to be the same sort of problem you described, then it's possible that in the forthcoming merge he and I can fix things by switching to the Cocoa-event-loop-centric model.
I'm actually getting very close to feeling ready to do a "release" (i.e. let somebody else see the code 8-). I'm waiting on implementing mouse moved events (hopefully will be done tonight), and getting user-input displayed in the window (key-up / key-down events are already received but seem to have no effect). The user-input display "problem" for me at the moment is I'm not clear on whether CLIM responds to a key event by drawing a glyph, or if I should draw the glyph on my own and then just report the key event to CLIM - so it may be quite straight-forward to resolve. I'll be looking at that tomorrow.
Interestingly (or maybe not) CLIM and Cocoa provide very similar facilities for much of their windowing functionality. Unfortunately its done differently enough to be a bit of a pain.
-Duncan
--me
mac-lisp-ide site list mac-lisp-ide@common-lisp.net http://common-lisp.net/mailman/listinfo/mac-lisp-ide
Indeed - I went off on a bit of a tangent for a couple of weeks 8-) investigating events and run-loops and all that stuff. The only way I got it to work in the end was to get rid of all that stuff and implement a simple solution.
... forgot to say: i.e. I do it the same way you would if you were writing an ObjectiveC Cocoa app that needed to respond to events - and ditched any manual manipulation.
This wasn't clear from what I said - sorry for any people puzzling and then saying "but surely that's just the standard way to do it..." - yes, it would be. I guess 8-)
-Duncan
On Feb 5, 2004, at 2:39 PM, Duncan Rose wrote:
There is definitely mention in the Apple docs that all threads have a run-loop created for them, and that any thread that *starts* the run-loop can process events. So I don't think all is lost for handling events safely in a multi-threaded application. It may be the case that multiple run-loops can only be run if no inputs are shared; I didn't get far enough in my experimentation to say either way.
There is a problem that the Window Server enqueues 'user' events for the main thread and no other. Other threads can create run loops and dequeue events, but under normal circumstances will never see 'user' events on their queues. The same holds true in Carbon as well.
On Feb 5, 2004, at 2:39 PM, Duncan Rose wrote:
The add-event-to-queue method converts the Cocoa event into a CLIM event and appends it to a list; the McCLIM event processing pops events off the list when it wants them and does its stuff. This provides a mechanism allowing Cocoa to force events on the back end code, and for CLIM to request them as needed.
Seems like it should be possible to make a thread for CLIM that waits on this queue, so that each time you enqueue a synthesized event the CLIM thread wakes up and dequeues it.
When you hand events to the CLIM code, do you tell Cocoa that the event was handled? Whichever way you answer there may be subtleties that could cause weird behavior; some events are meant to be handled by the first responder and that's it; others are meant to be maybe-handled by the first responder and then maybe handed off to one or more other responders. Finally, Command-key events traverse the responder chain *backwards* so that the application has first chance to process them.
On Thursday, February 5, 2004, at 10:58 PM, mikel evins wrote:
On Feb 5, 2004, at 2:39 PM, Duncan Rose wrote:
The add-event-to-queue method converts the Cocoa event into a CLIM event and appends it to a list; the McCLIM event processing pops events off the list when it wants them and does its stuff. This provides a mechanism allowing Cocoa to force events on the back end code, and for CLIM to request them as needed.
Seems like it should be possible to make a thread for CLIM that waits on this queue, so that each time you enqueue a synthesized event the CLIM thread wakes up and dequeues it.
When you hand events to the CLIM code, do you tell Cocoa that the event was handled? Whichever way you answer there may be subtleties that could cause weird behavior; some events are meant to be handled by the first responder and that's it; others are meant to be maybe-handled by the first responder and then maybe handed off to one or more other responders. Finally, Command-key events traverse the responder chain *backwards* so that the application has first chance to process them.
Currently (disclaimer: pre-pre-alpha 8-) any event that's of a type CLIM would be interested in gets put onto the event list for CLIM. Any other events continue up the responder chain as normal. (This seems to be acceptable for most events. Mouse moved events get sent to the window irrespective of where the mouse is - CLIM probably isn't too interested in these if they don't happen over a CLIM window).
I only handle key events in my lisp-window NSWindow subclass at the moment in any case, so *at the moment* it doesn't make any difference if they go up or down. There's no event handling in the NSApplication.
I can't say I'm particularly happy with the way I'm handling events at the moment; or, to be honest, with the way I'm handling anything else either! (I'm thinking at the moment it may have been a bad idea to attempt to "Cocoaify" the CLX back end, which is what I've done in effect, rather than start from scratch.)
-Duncan
mac-lisp-ide site list mac-lisp-ide@common-lisp.net http://common-lisp.net/mailman/listinfo/mac-lisp-ide
On Thursday, February 5, 2004, at 10:58 PM, mikel evins wrote:
Seems like it should be possible to make a thread for CLIM that waits on this queue, so that each time you enqueue a synthesized event the CLIM thread wakes up and dequeues it.
This is what is happening at the moment; but as yet, there's no waiting (just tight looping, ouch).
-Duncan
On Feb 5, 2004, at 11:39 PM, Duncan Rose wrote:
Basically what I do is subclass NSView with Lisp objects using def-objc-class and override the event methods. Each of these event methods looks like:
(ccl::define-objc-method ((:void :mouse-up event) lisp-view) (add-event-to-queue self event))
The add-event-to-queue method converts the Cocoa event into a CLIM event and appends it to a list; the McCLIM event processing pops events off the list when it wants them and does its stuff. This provides a mechanism allowing Cocoa to force events on the back end code, and for CLIM to request them as needed.
That's all fine. What I've been worried about all along is "events" going the other way. For example, the layout of the CLIM applications changes, so NSViews need to be deleted (perhaps) and resized.
It might be easier to implement this in send-event rather than implementing each event method individually, but I'm only at pre-pre-alpha stage currently.
There is definitely mention in the Apple docs that all threads have a run-loop created for them, and that any thread that *starts* the run-loop can process events. So I don't think all is lost for handling events safely in a multi-threaded application. It may be the case that multiple run-loops can only be run if no inputs are shared; I didn't get far enough in my experimentation to say either way.
Do you want/need to use a Cocoa specific mechanism for passing messages between threads instead of the CLIM event queues?
That said, I still get the occasional Cocoa error, "Unable to unlock topmost reader" but that may be the way the back end is structured at the moment. Because CLIM keeps track of what's been drawn and what needs redrawing, I don't make use of the draw-rect method at all - so every drawing operation currently does a lock-focus and a focus release. This means (for drawing something like a scroll-pane) there are dozens of lock - draw - unlock operations in a very short period of time, which I suspect may cause the problems (it seems to happen mostly when there's lots of debug being output on a separate window). Hopefully this won't be (such) a problem when native panes are implemented (and I suspect there are ways around the problem anyway if necessary - but it's not a priority at the moment).
You might think about keeping a pane's medium (view) locked from the first drawing operation up to the call to force-output in the code that waits for events.
He and I have been discussing a merge; if debugging event-handling is holding him up, and it turns out to be the same sort of problem you described, then it's possible that in the forthcoming merge he and I can fix things by switching to the Cocoa-event-loop-centric model.
I'm actually getting very close to feeling ready to do a "release" (i.e. let somebody else see the code 8-). I'm waiting on implementing mouse moved events (hopefully will be done tonight), and getting user-input displayed in the window (key-up / key-down events are already received but seem to have no effect). The user-input display "problem" for me at the moment is I'm not clear on whether CLIM responds to a key event by drawing a glyph, or if I should draw the glyph on my own and then just report the key event to CLIM - so it may be quite straight-forward to resolve. I'll be looking at that tomorrow.
I'm not sure how much of CLIM you're using at this point, but CLIM does expect to draw the glyphs itself after a key event on a stream.
Interestingly (or maybe not) CLIM and Cocoa provide very similar facilities for much of their windowing functionality. Unfortunately its done differently enough to be a bit of a pain.
Sigh, yeah... Tim
On Thursday, February 5, 2004, at 11:12 PM, Timothy Moore wrote:
On Feb 5, 2004, at 11:39 PM, Duncan Rose wrote:
Basically what I do is subclass NSView with Lisp objects using def-objc-class and override the event methods. Each of these event methods looks like:
(ccl::define-objc-method ((:void :mouse-up event) lisp-view) (add-event-to-queue self event))
The add-event-to-queue method converts the Cocoa event into a CLIM event and appends it to a list; the McCLIM event processing pops events off the list when it wants them and does its stuff. This provides a mechanism allowing Cocoa to force events on the back end code, and for CLIM to request them as needed.
That's all fine. What I've been worried about all along is "events" going the other way. For example, the layout of the CLIM applications changes, so NSViews need to be deleted (perhaps) and resized.
I'm hoping this will be fine, as long as CLIM redraws the sheets it resizes.
It might be easier to implement this in send-event rather than implementing each event method individually, but I'm only at pre-pre-alpha stage currently.
There is definitely mention in the Apple docs that all threads have a run-loop created for them, and that any thread that *starts* the run-loop can process events. So I don't think all is lost for handling events safely in a multi-threaded application. It may be the case that multiple run-loops can only be run if no inputs are shared; I didn't get far enough in my experimentation to say either way.
Do you want/need to use a Cocoa specific mechanism for passing messages between threads instead of the CLIM event queues?
I don't think that will be necessary. Events appear to be working ok at the moment (although perhaps not very efficiently...)
That said, I still get the occasional Cocoa error, "Unable to unlock topmost reader" but that may be the way the back end is structured at the moment. Because CLIM keeps track of what's been drawn and what needs redrawing, I don't make use of the draw-rect method at all - so every drawing operation currently does a lock-focus and a focus release. This means (for drawing something like a scroll-pane) there are dozens of lock - draw - unlock operations in a very short period of time, which I suspect may cause the problems (it seems to happen mostly when there's lots of debug being output on a separate window). Hopefully this won't be (such) a problem when native panes are implemented (and I suspect there are ways around the problem anyway if necessary - but it's not a priority at the moment).
You might think about keeping a pane's medium (view) locked from the first drawing operation up to the call to force-output in the code that waits for events.
I was trying to minimise the length of time I had a view locked for. Again, I'm not sure this is necessary but it seemed like a good idea at the time (I think locking a view will block the window server until the lock is released; it's possible it just locks the thread responsible for the locked view (maybe even likely) but I haven't experimented with this yet).
I was thinking of maybe hunting out the drawing-intensive methods (which I think are in the pane realization code somewhere - I haven't looked yet) and creating :around methods which locked the focus, call next method, then unlocked focus. But again, I'm not far enough down the path to have tried any of this yet.
He and I have been discussing a merge; if debugging event-handling is holding him up, and it turns out to be the same sort of problem you described, then it's possible that in the forthcoming merge he and I can fix things by switching to the Cocoa-event-loop-centric model.
I'm actually getting very close to feeling ready to do a "release" (i.e. let somebody else see the code 8-). I'm waiting on implementing mouse moved events (hopefully will be done tonight), and getting user-input displayed in the window (key-up / key-down events are already received but seem to have no effect). The user-input display "problem" for me at the moment is I'm not clear on whether CLIM responds to a key event by drawing a glyph, or if I should draw the glyph on my own and then just report the key event to CLIM - so it may be quite straight-forward to resolve. I'll be looking at that tomorrow.
I'm not sure how much of CLIM you're using at this point, but CLIM does expect to draw the glyphs itself after a key event on a stream.
Pretty much all of it. I think the problem may well be to do with my (CLIM) events in that case. The back end isn't being asked to draw glyphs after key down / up events are processed. Time to put some debug into the McCLIM "core" code and see what's happening to them...
Interestingly (or maybe not) CLIM and Cocoa provide very similar facilities for much of their windowing functionality. Unfortunately its done differently enough to be a bit of a pain.
Sigh, yeah... Tim
mac-lisp-ide site list mac-lisp-ide@common-lisp.net http://common-lisp.net/mailman/listinfo/mac-lisp-ide