|
5 | 5 |
|
6 | 6 | import pprint |
7 | 7 | import re |
| 8 | +import threading |
8 | 9 | from collections.abc import Mapping |
9 | 10 |
|
10 | 11 | import voluptuous |
11 | 12 |
|
12 | 13 | import taskgraph |
13 | 14 | from taskgraph.util.keyed_by import evaluate_keyed_by, iter_dot_path |
14 | 15 |
|
| 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 | + |
15 | 23 |
|
16 | 24 | def validate_schema(schema, obj, msg_prefix): |
17 | 25 | """ |
@@ -206,25 +214,32 @@ class Schema(voluptuous.Schema): |
206 | 214 | """ |
207 | 215 |
|
208 | 216 | def __init__(self, *args, check=True, **kwargs): |
209 | | - super().__init__(*args, **kwargs) |
| 217 | + with _schema_creation_lock: |
| 218 | + super().__init__(*args, **kwargs) |
210 | 219 |
|
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) |
214 | 223 |
|
215 | 224 | def extend(self, *args, **kwargs): |
216 | | - schema = super().extend(*args, **kwargs) |
| 225 | + with _schema_creation_lock: |
| 226 | + schema = super().extend(*args, **kwargs) |
217 | 227 |
|
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 |
223 | 233 |
|
224 | 234 | def _compile(self, schema): |
225 | 235 | if taskgraph.fast: |
226 | 236 | 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) |
228 | 243 |
|
229 | 244 | def __getitem__(self, item): |
230 | 245 | return self.schema[item] # type: ignore |
|
0 commit comments