1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
"""
Global Interpreter Lock.
"""
# This module adds a global lock to an object space.
# If multiple threads try to execute simultaneously in this space,
# all but one will be blocked. The other threads get a chance to run
# from time to time, using the hook yield_thread().
from pypy.module.thread import ll_thread as thread
from pypy.module.thread.error import wrap_thread_error
from pypy.interpreter.executioncontext import PeriodicAsyncAction
from pypy.module.thread.threadlocals import OSThreadLocals
from pypy.rlib.objectmodel import invoke_around_extcall
from pypy.rlib.rposix import get_errno, set_errno
class GILThreadLocals(OSThreadLocals):
"""A version of OSThreadLocals that enforces a GIL."""
ll_GIL = thread.null_ll_lock
def initialize(self, space):
# add the GIL-releasing callback as an action on the space
space.actionflag.register_periodic_action(GILReleaseAction(space),
use_bytecode_counter=True)
def setup_threads(self, space):
"""Enable threads in the object space, if they haven't already been."""
if not self.ll_GIL:
try:
self.ll_GIL = thread.allocate_ll_lock()
except thread.error:
raise wrap_thread_error(space, "can't allocate GIL")
thread.acquire_NOAUTO(self.ll_GIL, True)
self.enter_thread(space) # setup the main thread
result = True
else:
result = False # already set up
# add the GIL-releasing callback around external function calls.
#
# XXX we assume a single space, but this is not quite true during
# testing; for example, if you run the whole of test_lock you get
# a deadlock caused by the first test's space being reused by
# test_lock_again after the global state was cleared by
# test_compile_lock. As a workaround, we repatch these global
# fields systematically.
spacestate.ll_GIL = self.ll_GIL
invoke_around_extcall(before_external_call, after_external_call)
return result
def yield_thread(self):
thread.yield_thread() # explicitly release the gil (used by test_gil)
class GILReleaseAction(PeriodicAsyncAction):
"""An action called every sys.checkinterval bytecodes. It releases
the GIL to give some other thread a chance to run.
"""
def perform(self, executioncontext, frame):
# Other threads can run between the release() and the acquire()
# implicit in the following external function call (which has
# otherwise no effect).
thread.yield_thread()
class SpaceState:
def _freeze_(self):
self.ll_GIL = thread.null_ll_lock
self.action_after_thread_switch = None
# ^^^ set by AsyncAction.fire_after_thread_switch()
return False
def after_thread_switch(self):
# this is support logic for the signal module, to help it deliver
# signals to the main thread.
action = self.action_after_thread_switch
if action is not None:
self.action_after_thread_switch = None
action.fire()
spacestate = SpaceState()
spacestate._freeze_()
# Fragile code below. We have to preserve the C-level errno manually...
def before_external_call():
# this function must not raise, in such a way that the exception
# transformer knows that it cannot raise!
e = get_errno()
thread.release_NOAUTO(spacestate.ll_GIL)
set_errno(e)
before_external_call._gctransformer_hint_cannot_collect_ = True
def after_external_call():
e = get_errno()
thread.acquire_NOAUTO(spacestate.ll_GIL, True)
thread.gc_thread_run()
spacestate.after_thread_switch()
set_errno(e)
after_external_call._gctransformer_hint_cannot_collect_ = True
# The _gctransformer_hint_cannot_collect_ hack is needed for
# translations in which the *_external_call() functions are not inlined.
# They tell the gctransformer not to save and restore the local GC
# pointers in the shadow stack. This is necessary because the GIL is
# not held after the call to before_external_call() or before the call
# to after_external_call().
|