From: Jonathan P. <jp...@dc...> - 2005-04-14 16:20:09
|
On 14 Apr 2005, at 16:13, kimura wataru wrote: > RubyCocoa apps with ruby threads normarlly work fine, like > SimpleApp.app > in examples. But in this case, calling NSApplication#endSheet fails. > > I found NSApplication#endSheet succeeded when the method was invoked > with > new NSThread. I guess a sheet expects to receive "endSheet:" from other > run-loop. It's not the secondary run loop that's the problem, but rather unfortunate interactions between Ruby's thread scheduling and Cocoa's exception handling. Starting a new NSThread certainly solves the problem (since it uses a separate exception stack). I'll try to explain: Cocoa's exception handlers (NS_DURING etc) work by maintaining a per-pthread list of exception handlers. Therefore, when native code does: NS_DURING // some code NS_HANDLER // handler code NS_ENDHANDLER what actually happens is if (setjmp()) { // add jmp_buf to exception handler list for the current pthread // some code } else { // handler code } remove jmp_buf from exception handler list for the current thread The problem arises under the following circumstances (not related to endSheet at all): The main run loop calls a ruby callback, which spawns a thread. The secondary thread invokes an objective-c method which makes a callback to a ruby object. Ruby ruby callback runs long enough to yield back to the main thread. At this point, the NS_DURING exception handler for the objc method call (from ocm_perform) is on the exception handling stack, and if the main run loop handler returns (popping its exception handler) it'll find the wrong handler at the top of the stack. This causes a warning message, and then the program crashes. The following small program demonstrates this fairly repeatedly (doesn't happen every time though). The reason for this problem is that Ruby thread switching changes the stack, but Cocoa isn't aware of this. ############################################################# require 'osx/cocoa' class ExceptionTest < OSX::NSObject def foo Thread.new do self.performSelector_withObject(:bar,nil) end end def bar Thread.pass end end a = ExceptionTest.alloc.init t = a.performSelector_withObject(:foo,nil) t.join ############################################################# The error output looks like this: 2005-04-14 17:14:35.409 ruby[15510] *** Exception handlers were not properly removed. Some code has jumped or returned out of an NS_DURING...NS_HANDLER region without using the NS_VOIDRETURN or NS_VALUERETURN macros. /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/ objc/oc_wrapper.rb:17: [BUG] Bus Error ruby 1.8.2 (2004-12-25) [powerpc-darwin7.7.0] |