Skip to content

Commit d8e54a7

Browse files
committed
Phase 2
Adds the ability to make everything immutable and the `behaviors` module, which contains multi-interpreter lock implementations. Furthermore, module loading, unloading, and subclassing have been modified to be BOC safe. Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
1 parent 591bfc7 commit d8e54a7

29 files changed

Lines changed: 1353 additions & 59 deletions

Doc/c-api/type.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ The following functions and structs are used to create
466466
467467
* :c:member:`~PyTypeObject.tp_dict`
468468
* :c:member:`~PyTypeObject.tp_mro`
469-
* :c:member:`~PyTypeObject.tp_cache`
469+
* :c:member:`~PyTypeObject.tp_lock`
470470
* :c:member:`~PyTypeObject.tp_subclasses`
471471
* :c:member:`~PyTypeObject.tp_weaklist`
472472
* :c:member:`~PyTypeObject.tp_vectorcall`

Doc/c-api/typeobj.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ Quick Reference
133133
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
134134
| <:c:member:`~PyTypeObject.tp_mro`> | :c:type:`PyObject` * | __mro__ | | | ~ | |
135135
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
136-
| [:c:member:`~PyTypeObject.tp_cache`] | :c:type:`PyObject` * | | | | |
136+
| [:c:member:`~PyTypeObject.tp_lock`] | :c:type:`PyObject` * | | | | |
137137
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
138138
| [:c:member:`~PyTypeObject.tp_subclasses`] | void * | __subclasses__ | | | |
139139
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
@@ -2013,9 +2013,9 @@ and :c:data:`PyType_Type` effectively act as defaults.)
20132013
:c:func:`PyType_Ready`.
20142014

20152015

2016-
.. c:member:: PyObject* PyTypeObject.tp_cache
2016+
.. c:member:: PyObject* PyTypeObject.tp_lock
20172017
2018-
Unused. Internal use only.
2018+
Internal use only.
20192019

20202020
**Inheritance:**
20212021

Doc/data/python3.12.abi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19363,7 +19363,7 @@
1936319363
<var-decl name='tp_mro' type-id='type-id-2' visibility='default' filepath='./Include/cpython/object.h' line='217' column='1'/>
1936419364
</data-member>
1936519365
<data-member access='public' layout-offset-in-bits='2816'>
19366-
<var-decl name='tp_cache' type-id='type-id-2' visibility='default' filepath='./Include/cpython/object.h' line='218' column='1'/>
19366+
<var-decl name='tp_lock' type-id='type-id-2' visibility='default' filepath='./Include/cpython/object.h' line='218' column='1'/>
1936719367
</data-member>
1936819368
<data-member access='public' layout-offset-in-bits='2880'>
1936919369
<var-decl name='tp_subclasses' type-id='type-id-22' visibility='default' filepath='./Include/cpython/object.h' line='219' column='1'/>

Doc/includes/typestruct.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ typedef struct _typeobject {
7070
inquiry tp_is_gc; /* For PyObject_IS_GC */
7171
PyObject *tp_bases;
7272
PyObject *tp_mro; /* method resolution order */
73-
PyObject *tp_cache;
73+
PyObject *tp_lock;
7474
PyObject *tp_subclasses;
7575
PyObject *tp_weaklist;
7676
destructor tp_del;

Include/cpython/object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ struct _typeobject {
215215
inquiry tp_is_gc; /* For PyObject_IS_GC */
216216
PyObject *tp_bases;
217217
PyObject *tp_mro; /* method resolution order */
218-
PyObject *tp_cache; /* no longer used */
218+
PyObject *tp_lock; /* used by immutable types */
219219
void *tp_subclasses; /* for static builtin types this is an index */
220220
PyObject *tp_weaklist; /* not used for static builtin types */
221221
destructor tp_del;

Include/internal/pycore_dict.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ typedef struct {
4040
#define _PyDictEntry_SetValue(entry, value) ((entry)->_me_value = value)
4141
#define _PyDictEntry_IsEmpty(entry) ((entry)->_me_value == NULL)
4242

43-
extern bool _PyDict_IsKeyImmutable(PyObject* op, PyObject* key);
43+
extern PyObject *_PyDict_IsKeyImmutable(PyObject* op, PyObject* key);
4444
extern PyDictKeysObject *_PyDict_NewKeysForClass(void);
4545
extern PyObject *_PyDict_FromKeys(PyObject *, PyObject *, PyObject *);
4646

Include/internal/pycore_regions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ extern "C" {
1717
PyObject* _Py_MakeImmutable(PyObject* obj);
1818
#define Py_MakeImmutable(op) _Py_MakeImmutable(_PyObject_CAST(op))
1919

20+
PyObject* Py_MakeGlobalsImmutable(void);
21+
22+
bool _PyGlobalsImmutable_Check(void);
23+
2024
#ifdef NDEBUG
2125
#define _Py_VPYDBG(fmt, ...)
2226
#define _Py_VPYDBGPRINT(fmt, ...)

Lib/importlib/_bootstrap.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def _object_name(obj):
2929
# Bootstrap-related code ######################################################
3030

3131
# Modules injected manually by _setup()
32+
_behaviors = None
3233
_thread = None
3334
_warnings = None
3435
_weakref = None
@@ -407,6 +408,69 @@ def __repr__(self):
407408
return f'_DummyModuleLock({self.name!r}) at {id(self)}'
408409

409410

411+
class _MultiInterpreterModuleLock:
412+
"""A recursive lock implementation which is able to detect deadlocks
413+
across multiple subinterpreters.
414+
"""
415+
416+
def __init__(self, name):
417+
self.lock = _behaviors.RLock()
418+
self.wakeup = _behaviors.Lock()
419+
self.name = name
420+
self.owner = None
421+
self.count = []
422+
self.waiters = []
423+
424+
def has_deadlock(self):
425+
return _has_deadlocked(
426+
# get the thread id of the current subinterpreter
427+
target_id=_behaviors.get_ident(),
428+
seen_ids=set(),
429+
candidate_ids=[self.owner],
430+
blocking_on=_blocking_on,
431+
)
432+
433+
def acquire(self):
434+
"""
435+
Acquire the module lock. If a potential deadlock is detected,
436+
a _DeadlockError is raised.
437+
Otherwise, the lock is always acquired and True is returned.
438+
"""
439+
bid = _behaviors.get_ident()
440+
with _BlockingOnManager(bid, self):
441+
while True:
442+
with self.lock:
443+
if self.count == [] or self.owner == bid:
444+
self.owner = bid
445+
self.count.append(True)
446+
return True
447+
448+
if self.has_deadlock():
449+
raise _DeadlockError(f'deadlock detected by {self!r}')
450+
451+
if self.wakeup.acquire(False):
452+
self.waiters.append(None)
453+
454+
self.wakeup.acquire()
455+
self.wakeup.release()
456+
457+
def release(self):
458+
bid = _behaviors.get_ident()
459+
with self.lock:
460+
if self.owner != bid:
461+
raise RuntimeError('cannot release un-acquired lock')
462+
assert len(self.count) > 0
463+
self.count.pop()
464+
if not len(self.count):
465+
self.owner = None
466+
if len(self.waiters) > 0:
467+
self.waiters.pop()
468+
self.wakeup.release()
469+
470+
def __repr__(self):
471+
return f'_MultiInterpreterModuleLock({self.name!r}) at {id(self)}'
472+
473+
410474
class _ModuleLockManager:
411475

412476
def __init__(self, name):
@@ -439,6 +503,8 @@ def _get_module_lock(name):
439503
if lock is None:
440504
if _thread is None:
441505
lock = _DummyModuleLock(name)
506+
elif _behaviors is not None and _behaviors.running():
507+
lock = _MultiInterpreterModuleLock(name)
442508
else:
443509
lock = _ModuleLock(name)
444510

@@ -943,6 +1009,11 @@ def _load_unlocked(spec):
9431009
finally:
9441010
spec._initializing = False
9451011

1012+
if _behaviors is not None and _behaviors.running():
1013+
# all modules must be made immutable upon load
1014+
if not isimmutable(module):
1015+
makeimmutable(module)
1016+
9461017
return module
9471018

9481019
# A method used during testing of _load_unlocked() and by
@@ -1518,7 +1589,7 @@ def _setup(sys_module, _imp_module):
15181589

15191590
# Directly load built-in modules needed during bootstrap.
15201591
self_module = sys.modules[__name__]
1521-
for builtin_name in ('_thread', '_warnings', '_weakref'):
1592+
for builtin_name in ('_thread', '_warnings', '_weakref', '_behaviors'):
15221593
if builtin_name not in sys.modules:
15231594
builtin_module = _builtin_from_name(builtin_name)
15241595
else:

Makefile.pre.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ PYTHON_OBJS= \
431431
Python/fileutils.o \
432432
Python/suggestions.o \
433433
Python/perf_trampoline.o \
434+
Python/regions.o \
434435
Python/$(DYNLOADFILE) \
435436
$(LIBOBJS) \
436437
$(MACHDEP_OBJS) \
@@ -475,7 +476,6 @@ OBJECT_OBJS= \
475476
Objects/obmalloc.o \
476477
Objects/picklebufobject.o \
477478
Objects/rangeobject.o \
478-
Objects/regions.o \
479479
Objects/setobject.o \
480480
Objects/sliceobject.o \
481481
Objects/structseq.o \

Modules/Setup

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ PYTHONPATH=$(COREPYTHONPATH)
131131
# Modules that should always be present (POSIX and Windows):
132132

133133
#_asyncio _asynciomodule.c
134+
#_behaviors _behaviorsmodule.c
134135
#_bisect _bisectmodule.c
135136
#_contextvars _contextvarsmodule.c
136137
#_csv _csv.c

0 commit comments

Comments
 (0)