|
4 | 4 |
|
5 | 5 | import pprint |
6 | 6 | import re |
| 7 | +import threading |
7 | 8 | from collections.abc import Mapping |
8 | 9 | from functools import reduce |
9 | 10 | from typing import Literal, Optional, Union |
|
14 | 15 | import taskgraph |
15 | 16 | from taskgraph.util.keyed_by import evaluate_keyed_by, iter_dot_path |
16 | 17 |
|
| 18 | +_schema_creation_lock = threading.RLock() |
| 19 | + |
17 | 20 | # Common type definitions that are used across multiple schemas |
18 | 21 | TaskPriority = Literal[ |
19 | 22 | "highest", "very-high", "high", "medium", "low", "very-low", "lowest" |
@@ -248,28 +251,36 @@ class LegacySchema(voluptuous.Schema): |
248 | 251 | """ |
249 | 252 | Operates identically to voluptuous.Schema, but applying some taskgraph-specific checks |
250 | 253 | 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. |
251 | 257 | """ |
252 | 258 |
|
253 | 259 | 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) |
255 | 263 |
|
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) |
259 | 267 |
|
260 | 268 | 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) |
262 | 272 |
|
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 |
268 | 278 |
|
269 | 279 | 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) |
273 | 284 |
|
274 | 285 | def __getitem__(self, item): |
275 | 286 | return self.schema[item] # type: ignore |
|
0 commit comments