aboutsummaryrefslogtreecommitdiff
blob: 00835a162ceab7168b7ff657c6525e9181f97be4 (plain)
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
"""
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 periodic action GILReleaseAction.

from rpython.rlib import rthread, rgil
from pypy.module.thread.error import wrap_thread_error
from pypy.interpreter.executioncontext import PeriodicAsyncAction
from pypy.module.thread.threadlocals import OSThreadLocals
from rpython.rlib.objectmodel import invoke_around_extcall
from rpython.rlib.rposix import get_errno, set_errno

class GILThreadLocals(OSThreadLocals):
    """A version of OSThreadLocals that enforces a GIL."""
    gil_ready = False
    _immutable_fields_ = ['gil_ready?']

    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 _initialize_gil(self, space):
        rgil.gil_allocate()

    def setup_threads(self, space):
        """Enable threads in the object space, if they haven't already been."""
        if not self.gil_ready:
            self._initialize_gil(space)
            self.gil_ready = True
            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.
        invoke_around_extcall(before_external_call, after_external_call)
        return result

    def reinit_threads(self, space):
        "Called in the child process after a fork()"
        OSThreadLocals.reinit_threads(self, space)
        if self.gil_ready:     # re-initialize the gil if needed
            self._initialize_gil(space)


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):
        do_yield_thread()


after_thread_switch = lambda: None     # hook for signal.py

# 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!
    rgil.gil_release()
before_external_call._gctransformer_hint_cannot_collect_ = True
before_external_call._dont_reach_me_in_del_ = True

def after_external_call():
    e = get_errno()
    rgil.gil_acquire()
    rthread.gc_thread_run()
    after_thread_switch()
    set_errno(e)
after_external_call._gctransformer_hint_cannot_collect_ = True
after_external_call._dont_reach_me_in_del_ = 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().

def do_yield_thread():
    # explicitly release the gil, in a way that tries to give more
    # priority to other threads (as opposed to continuing to run in
    # the same thread).
    if rgil.gil_yield_thread():
        rthread.gc_thread_run()
        after_thread_switch()
do_yield_thread._gctransformer_hint_close_stack_ = True
do_yield_thread._dont_reach_me_in_del_ = True
do_yield_thread._dont_inline_ = True

# do_yield_thread() needs a different hint: _gctransformer_hint_close_stack_.
# The *_external_call() functions are themselves called only from the rffi
# module from a helper function that also has this hint.