|
25 | 25 | @test PythonCall.C.PyThreadState_GetUnchecked() == C_NULL |
26 | 26 |
|
27 | 27 | # Nested regions reuse the state cached by the owning Julia thread. A break |
28 | | - # must relinquish that cache while yielding and restore it on return. |
| 28 | + # must relinquish that cache while yielding, keep the task on the same Julia |
| 29 | + # thread, and restore both the cache and the task's original stickiness. |
| 30 | + test_task = current_task() |
| 31 | + oldsticky = test_task.sticky |
29 | 32 | @pyregion begin |
30 | 33 | local state = PythonCall.C.TASK_STATE() |
31 | 34 | local thread_state = PythonCall.C.THREAD_STATE() |
32 | | - @test thread_state.task === current_task() |
| 35 | + local tid = threadid() |
| 36 | + @test test_task.sticky |
| 37 | + @test thread_state.task === test_task |
33 | 38 | @test thread_state.task_state === state |
34 | | - @pyregion @test PythonCall.C.current_task_state(current_task()) === state |
| 39 | + @pyregion @test PythonCall.C.current_task_state(test_task) === state |
35 | 40 | @pyregionbreak begin |
| 41 | + @test test_task.sticky |
36 | 42 | @test thread_state.task === nothing |
37 | 43 | yield() |
| 44 | + @test threadid() == tid |
38 | 45 | end |
39 | | - @test thread_state.task === current_task() |
| 46 | + @test thread_state.task === test_task |
40 | 47 | @test thread_state.task_state === state |
41 | 48 | end |
| 49 | + @test test_task.sticky == oldsticky |
42 | 50 | @test PythonCall.C.THREAD_STATE().task === nothing |
43 | 51 |
|
44 | 52 | @test @pyregion begin |
|
51 | 59 | pyconvert(Int, pyint(3)) == 3 |
52 | 60 | end |
53 | 61 |
|
| 62 | + # A task waiting in ordinary Julia code must release its Python state so that |
| 63 | + # another task on the same Julia thread can use Python. The bounded wait makes |
| 64 | + # a missing break fail promptly instead of leaving the test suite deadlocked. |
| 65 | + started = Channel{Nothing}(1) |
| 66 | + ready = Ref(false) |
| 67 | + break_status = Ref(:not_started) |
| 68 | + values = Ref((0, 0)) |
| 69 | + @sync begin |
| 70 | + @async @pyregion begin |
| 71 | + put!(started, nothing) |
| 72 | + first = pyconvert(Int, pybuiltins.sum([1, 2, 3])) |
| 73 | + break_status[] = @pyregionbreak timedwait(() -> ready[], 2; pollint=0.001) |
| 74 | + second = pyconvert(Int, pybuiltins.sum([4, 5, 6])) |
| 75 | + values[] = (first, second) |
| 76 | + end |
| 77 | + @async begin |
| 78 | + take!(started) |
| 79 | + @test pyconvert(Int, pybuiltins.sum([7, 8, 9])) == 24 |
| 80 | + ready[] = true |
| 81 | + end |
| 82 | + end |
| 83 | + @test break_status[] == :ok |
| 84 | + @test values[] == (6, 15) |
| 85 | + |
54 | 86 | @test_throws ErrorException @pyregion @pyregionbreak error("region exception") |
55 | 87 | @test PythonCall.C.PyThreadState_GetUnchecked() == C_NULL |
56 | 88 | @test_throws PyException @pyregion pybuiltins.int("not an integer") |
|
0 commit comments