Skip to content

Commit fc3dff4

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 a3fe22b commit fc3dff4

1 file changed

Lines changed: 24 additions & 13 deletions

File tree

src/taskgraph/util/schema.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import pprint
66
import re
7+
import threading
78
from collections.abc import Mapping
89
from functools import reduce
910
from typing import Literal, Optional, Union
@@ -14,6 +15,8 @@
1415
import taskgraph
1516
from taskgraph.util.keyed_by import evaluate_keyed_by, iter_dot_path
1617

18+
_schema_creation_lock = threading.RLock()
19+
1720
# Common type definitions that are used across multiple schemas
1821
TaskPriority = Literal[
1922
"highest", "very-high", "high", "medium", "low", "very-low", "lowest"
@@ -248,28 +251,36 @@ class LegacySchema(voluptuous.Schema):
248251
"""
249252
Operates identically to voluptuous.Schema, but applying some taskgraph-specific checks
250253
in the process.
254+
255+
voluptuous.Schema's `_compile` method is thread-unsafe. Any usage (whether direct or
256+
indirect) of it must be protected by a lock.
251257
"""
252258

253259
def __init__(self, *args, check=True, **kwargs):
254-
super().__init__(*args, **kwargs)
260+
with _schema_creation_lock:
261+
# this constructor may call `_compile`
262+
super().__init__(*args, **kwargs)
255263

256-
self.check = check
257-
if not taskgraph.fast and self.check:
258-
check_schema(self)
264+
self.check = check
265+
if not taskgraph.fast and self.check:
266+
check_schema(self)
259267

260268
def extend(self, *args, **kwargs):
261-
schema = super().extend(*args, **kwargs)
269+
with _schema_creation_lock:
270+
# `extend` may create a new Schema object, which may call `_compile`
271+
schema = super().extend(*args, **kwargs)
262272

263-
if self.check:
264-
check_schema(schema)
265-
# We want twice extend schema to be checked too.
266-
schema.__class__ = LegacySchema
267-
return schema
273+
if self.check:
274+
check_schema(schema)
275+
# We want twice extend schema to be checked too.
276+
schema.__class__ = LegacySchema
277+
return schema
268278

269279
def _compile(self, schema):
270-
if taskgraph.fast:
271-
return
272-
return super()._compile(schema)
280+
with _schema_creation_lock:
281+
if taskgraph.fast:
282+
return
283+
return super()._compile(schema)
273284

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

0 commit comments

Comments
 (0)