@@ -29,6 +29,79 @@ function _atpyexit()
2929 return
3030end
3131
32+
33+ function setup_onfixedthread ()
34+ channel_input = Channel (1 )
35+ channel_output = Channel (1 )
36+ islaunched = Ref (false ) # use Ref to avoid closure boxing of variable
37+ function launch_worker (tid)
38+ islaunched[] && error (" Cannot launch more than once: call setup_onfixedthread again if need be." )
39+ islaunched[] = true
40+ worker_task = Task () do
41+ while true
42+ f = take! (channel_input)
43+ ret = try
44+ Some (invokelatest (f))
45+ # invokelatest is necessary for development and interactive use.
46+ # Otherwise, only a method f defined in a world prior to the call of
47+ # launch_worker would work.
48+ catch e
49+ e, catch_backtrace ()
50+ end
51+ put! (channel_output, ret)
52+ end
53+ end
54+ # code adapted from set_task_tid! in StableTasks.jl, itself taken from Dagger.jl
55+ worker_task. sticky = true
56+ for _ in 1 : 100
57+ # try to fix the task id to tid, retrying up to 100 times
58+ ret = ccall (:jl_set_task_tid , Cint, (Any, Cint), worker_task, tid- 1 )
59+ if ret == 1
60+ break # success
61+ elseif ret == 0
62+ yield ()
63+ else
64+ error (" Unexpected retcode from jl_set_task_tid: $ret " )
65+ end
66+ end
67+ if Threads. threadid (worker_task) != tid
68+ error (" Failed setting the thread ID to $tid ." )
69+ end
70+ schedule (worker_task)
71+ end
72+ function onfixedthread (f)
73+ put! (channel_input, f)
74+ ret = take! (channel_output)
75+ if ret isa Tuple
76+ e, backtrace = ret
77+ printstyled (stderr , " ERROR: " ; color= :red , bold= true )
78+ showerror (stderr , e)
79+ Base. show_backtrace (stderr , backtrace)
80+ println (stderr )
81+ throw (e) # the stacktrace of the actual error is printed above
82+ else
83+ something (ret)
84+ end
85+ end
86+ launch_worker, onfixedthread
87+ end
88+
89+ # launch_on_main_thread is used in init_context(), after which on_main_thread becomes usable
90+ const launch_on_main_thread, on_main_thread = setup_onfixedthread ()
91+
92+ """
93+ on_main_thread(f)
94+
95+ Execute `f()` on the main thread.
96+
97+ !!! warning
98+ The value returned by `on_main_thread(f)` cannot be type-inferred by the compiler:
99+ if necessary, use explicit type annotations such as `on_main_thread(f)::T`, where `T` is
100+ the expected return type.
101+ """
102+ on_main_thread
103+
104+
32105function init_context ()
33106
34107 CTX. is_embedded = hasproperty (Base. Main, :__PythonCall_libptr )
@@ -240,6 +313,8 @@ function init_context()
240313 " Only Python 3.10+ is supported, this is Python $(CTX. version) at $(CTX. exe_path=== missing ? " unknown location" : CTX. exe_path) ." ,
241314 )
242315
316+ launch_on_main_thread (Threads. threadid ()) # makes on_main_thread usable
317+
243318 @debug " Initialized PythonCall.jl" CTX. is_embedded CTX. is_initialized CTX. exe_path CTX. lib_path CTX. lib_ptr CTX. pyprogname CTX. pyhome CTX. version
244319
245320 return
0 commit comments