Skip to content

Commit d2d2941

Browse files
committed
fix: make schema code thread-safe with locking
Voluptuous contains thread-unsafe code; work around this by locking before calling into it.
1 parent 6a308f9 commit d2d2941

File tree

1 file changed

+26
-11
lines changed

1 file changed

+26
-11
lines changed

src/taskgraph/util/schema.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@
55

66
import pprint
77
import re
8+
import threading
89
from collections.abc import Mapping
910

1011
import voluptuous
1112

1213
import taskgraph
1314
from taskgraph.util.keyed_by import evaluate_keyed_by, iter_dot_path
1415

16+
# Global reentrant lock for thread-safe Schema creation, largely to work around
17+
# thread unsafe code in voluptuous.
18+
# RLock allows the same thread to acquire the lock multiple times (for recursive validation)
19+
# This is particularly important when running with a free-threaded Python, which
20+
# makes races more likely.
21+
_schema_creation_lock = threading.RLock()
22+
1523

1624
def validate_schema(schema, obj, msg_prefix):
1725
"""
@@ -206,25 +214,32 @@ class Schema(voluptuous.Schema):
206214
"""
207215

208216
def __init__(self, *args, check=True, **kwargs):
209-
super().__init__(*args, **kwargs)
217+
with _schema_creation_lock:
218+
super().__init__(*args, **kwargs)
210219

211-
self.check = check
212-
if not taskgraph.fast and self.check:
213-
check_schema(self)
220+
self.check = check
221+
if not taskgraph.fast and self.check:
222+
check_schema(self)
214223

215224
def extend(self, *args, **kwargs):
216-
schema = super().extend(*args, **kwargs)
225+
with _schema_creation_lock:
226+
schema = super().extend(*args, **kwargs)
217227

218-
if self.check:
219-
check_schema(schema)
220-
# We want twice extend schema to be checked too.
221-
schema.__class__ = Schema
222-
return schema
228+
if self.check:
229+
check_schema(schema)
230+
# We want twice extend schema to be checked too.
231+
schema.__class__ = Schema
232+
return schema
223233

224234
def _compile(self, schema):
225235
if taskgraph.fast:
226236
return
227-
return super()._compile(schema)
237+
with _schema_creation_lock:
238+
return super()._compile(schema)
239+
240+
def __call__(self, data):
241+
with _schema_creation_lock:
242+
return super().__call__(data)
228243

229244
def __getitem__(self, item):
230245
return self.schema[item] # type: ignore

0 commit comments

Comments
 (0)