Search code examples
pythonmacoswxpython

TextCtrl providing an out of bound exception in wxPython


I am new to WX, so I decided to make a program that will periodically write out a line of text to the screen based on an outside input. The basis of the program contains a basic window with the multiline text control covering the entire window. The only other method I have in the frame is to print out what ever the method listen_event gets as a new line for the multiline TextCtrl. Thats it, as proof the code is below:

class Frame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None,
            title = 'Program',
            size = (640, 480),
            style = wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.MINIMIZE_BOX)
        panel = wx.Panel(self)
        self.textArea = wx.TextCtrl(parent = panel,
            id = -1,
            pos = (0, 0),
            size = (-1, -1),
            style = wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_AUTO_URL)

    def listen_event(self, data):
        self.textArea.AppendText(data)

The listen event is is called periodically from another thread. All seems to be fine, the program works, however, every so often (more often than I would like) I get a massive dump of what reminds me of ObjectiveC error messages:

2013-06-21 20:11:47.820 Python[85638:420b] An uncaught exception was raised
2013-06-21 20:11:47.821 Python[85638:420b] NSMutableRLEArray replaceObjectsInRange:withObject:length:: Out of bounds
2013-06-21 20:11:47.824 Python[85638:420b] (
    0   CoreFoundation                      0x00007fff92e2bf56 __exceptionPreprocess + 198
    1   libobjc.A.dylib                     0x00007fff8fc0bd5e objc_exception_throw + 43
    2   CoreFoundation                      0x00007fff92e2bd8a +[NSException raise:format:arguments:] + 106
    3   CoreFoundation                      0x00007fff92e2bd14 +[NSException raise:format:] + 116
    4   Foundation                          0x00007fff8fe93b20 -[NSMutableRLEArray replaceObjectsInRange:withObject:length:] + 132
    5   AppKit                              0x00007fff8d3e33f8 -[NSLayoutManager addTemporaryAttribute:value:forCharacterRange:] + 500
    6   AppKit                              0x00007fff8d7f9716 -[NSTextView _markTextEditedForRange:] + 1025
    7   AppKit                              0x00007fff8d7f8392 -[NSTextView insertText:replacementRange:] + 2400
    8   AppKit                              0x00007fff8d7f7a25 -[NSTextView insertText:] + 320
    9   libwx_osx_cocoau-2.9.4.0.0.dylib    0x0000000101953b71 _ZN19wxNSTextViewControl9WriteTextERK8wxString + 257
    10  libwx_osx_cocoau-2.9.4.0.0.dylib    0x00000001018bfb53 _ZN11wxTextEntry9WriteTextERK8wxString + 67
    11  _core_.so                           0x00000001014dec57 _wrap_TextEntryBase_AppendText + 199
    12  Python                              0x00000001000c1112 PyEval_EvalFrameEx + 22626
    13  Python                              0x00000001000c2d29 PyEval_EvalCodeEx + 2137
    14  Python                              0x00000001000c0b6a PyEval_EvalFrameEx + 21178
    15  Python                              0x00000001000c1ebe PyEval_EvalFrameEx + 26126
    16  Python                              0x00000001000c1ebe PyEval_EvalFrameEx + 26126
    17  Python                              0x00000001000c1ebe PyEval_EvalFrameEx + 26126
    18  Python                              0x00000001000c1ebe PyEval_EvalFrameEx + 26126
    19  Python                              0x00000001000c2d29 PyEval_EvalCodeEx + 2137
    20  Python                              0x00000001000c0b6a PyEval_EvalFrameEx + 21178
    21  Python                              0x00000001000c1ebe PyEval_EvalFrameEx + 26126
    22  Python                              0x00000001000c2d29 PyEval_EvalCodeEx + 2137
    23  Python                              0x000000010003da80 function_call + 176
    24  Python                              0x000000010000c5e2 PyObject_Call + 98
    25  Python                              0x000000010001ebcb instancemethod_call + 363
    26  Python                              0x000000010000c5e2 PyObject_Call + 98
    27  Python                              0x00000001000ba5f7 PyEval_CallObjectWithKeywords + 87
    28  Python                              0x0000000100100a63 t_bootstrap + 67
    29  libsystem_c.dylib                   0x00007fff933008bf _pthread_start + 335
    30  libsystem_c.dylib                   0x00007fff93303b75 thread_start + 13
)
2013-06-21 20:11:47.825 Python[85638:420b] *** Terminating app due to uncaught exception 'NSRangeException', reason: 'NSMutableRLEArray replaceObjectsInRange:withObject:length:: Out of bounds'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff92e2bf56 __exceptionPreprocess + 198
    1   libobjc.A.dylib                     0x00007fff8fc0bd5e objc_exception_throw + 43
    2   CoreFoundation                      0x00007fff92e2bd8a +[NSException raise:format:arguments:] + 106
    3   CoreFoundation                      0x00007fff92e2bd14 +[NSException raise:format:] + 116
    4   Foundation                          0x00007fff8fe93b20 -[NSMutableRLEArray replaceObjectsInRange:withObject:length:] + 132
    5   AppKit                              0x00007fff8d3e33f8 -[NSLayoutManager addTemporaryAttribute:value:forCharacterRange:] + 500
    6   AppKit                              0x00007fff8d7f9716 -[NSTextView _markTextEditedForRange:] + 1025
    7   AppKit                              0x00007fff8d7f8392 -[NSTextView insertText:replacementRange:] + 2400
    8   AppKit                              0x00007fff8d7f7a25 -[NSTextView insertText:] + 320
    9   libwx_osx_cocoau-2.9.4.0.0.dylib    0x0000000101953b71 _ZN19wxNSTextViewControl9WriteTextERK8wxString + 257
    10  libwx_osx_cocoau-2.9.4.0.0.dylib    0x00000001018bfb53 _ZN11wxTextEntry9WriteTextERK8wxString + 67
    11  _core_.so                           0x00000001014dec57 _wrap_TextEntryBase_AppendText + 199
    12  Python                              0x00000001000c1112 PyEval_EvalFrameEx + 22626
    13  Python                              0x00000001000c2d29 PyEval_EvalCodeEx + 2137
    14  Python                              0x00000001000c0b6a PyEval_EvalFrameEx + 21178
    15  Python                              0x00000001000c1ebe PyEval_EvalFrameEx + 26126
    16  Python                              0x00000001000c1ebe PyEval_EvalFrameEx + 26126
    17  Python                              0x00000001000c1ebe PyEval_EvalFrameEx + 26126
    18  Python                              0x00000001000c1ebe PyEval_EvalFrameEx + 26126
    19  Python                              0x00000001000c2d29 PyEval_EvalCodeEx + 2137
    20  Python                              0x00000001000c0b6a PyEval_EvalFrameEx + 21178
    21  Python                              0x00000001000c1ebe PyEval_EvalFrameEx + 26126
    22  Python                              0x00000001000c2d29 PyEval_EvalCodeEx + 2137
    23  Python                              0x000000010003da80 function_call + 176
    24  Python                              0x000000010000c5e2 PyObject_Call + 98
    25  Python                              0x000000010001ebcb instancemethod_call + 363
    26  Python                              0x000000010000c5e2 PyObject_Call + 98
    27  Python                              0x00000001000ba5f7 PyEval_CallObjectWithKeywords + 87
    28  Python                              0x0000000100100a63 t_bootstrap + 67
    29  libsystem_c.dylib                   0x00007fff933008bf _pthread_start + 335
    30  libsystem_c.dylib                   0x00007fff93303b75 thread_start + 13
)
terminate called throwing an exception

What gives? I cannot find a rhyme or reason for these errors. On occasion it works for a couple in a row, then crashes. And sometimes it crashes on the first go. Am I implementing the text control box incorrectly?


Solution

  • This may or may not be the reason for your crash, but if definitely will cause crashes, and they're likely to be exactly the kind of crash you're seeing (sometimes it works, sometimes it fails, sometimes it works for a while and then suddenly fails even though nothing visible has changed…).

    You cannot call methods that operate on UI objects from other threads. Every time you call self.textArea.AppendText from another thread, there is a chance of crashing—or, more fun, corrupting memory or other resources leading to a crash later.

    There are a few different ways around this:

    • Use PostEvent to queue an event for the main UI thread (with the actual UI-changing code in the event handler).
    • Use CallAfter or CallLater to schedule a function to be called on the main UI thread (with the actual UI-changing code inside that function).
    • Use the pubsub framework.
    • Use some external-to-wx mechanism (e.g., from the threading module, or a pipe, or whatever) to signal the main UI thread.
    • More than one of the above.

    (Under the covers, CallAfter is a wrapper around PostEvent, and CallLater is a wrapper around a timer plus CallAfter, and the timer and threading are both wrappers around the same native thread APIs, and so on, so this isn't as many different possibilities as it seems…)


    Anyway, the most trivial change to your code is:

    def listen_event(self, data):
        wx.CallAfter(self.textArea.AppendText, data)
    

    On top of that, you're violating the first rule of multithreaded programming: shared mutable objects must only be accessed under locks.

    I don't think this is causing your problem here. Since self will never change attributes after creation, and (assuming you're using CPython, or some other Python implementation with a GIL) there's nothing else that can happen non-atomically that can affect you, you'll get away with it here. But if you don't understand why your code (other than the wx calls) is thread-safe, you shouldn't count on it.