Home > Software design >  TkInter crashes when called through gRPC
TkInter crashes when called through gRPC

Time:10-14

Setup

  • macOS Big Sur
  • Python 3.9.5
  • main dependencies
grpcio = "^1.41.0"
grpcio-tools = "^1.41.0"
grpcio-reflection = "^1.41.0"

Problem

I have a simple Python script using TkInter that works under commandline mode, but crashes when the module gets called using gRPC.

Code

The TkInter related code:

import tkinter as tk
import tkinter.filedialog as tkfiledialog

def main():
    root = tk.Tk()
    
    root.withdraw()
    root.after_idle(prompt, root)
    root.mainloop()

def prompt(root):
    return tkfiledialog.askopenfilename(
        parent=root,
        title="Select Files",
        filety[es=[("All Files", "*")],
        defaultextension=("All Files", "*"),
        multiple=True,
    ) 

The gRPC architecture is quite standard that I don't think is worth posting. It has little to do with my client implementation as well, because I can easily reproduce the crash using grpcurl


grpcurl -d '{"dryrun": false, "verbose": false, "startdir": ".", "dironly": false, "filepatterns": "*" }' -plaintext localhost:57728 selectfilesfolders.SelectFilesFolders.SelectFilesFolders

Crash log

2021-10-10 16:06:50.147 Python[2855:90390] WARNING: NSWindow drag regions should only be invalidated on the Main Thread! This will throw an exception in the future. Called from (
    0   AppKit                              0x00007fff22e55ed1 -[NSWindow(NSWindow_Theme) _postWindowNeedsToResetDragMarginsUnlessPostingDisabled]   352
    1   AppKit                              0x00007fff22e40aa2 -[NSWindow _initContent:styleMask:backing:defer:contentView:]   1296
    2   AppKit                              0x00007fff23002c2f -[NSPanel _initContent:styleMask:backing:defer:contentView:]   50
    3   AppKit                              0x00007fff22e4058b -[NSWindow initWithContentRect:styleMask:backing:defer:]   42
    4   AppKit                              0x00007fff23002be4 -[NSPanel initWithContentRect:styleMask:backing:defer:]   64
    5   AppKit                              0x00007fff2384e274 -[NSSavePanel initWithContentRect:styleMask:backing:defer:]   97
    6   AppKit                              0x00007fff2385636d -[NSOpenPanel initWithContentRect:styleMask:backing:defer:]   151
    7   AppKit                              0x00007fff2314f0e5 -[NSPanel init]   75
    8   AppKit                              0x00007fff2384e1f0 -[NSSavePanel init]   197
    9   AppKit                              0x00007fff23522c3a  [NSSavePanel(Instantiation) _crunchyRawUnbonedPanel]   58
    10  libtk8.6.dylib                      0x0000000102b97a11 Tk_GetOpenFileObjCmd   67
    11  libtcl8.6.dylib                     0x00000001029886dc TclNRRunCallbacks   80
    12  _tkinter.cpython-39-darwin.so       0x0000000102961029 Tkapp_Call   585
    13  Python                              0x000000010123245d cfunction_call   125
    14  Python                              0x00000001011f3a3c _PyObject_Call   140
    15  Python                              0x00000001012c7a73 _PyEval_EvalFrameDefault   27027
    16  Python                              0x00000001012cab33 _PyEval_EvalCode   2611
    17  Python                              0x00000001011f3c41 _PyFunction_Vectorcall   289
    18  Python                              0x00000001012c9e3c call_function   732
    19  Python                              0x00000001012c7342 _PyEval_EvalFrameDefault   25186
    20  Python                              0x00000001012cab33 _PyEval_EvalCode   2611
    21  Python                              0x00000001011f3c41 _PyFunction_Vectorcall   289
    22  Python                              0x00000001012c9e3c call_function   732
    23  Python                              0x00000001012c7491 _PyEval_EvalFrameDefault   25521
    24  Python                              0x00000001011f3cb8 function_code_fastcall   104
    25  Python                              0x00000001011f5de9 method_vectorcall   441
    26  Python                              0x00000001012c7a73 _PyEval_EvalFrameDefault   27027
    27  Python                              0x00000001012cab33 _PyEval_EvalCode   2611
    28  Python                              0x00000001011f3c41 _PyFunction_Vectorcall   289
    29  Python                              0x00000001012c7a73 _PyEval_EvalFrameDefault   27027
    30  Python                              0x00000001012cab33 _PyEval_EvalCode   2611
    31  Python                              0x00000001011f3c41 _PyFunction_Vectorcall   289
    32  Python                              0x00000001011f5d42 method_vectorcall   274
    33  _tkinter.cpython-39-darwin.so       0x0000000102963bb1 PythonCmd   209
    34  libtcl8.6.dylib                     0x00000001029886dc TclNRRunCallbacks   80
    35  libtcl8.6.dylib                     0x0000000102a54ed3 AfterProc   83
    36  libtcl8.6.dylib                     0x0000000102a54570 TclServiceIdle   87
    37  libtcl8.6.dylib                     0x0000000102a37d27 Tcl_DoOneEvent   349
    38  libtk8.6.dylib                      0x0000000102b1aa02 MapFrame   40
    39  libtcl8.6.dylib                     0x0000000102a54570 TclServiceIdle   87
    40  libtcl8.6.dylib                     0x0000000102a37d27 Tcl_DoOneEvent   349
    41  _tkinter.cpython-39-darwin.so       0x00000001029633de _tkinter_tkapp_mainloop   382
    42  Python                              0x00000001011fc3df method_vectorcall_FASTCALL   335
    43  Python                              0x00000001012c9e3c call_function   732
    44  Python                              0x00000001012c7342 _PyEval_EvalFrameDefault   25186
    45  Python                              0x00000001012cab33 _PyEval_EvalCode   2611
    46  Python                              0x00000001011f3c41 _PyFunction_Vectorcall   289
    47  Python                              0x00000001011f5cfa method_vectorcall   202
    48  Python                              0x00000001012c9e3c call_function   732
    49  Python                              0x00000001012c7363 _PyEval_EvalFrameDefault   25219
    50  Python                              0x00000001011f3cb8 function_code_fastcall   104
    51  Python                              0x00000001012c9e3c call_function   732
    52  Python                              0x00000001012c7342 _PyEval_EvalFrameDefault   25186
    53  Python                              0x00000001011f3cb8 function_code_fastcall   104
    54  Python                              0x00000001012c7a73 _PyEval_EvalFrameDefault   27027
    55  Python                              0x00000001012cab33 _PyEval_EvalCode   2611
    56  Python                              0x00000001011f3c41 _PyFunction_Vectorcall   289
    57  Python                              0x00000001012c9e3c call_function   732
    58  Python                              0x00000001012c7363 _PyEval_EvalFrameDefault   25219
    59  Python                              0x00000001011f3cb8 function_code_fastcall   104
    60  Python                              0x00000001011f5cfa method_vectorcall   202
    61  Python                              0x00000001012c9e3c call_function   732
    62  Python                              0x00000001012c73fb _PyEval_EvalFrameDefault   25371
    63  Python                              0x00000001012cab33 _PyEval_EvalCode   2611
    64  Python                              0x00000001011f3c41 _PyFunction_Vectorcall   289
    65  Python                              0x00000001012c9e3c call_function   732
    66  Python                              0x00000001012c73fb _PyEval_EvalFrameDefault   25371
    67  Python                              0x00000001011f3cb8 function_code_fastcall   104
    68  Python                              0x00000001012c7a73 _PyEval_EvalFrameDefault   27027
    69  Python                              0x00000001011f3cb8 function_code_fastcall   104
    70  Python                              0x00000001012c9e3c call_function   732
    71  Python                              0x00000001012c7342 _PyEval_EvalFrameDefault   25186
    72  Python                              0x00000001011f3cb8 function_code_fastcall   104
    73  Python                              0x00000001012c7a73 _PyEval_EvalFrameDefault   27027
    74  Python                              0x00000001011f3cb8 function_code_fastcall   104
    75  Python                              0x00000001012c9e3c call_function   732
    76  Python                              0x00000001012c7342 _PyEval_EvalFrameDefault   25186
    77  Python                              0x00000001011f3cb8 function_code_fastcall   104
    78  Python                              0x00000001012c9e3c call_function   732
    79  Python                              0x00000001012c7342 _PyEval_EvalFrameDefault   25186
    80  Python                              0x00000001011f3cb8 function_code_fastcall   104
    81  Python                              0x00000001011f5d42 method_vectorcall   274
    82  Python                              0x000000010136ec56 t_bootstrap   70
    83  Python                              0x0000000101320169 pythread_wrapper   25
    84  libsystem_pthread.dylib             0x00007fff2052c8fc _pthread_start   224
    85  libsystem_pthread.dylib             0x00007fff20528443 thread_start   15
)

Workaround

  • If I replace the TkInter part with ANY code, everything works.
  • Call this with IPC through child process works as well

Question

How should I go about debugging or should this be a sign that TkInter is not proper for my task?

CodePudding user response:

According to this GitHub anwser

It is likely a macOS-specific threading issue

On the other hand, according to tkinter's doc. The library may have special event handling logic and threading restrictions, and gRPC Python uses a thread pool to handle incoming requests with gRPC Core handling the IO.

By digging into that doc, we find

A Python interpreter may have many threads associated with it. In Tcl, multiple threads can be created, but each thread has a separate Tcl interpreter instance associated with it. Threads can also create more than one interpreter instance, though each interpreter instance can be used only by the one thread that created it.

Each Tk object created by tkinter contains a Tcl interpreter. It also keeps track of which thread created that interpreter. Calls to tkinter can be made from any Python thread. Internally, if a call comes from a thread other than the one that created the Tk object, an event is posted to the interpreter’s event queue, and when executed, the result is returned to the calling Python thread.

Although I'm not an expert on platform-specific threading models, I do know that macOS is using a "Grand Central Dispatch" task-queue system that hides away a lot of thread-pool details, which is different from Windows.

Thus, without digging into gRPC's thread-pool model, I can't say it's safe to call Tkinter widgets, who keep track of thread that create them, from the volatile gRPC's I/O threads.

  • Related