cinderx 2026.1.16.2__cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. __static__/__init__.py +641 -0
  2. __static__/compiler_flags.py +8 -0
  3. __static__/enum.py +160 -0
  4. __static__/native_utils.py +77 -0
  5. __static__/type_code.py +48 -0
  6. __strict__/__init__.py +39 -0
  7. _cinderx.so +0 -0
  8. cinderx/__init__.py +577 -0
  9. cinderx/__pycache__/__init__.cpython-314.pyc +0 -0
  10. cinderx/_asyncio.py +156 -0
  11. cinderx/compileall.py +710 -0
  12. cinderx/compiler/__init__.py +40 -0
  13. cinderx/compiler/__main__.py +137 -0
  14. cinderx/compiler/config.py +7 -0
  15. cinderx/compiler/consts.py +72 -0
  16. cinderx/compiler/debug.py +70 -0
  17. cinderx/compiler/dis_stable.py +283 -0
  18. cinderx/compiler/errors.py +151 -0
  19. cinderx/compiler/flow_graph_optimizer.py +1287 -0
  20. cinderx/compiler/future.py +91 -0
  21. cinderx/compiler/misc.py +32 -0
  22. cinderx/compiler/opcode_cinder.py +18 -0
  23. cinderx/compiler/opcode_static.py +100 -0
  24. cinderx/compiler/opcodebase.py +158 -0
  25. cinderx/compiler/opcodes.py +991 -0
  26. cinderx/compiler/optimizer.py +547 -0
  27. cinderx/compiler/pyassem.py +3711 -0
  28. cinderx/compiler/pycodegen.py +7660 -0
  29. cinderx/compiler/pysourceloader.py +62 -0
  30. cinderx/compiler/static/__init__.py +1404 -0
  31. cinderx/compiler/static/compiler.py +629 -0
  32. cinderx/compiler/static/declaration_visitor.py +335 -0
  33. cinderx/compiler/static/definite_assignment_checker.py +280 -0
  34. cinderx/compiler/static/effects.py +160 -0
  35. cinderx/compiler/static/module_table.py +666 -0
  36. cinderx/compiler/static/type_binder.py +2176 -0
  37. cinderx/compiler/static/types.py +10580 -0
  38. cinderx/compiler/static/util.py +81 -0
  39. cinderx/compiler/static/visitor.py +91 -0
  40. cinderx/compiler/strict/__init__.py +69 -0
  41. cinderx/compiler/strict/class_conflict_checker.py +249 -0
  42. cinderx/compiler/strict/code_gen_base.py +409 -0
  43. cinderx/compiler/strict/common.py +507 -0
  44. cinderx/compiler/strict/compiler.py +352 -0
  45. cinderx/compiler/strict/feature_extractor.py +130 -0
  46. cinderx/compiler/strict/flag_extractor.py +97 -0
  47. cinderx/compiler/strict/loader.py +827 -0
  48. cinderx/compiler/strict/preprocessor.py +11 -0
  49. cinderx/compiler/strict/rewriter/__init__.py +5 -0
  50. cinderx/compiler/strict/rewriter/remove_annotations.py +84 -0
  51. cinderx/compiler/strict/rewriter/rewriter.py +975 -0
  52. cinderx/compiler/strict/runtime.py +77 -0
  53. cinderx/compiler/symbols.py +1754 -0
  54. cinderx/compiler/unparse.py +414 -0
  55. cinderx/compiler/visitor.py +194 -0
  56. cinderx/jit.py +230 -0
  57. cinderx/opcode.py +202 -0
  58. cinderx/static.py +113 -0
  59. cinderx/strictmodule.py +6 -0
  60. cinderx/test_support.py +341 -0
  61. cinderx-2026.1.16.2.dist-info/METADATA +15 -0
  62. cinderx-2026.1.16.2.dist-info/RECORD +68 -0
  63. cinderx-2026.1.16.2.dist-info/WHEEL +6 -0
  64. cinderx-2026.1.16.2.dist-info/licenses/LICENSE +21 -0
  65. cinderx-2026.1.16.2.dist-info/top_level.txt +5 -0
  66. opcodes/__init__.py +0 -0
  67. opcodes/assign_opcode_numbers.py +272 -0
  68. opcodes/cinderx_opcodes.py +121 -0
cinderx/__init__.py ADDED
@@ -0,0 +1,577 @@
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # pyre-strict
3
+ """High-performance Python runtime extensions."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import gc
8
+ import platform
9
+ import sys
10
+ from os import environ
11
+
12
+ # ============================================================================
13
+ # Note!
14
+ #
15
+ # At Meta, this module is currently loaded as part of Lib/site.py in an attempt
16
+ # to get benefits from using it as soon as possible. However
17
+ # Lib/test/test_site.py will assert that site.py does not import too many
18
+ # modules. Be careful with adding import statements here.
19
+ #
20
+ # The plan is to move applications over to using an explicit initialization
21
+ # step rather than Lib/site.py. Once that is done we can add all the imports
22
+ # we want here.
23
+ # ============================================================================
24
+
25
+
26
+ _import_error: ImportError | None = None
27
+
28
+
29
+ def is_supported_runtime() -> bool:
30
+ """
31
+ Check that the current Python runtime will be able to load the _cinderx
32
+ native extension.
33
+ """
34
+
35
+ if sys.platform not in ("darwin", "linux"):
36
+ return False
37
+
38
+ version = (sys.version_info.major, sys.version_info.minor)
39
+ if version == (3, 14) or version == (3, 15):
40
+ # Can't load the native extension if the GIL is forcibly being disabled. The
41
+ # native extension doesn't support free-threading properly yet.
42
+ return environ.get("PYTHON_GIL") != "0"
43
+ if version == (3, 12):
44
+ return "+meta" in sys.version
45
+ if version == (3, 10):
46
+ return "+cinder" in sys.version
47
+ return False
48
+
49
+
50
+ try:
51
+ # Currently if we try to import _cinderx on runtimes without our internal patches
52
+ # the import will crash. This is meant to go away in the future.
53
+ if not is_supported_runtime():
54
+ raise ImportError(
55
+ f"The _cinderx native extension is not supported for Python version '{sys.version}' on platform '{sys.platform}'"
56
+ )
57
+
58
+ # pyre-ignore[21]: _cinderx is not a real cpp_python_extension() yet.
59
+ from _cinderx import ( # noqa: F401
60
+ _compile_perf_trampoline_pre_fork,
61
+ _is_compile_perf_trampoline_pre_fork_enabled,
62
+ async_cached_classproperty,
63
+ async_cached_property,
64
+ cached_classproperty,
65
+ cached_property,
66
+ cached_property_with_descr,
67
+ clear_caches,
68
+ clear_classloader_caches,
69
+ disable_parallel_gc,
70
+ enable_parallel_gc,
71
+ freeze_type,
72
+ get_parallel_gc_settings,
73
+ has_parallel_gc,
74
+ immortalize_heap,
75
+ is_immortal,
76
+ strict_module_patch,
77
+ strict_module_patch_delete,
78
+ strict_module_patch_enabled,
79
+ StrictModule,
80
+ watch_sys_modules,
81
+ )
82
+
83
+ if sys.version_info < (3, 11):
84
+ # In 3.12+ use the versions in the polyfill cinder library instead.
85
+ from _cinderx import (
86
+ _get_entire_call_stack_as_qualnames_with_lineno,
87
+ _get_entire_call_stack_as_qualnames_with_lineno_and_frame,
88
+ clear_all_shadow_caches,
89
+ )
90
+
91
+ if sys.version_info >= (3, 12):
92
+ from _cinderx import delay_adaptive, get_adaptive_delay, set_adaptive_delay
93
+
94
+ except ImportError as e:
95
+ if "undefined symbol:" in str(e):
96
+ # If we're on a dev build report this as an error, otherwise muddle along with alternative definitions
97
+ # on unsupported Python's.
98
+ from os.path import dirname, exists, join
99
+
100
+ if exists(join(dirname(__file__), ".dev_build")):
101
+ raise ImportError(
102
+ "The _cinderx native extension is not available due to a missing symbol. This is likely a bug you introduced. "
103
+ "Please ensure that the cinderx kernel is being used."
104
+ ) from e
105
+ _import_error = e
106
+
107
+ def _compile_perf_trampoline_pre_fork() -> None:
108
+ pass
109
+
110
+ def _get_entire_call_stack_as_qualnames_with_lineno() -> list[tuple[str, int]]:
111
+ return []
112
+
113
+ def _get_entire_call_stack_as_qualnames_with_lineno_and_frame() -> list[
114
+ tuple[str, int, object]
115
+ ]:
116
+ return []
117
+
118
+ def _is_compile_perf_trampoline_pre_fork_enabled() -> bool:
119
+ return False
120
+
121
+ from asyncio import AbstractEventLoop, Future
122
+ from typing import (
123
+ Awaitable,
124
+ Callable,
125
+ Dict,
126
+ final,
127
+ Generator,
128
+ Generic,
129
+ List,
130
+ NoReturn,
131
+ Optional,
132
+ overload,
133
+ Tuple,
134
+ Type,
135
+ TYPE_CHECKING,
136
+ TypeVar,
137
+ )
138
+
139
+ _TClass = TypeVar("_TClass")
140
+ _TReturnType = TypeVar("_TReturnType")
141
+
142
+ @final
143
+ class NoValueSet:
144
+ pass
145
+
146
+ NO_VALUE_SET = NoValueSet()
147
+
148
+ class _BaseCachedProperty(Generic[_TClass, _TReturnType]):
149
+ fget: Callable[[_TClass], _TReturnType]
150
+ __name__: str
151
+
152
+ def __init__(
153
+ self,
154
+ f: Callable[[_TClass], _TReturnType],
155
+ slot: Optional[Descriptor[_TReturnType]] = None,
156
+ ) -> None:
157
+ self.fget: Callable[[_TClass], _TReturnType] = f
158
+ self.__name__ = f.__name__
159
+ self.__doc__: str | None = f.__doc__
160
+ self.slot = slot
161
+
162
+ @overload
163
+ def __get__(
164
+ self, obj: None, cls: Type[_TClass]
165
+ ) -> _BaseCachedProperty[_TClass, _TReturnType]: ...
166
+
167
+ @overload
168
+ def __get__(self, obj: _TClass, cls: Type[_TClass]) -> _TReturnType: ...
169
+
170
+ def __get__(
171
+ self, obj: Optional[_TClass], cls: Type[_TClass]
172
+ ) -> _BaseCachedProperty[_TClass, _TReturnType] | _TReturnType:
173
+ if obj is None:
174
+ return self
175
+ slot = self.slot
176
+ if slot is not None:
177
+ try:
178
+ res = slot.__get__(obj, cls)
179
+ except AttributeError:
180
+ res = self.fget(obj)
181
+ slot.__set__(obj, res)
182
+ return res
183
+
184
+ result = self.fget(obj)
185
+ obj.__dict__[self.__name__] = result
186
+ return result
187
+
188
+ class _AsyncLazyValueState:
189
+ NotStarted = 0
190
+ Running = 1
191
+ Done = 2
192
+
193
+ _T = TypeVar("_T", covariant=True)
194
+ _TParams = TypeVar("_TParams")
195
+
196
+ # noqa: F401
197
+ import asyncio
198
+
199
+ class _AsyncLazyValue(Awaitable[_T]):
200
+ """
201
+ This is a low-level class used mainly for two things:
202
+ * It helps to avoid calling a coroutine multiple times, by caching the
203
+ result of a previous call
204
+ * It ensures that the coroutine is called only once
205
+
206
+ _AsyncLazyValue has well defined cancellation behavior in these cases:
207
+
208
+ 1. When we have a single task stack (call stack for you JS folks), which is
209
+ awaiting on the AsyncLazyValue
210
+ -> In this case, we mimic the behavior of a normal await. i.e: If the
211
+ task stack gets cancelled, we cancel the coroutine (by raising a
212
+ CancelledError in the underlying future)
213
+
214
+ 2. When we have multiple task stacks awaiting on the future.
215
+ We have two sub cases here.
216
+
217
+ 2.1. The initial task stack (which resulted in an await of the coroutine)
218
+ gets cancelled.
219
+ -> In this case, we cancel the coroutine, and all the tasks depending
220
+ on it. If we don't do that, we'd have to implement retry logic,
221
+ which is a bad idea in such low level code. Even if we do implement
222
+ retries, there's no guarantee that they would succeed, so it's better
223
+ to just fail here.
224
+
225
+ Also, the number of times this happens is very small (I don't have
226
+ data to prove it, but qualitative arguments suggest this is the
227
+ case).
228
+
229
+ 2.2. One of the many task stacks gets cancelled (but not the one which ended
230
+ up awaiting the coroutine)
231
+ -> In this case, we just allow the task stack to be cancelled, but
232
+ the rest of them are processed without being affected.
233
+ """
234
+
235
+ def __init__(
236
+ self,
237
+ # pyre-fixme[31]: Expression `typing.Callable[(_TParams,
238
+ # typing.Awaitable[_T])]` is not a valid type.
239
+ coro_func: Callable[_TParams, Awaitable[_T]],
240
+ # pyre-fixme[11]: Annotation `args` is not defined as a type.
241
+ *args: _TParams.args,
242
+ # pyre-fixme[11]: Annotation `kwargs` is not defined as a type.
243
+ **kwargs: _TParams.kwargs,
244
+ ) -> None:
245
+ global asyncio
246
+ # pyre-fixme[31]: Expression `typing.Optional[typing.Callable[(_TParams,
247
+ # typing.Awaitable[_T])]]` is not a valid type.
248
+ self.coro_func: Optional[Callable[_TParams, Awaitable[_T]]] = coro_func
249
+ self.args: Tuple[object, ...] = args
250
+ self.kwargs: Dict[str, object] = kwargs
251
+ self.state: int = _AsyncLazyValueState.NotStarted
252
+ self.res: Optional[_T] = None
253
+ self._futures: List[Future] = []
254
+ self._awaiting_tasks = 0
255
+
256
+ async def _async_compute(self) -> _T:
257
+ futures = self._futures
258
+ try:
259
+ coro_func = self.coro_func
260
+ # lint-fixme: NoAssertsRule
261
+ assert coro_func is not None
262
+ self.res = res = await coro_func(*self.args, **self.kwargs)
263
+
264
+ self.state = _AsyncLazyValueState.Done
265
+
266
+ # pyre-fixme[1001]: Awaitable assigned to `value` is never awaited.
267
+ for value in futures:
268
+ if not value.done():
269
+ value.set_result(self.res)
270
+
271
+ self.args = ()
272
+ self.kwargs.clear()
273
+ del self._futures[:]
274
+ self.coro_func = None
275
+
276
+ return res
277
+
278
+ except (Exception, asyncio.CancelledError) as e:
279
+ # pyre-fixme[1001]: Awaitable assigned to `value` is never awaited.
280
+ for value in futures:
281
+ if not value.done():
282
+ value.set_exception(e)
283
+ self._futures = []
284
+ self.state = _AsyncLazyValueState.NotStarted
285
+ raise
286
+
287
+ def _get_future(self, loop: Optional[AbstractEventLoop]) -> Future:
288
+ if loop is None:
289
+ loop = asyncio.get_event_loop()
290
+ f = asyncio.Future(loop=loop)
291
+ self._futures.append(f)
292
+ self._awaiting_tasks += 1
293
+ return f
294
+
295
+ def __iter__(self) -> _AsyncLazyValue[_T]:
296
+ return self
297
+
298
+ def __next__(self) -> NoReturn:
299
+ raise StopIteration(self.res)
300
+
301
+ def __await__(self) -> Generator[None, None, _T]:
302
+ if self.state == _AsyncLazyValueState.Done:
303
+ # pyre-ignore[7]: Expected `Generator[None, None, Variable[_T](covariant)]`
304
+ # but got `_AsyncLazyValue[Variable[_T](covariant)]`.
305
+ return self
306
+ elif self.state == _AsyncLazyValueState.Running:
307
+ c = self._get_future(None)
308
+ return c.__await__()
309
+ else:
310
+ self.state = _AsyncLazyValueState.Running
311
+ c = self._async_compute()
312
+ return c.__await__()
313
+
314
+ def as_future(self, loop: AbstractEventLoop) -> Future:
315
+ if self.state == _AsyncLazyValueState.Done:
316
+ f = asyncio.Future(loop=loop)
317
+ f.set_result(self.res)
318
+ return f
319
+ elif self.state == _AsyncLazyValueState.Running:
320
+ return self._get_future(loop)
321
+ else:
322
+ if loop is None:
323
+ loop = asyncio.get_event_loop()
324
+ t = loop.create_task(self._async_compute())
325
+ self.state = _AsyncLazyValueState.Running
326
+ # pyre-ignore[16]: Undefined attribute `asyncio.tasks.Task`
327
+ # has no attribute `_source_traceback`.
328
+ if t._source_traceback:
329
+ del t._source_traceback[-1]
330
+ # pyre-fixme[7]: Expected `Future[Any]` but got `Task[_T]`.
331
+ return t
332
+
333
+ _TAwaitableReturnType = TypeVar("_TAwaitableReturnType")
334
+
335
+ class async_cached_property(
336
+ Generic[_TAwaitableReturnType, _TClass],
337
+ _BaseCachedProperty[_TClass, Awaitable[_TAwaitableReturnType]],
338
+ ):
339
+ def __init__(
340
+ self,
341
+ f: Callable[[_TClass], _TReturnType],
342
+ slot: Optional[Descriptor[_TReturnType]] = None,
343
+ ) -> None:
344
+ super().__init__(f, slot)
345
+
346
+ def __get__(
347
+ self, obj: Optional[_TClass], cls: Type[_TClass]
348
+ ) -> (
349
+ _BaseCachedProperty[_TClass, Awaitable[_TAwaitableReturnType]]
350
+ | Awaitable[_TAwaitableReturnType]
351
+ ):
352
+ if obj is None:
353
+ return self
354
+
355
+ slot = self.slot
356
+ if slot is not None:
357
+ try:
358
+ res = slot.__get__(obj, cls)
359
+ except AttributeError:
360
+ res = _AsyncLazyValue(self.fget, obj)
361
+ slot.__set__(obj, res)
362
+ return res
363
+
364
+ lazy_value = _AsyncLazyValue(self.fget, obj)
365
+ setattr(obj, self.__name__, lazy_value)
366
+ return lazy_value
367
+
368
+ class async_cached_classproperty(
369
+ Generic[_TAwaitableReturnType, _TClass],
370
+ _BaseCachedProperty[Type[_TClass], Awaitable[_TAwaitableReturnType]],
371
+ ):
372
+ def __init__(
373
+ self,
374
+ f: Callable[[_TClass], Awaitable[_TAwaitableReturnType]],
375
+ slot: Optional[Descriptor[Awaitable[_TAwaitableReturnType]]] = None,
376
+ ) -> None:
377
+ super().__init__(f, slot)
378
+ self._value: NoValueSet | Awaitable[_TAwaitableReturnType] = NO_VALUE_SET
379
+
380
+ def __get__(
381
+ self, obj: Optional[_TClass], cls: Type[_TClass]
382
+ ) -> Awaitable[_TAwaitableReturnType]:
383
+ lazy_value = self._value
384
+ if not isinstance(lazy_value, NoValueSet):
385
+ return lazy_value
386
+ self._value = lazy_value = _AsyncLazyValue(self.fget, cls)
387
+ return lazy_value
388
+
389
+ class cached_classproperty(_BaseCachedProperty[Type[_TClass], _TReturnType]):
390
+ def __init__(
391
+ self,
392
+ f: Callable[[_TClass], _TReturnType],
393
+ slot: Optional[Descriptor[_TReturnType]] = None,
394
+ ) -> None:
395
+ super().__init__(f, slot)
396
+ self._value: NoValueSet | _TReturnType = NO_VALUE_SET
397
+
398
+ def __get__(self, obj: Optional[_TClass], cls: Type[_TClass]) -> _TReturnType:
399
+ result = self._value
400
+ if not isinstance(result, NoValueSet):
401
+ return result
402
+ self._value = result = self.fget(cls)
403
+ return result
404
+
405
+ _TClass = TypeVar("_TClass")
406
+ _TReturnType = TypeVar("_TReturnType")
407
+
408
+ if TYPE_CHECKING:
409
+ from abc import ABC
410
+
411
+ @final
412
+ class Descriptor(ABC, Generic[_TReturnType]):
413
+ __name__: str
414
+ __objclass__: Type[object]
415
+
416
+ def __get__(
417
+ self, inst: object, ctx: Optional[Type[object]] = None
418
+ ) -> _TReturnType: ...
419
+
420
+ def __set__(self, inst: object, value: _TReturnType) -> None:
421
+ pass
422
+
423
+ def __delete__(self, inst: object) -> None:
424
+ pass
425
+
426
+ class cached_property(_BaseCachedProperty[_TClass, _TReturnType]):
427
+ def __init__(
428
+ self,
429
+ f: Callable[[_TClass], _TReturnType],
430
+ slot: Optional[Descriptor[_TReturnType]] = None,
431
+ ) -> None:
432
+ super().__init__(f, slot)
433
+ if slot is not None:
434
+ if (
435
+ type(self) is not cached_property
436
+ and type(self) is not cached_property_with_descr
437
+ ):
438
+ raise TypeError(
439
+ "slot can't be used with subtypes of cached_property"
440
+ )
441
+ # pyre-ignore[4]: Missing attribute annotation for __class__
442
+ self.__class__ = cached_property_with_descr
443
+
444
+ class cached_property_with_descr(cached_property[_TClass, _TReturnType]):
445
+ def __set__(self, inst: object, value: _TReturnType) -> None:
446
+ slot = self.slot
447
+ if slot is not None:
448
+ slot.__set__(inst, value)
449
+ else:
450
+ setattr(inst, self.__name__, value)
451
+
452
+ def __delete__(self, inst: object) -> None:
453
+ slot = self.slot
454
+ if slot is not None:
455
+ slot.__delete__(inst)
456
+ else:
457
+ delattr(inst, self.__name__)
458
+
459
+ if sys.version_info < (3, 11):
460
+
461
+ def clear_all_shadow_caches() -> None:
462
+ pass
463
+
464
+ def clear_caches() -> None:
465
+ pass
466
+
467
+ def clear_classloader_caches() -> None:
468
+ pass
469
+
470
+ def disable_parallel_gc() -> None:
471
+ pass
472
+
473
+ def enable_parallel_gc(min_generation: int = 2, num_threads: int = 0) -> None:
474
+ raise RuntimeError(
475
+ "No Parallel GC support because _cinderx did not load correctly"
476
+ )
477
+
478
+ def freeze_type(ty: object) -> object:
479
+ return ty
480
+
481
+ def get_parallel_gc_settings() -> dict[str, int] | None:
482
+ return None
483
+
484
+ def has_parallel_gc() -> bool:
485
+ return False
486
+
487
+ def immortalize_heap() -> None:
488
+ pass
489
+
490
+ def is_immortal(obj: object) -> bool:
491
+ raise RuntimeError(
492
+ "Can't answer whether an object is mortal or immortal from Python code"
493
+ )
494
+
495
+ def strict_module_patch(mod: object, name: str, value: object) -> None:
496
+ pass
497
+
498
+ def strict_module_patch_delete(mod: object, name: str) -> None:
499
+ pass
500
+
501
+ def strict_module_patch_enabled(mod: object) -> bool:
502
+ return False
503
+
504
+ class StrictModule:
505
+ def __init__(self, d: dict[str, object], b: bool) -> None:
506
+ pass
507
+
508
+ def watch_sys_modules() -> None:
509
+ pass
510
+
511
+
512
+ def maybe_enable_parallel_gc() -> None:
513
+ """Conditionally enable parallel GC based on environment variables."""
514
+ is_parallel_gc_enabled = environ.get("PARALLEL_GC_ENABLED", "0") == "1"
515
+ if not has_parallel_gc() or not is_parallel_gc_enabled:
516
+ return
517
+ thresholds = gc.get_threshold()
518
+ parallel_gc_threshold_gen0 = int(
519
+ environ.get("PARALLEL_GC_THRESHOLD_GEN0", thresholds[0])
520
+ )
521
+ parallel_gc_threshold_gen1 = int(
522
+ environ.get("PARALLEL_GC_THRESHOLD_GEN1", thresholds[1])
523
+ )
524
+ parallel_gc_threshold_gen2 = int(
525
+ environ.get("PARALLEL_GC_THRESHOLD_GEN2", thresholds[2])
526
+ )
527
+ gc.set_threshold(
528
+ parallel_gc_threshold_gen0,
529
+ parallel_gc_threshold_gen1,
530
+ parallel_gc_threshold_gen2,
531
+ )
532
+
533
+ parallel_gc_num_threads = int(environ.get("PARALLEL_GC_NUM_THREADS", "0"))
534
+ parallel_gc_min_generation = int(environ.get("PARALLEL_GC_MIN_GENERATION", "2"))
535
+
536
+ enable_parallel_gc(
537
+ min_generation=parallel_gc_min_generation,
538
+ num_threads=parallel_gc_num_threads,
539
+ )
540
+
541
+
542
+ _is_init: bool = False
543
+
544
+
545
+ def init() -> None:
546
+ """Initialize CinderX."""
547
+ global _is_init
548
+
549
+ # Failed to import _cinderx, nothing to initialize.
550
+ if _import_error is not None:
551
+ return
552
+
553
+ # Already initialized.
554
+ if _is_init:
555
+ return
556
+
557
+ maybe_enable_parallel_gc()
558
+
559
+ _is_init = True
560
+
561
+
562
+ def is_initialized() -> bool:
563
+ """
564
+ Check if the cinderx extension has been properly initialized.
565
+ """
566
+ return _is_init
567
+
568
+
569
+ def get_import_error() -> ImportError | None:
570
+ """
571
+ Get the ImportError that occurred when _cinderx was imported, if there was
572
+ an error.
573
+ """
574
+ return _import_error
575
+
576
+
577
+ init()