deane: (Default)
[personal profile] deane
I ran into an interesting bug on OS X this week. I had an object which was trying to calculate a value that had been requested of it. The calculation can take a long time so every now and then the object would check to see if the user had pressed the escape key to abort the calculation. But the very first time it tried to make the check, it would crash.

Using a combination of the debugger and print statements I was able to determine that the problem was that when it tried to check for the escape key, window redraw events were running and at least one of those was calling on the object to calculate the same value that it was already in the process of calculating. Since the object was non-re-entrant (i.e. it had to finish doing one thing before it could be asked to do another), that was resulting in the crash.

The thing is, the check for the escape key shouldn't be allowing any other events to run. Most or our code is written in C++, but this bit was in Cocoa/Objective-C and looked like this:

NSUInteger mask = NSKeyDownMask|NSKeyUpMask;

NSEvent *event = [NSApp nextEventMatchingMask:mask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES];

The first line defines a mask of the types of events we want to consider: just key presses and key releases. The second line, which is probably broken into two in your browser, asks the application's event loop (known in OS X jargon as a 'run loop') to return the first event matching the mask which is currently queued up for processing, if there is one.

Given that the mask setting should only allow key presses and releases to be processed, how the heck were window redraw events getting through?

It turns out that the answer lies in that 'inMode:NSDefaultRunLoopMode' bit. A "run loop mode" determines which input sources the loop will check for events (keyboard, mouse, etc) and, critically, which observers it should talk to while processing events. Observers are other bits of code which have asked to be alerted at various stages during the processing of events.

You can define your own run loop modes, but OS X has four pre-defined ones:
  • NSDefaultRunLoopMode
  • NSEventTrackingRunLoopMode
  • NSModalPanelRunLoopMode
  • NSConnectionReplyMode
Without going into the details of each one, it's sufficient for the purposes of this post to just note that the first three are so frequently used that they are grouped together as "common modes". Observers can subscribe to all three just by subscribing to 'NSRunLoopCommonModes'.

We use Nokia's Qt toolkit to implement our application's GUI. It turns out that Qt sets up an observer on the common run loop modes. That observer apparently does not (or cannot) check that we are running the loop with a mask which only allows for keystroke events. So it goes ahead and issues its own internally generated window redraw events, even when we are only asking for keystroke events. Thus the crash.

The fourth run loop mode, NSConnectionReplyMode, is intended for waiting for network connection events, but we don't really care about that. The important things are that it is not one of the common run loop modes and thus does not have a Qt observer sitting on it, and it accepts the keyboard as a valid input source. By using it, we can stop Qt from issuing unwanted GUI events while we check for key presses:

NSEvent *event = [NSApp nextEventMatchingMask:mask untilDate:nil inMode:NSConnectionReplyMode dequeue:YES];

It would be even better if we created our own run loop mode which had no observers at all attached to it, and whose only valid input source was the keyboard. Unfortunately, while I've be able to figure out how to create my own run loop, I haven't been able to figure out how to get hold of the 'port' for the keyboard so that I can add it to the run loop. So we'll have to make do with NSConnectionReplyMode for now.

Profile

deane: (Default)
deane

April 2014

S M T W T F S
  12345
6789101112
13141516171819
20212223242526
27282930   
Progressive Bloggers

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags