Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

local.py 4.7KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import random
  2. import string
  3. import sys
  4. import threading
  5. import weakref
  6. class Local:
  7. """
  8. A drop-in replacement for threading.locals that also works with asyncio
  9. Tasks (via the current_task asyncio method), and passes locals through
  10. sync_to_async and async_to_sync.
  11. Specifically:
  12. - Locals work per-coroutine on any thread not spawned using asgiref
  13. - Locals work per-thread on any thread not spawned using asgiref
  14. - Locals are shared with the parent coroutine when using sync_to_async
  15. - Locals are shared with the parent thread when using async_to_sync
  16. (and if that thread was launched using sync_to_async, with its parent
  17. coroutine as well, with this working for indefinite levels of nesting)
  18. Set thread_critical to True to not allow locals to pass from an async Task
  19. to a thread it spawns. This is needed for code that truly needs
  20. thread-safety, as opposed to things used for helpful context (e.g. sqlite
  21. does not like being called from a different thread to the one it is from).
  22. Thread-critical code will still be differentiated per-Task within a thread
  23. as it is expected it does not like concurrent access.
  24. This doesn't use contextvars as it needs to support 3.6. Once it can support
  25. 3.7 only, we can then reimplement the storage more nicely.
  26. """
  27. def __init__(self, thread_critical: bool = False) -> None:
  28. self._thread_critical = thread_critical
  29. self._thread_lock = threading.RLock()
  30. self._context_refs: "weakref.WeakSet[object]" = weakref.WeakSet()
  31. # Random suffixes stop accidental reuse between different Locals,
  32. # though we try to force deletion as well.
  33. self._attr_name = "_asgiref_local_impl_{}_{}".format(
  34. id(self),
  35. "".join(random.choice(string.ascii_letters) for i in range(8)),
  36. )
  37. def _get_context_id(self):
  38. """
  39. Get the ID we should use for looking up variables
  40. """
  41. # Prevent a circular reference
  42. from .sync import AsyncToSync, SyncToAsync
  43. # First, pull the current task if we can
  44. context_id = SyncToAsync.get_current_task()
  45. context_is_async = True
  46. # OK, let's try for a thread ID
  47. if context_id is None:
  48. context_id = threading.current_thread()
  49. context_is_async = False
  50. # If we're thread-critical, we stop here, as we can't share contexts.
  51. if self._thread_critical:
  52. return context_id
  53. # Now, take those and see if we can resolve them through the launch maps
  54. for i in range(sys.getrecursionlimit()):
  55. try:
  56. if context_is_async:
  57. # Tasks have a source thread in AsyncToSync
  58. context_id = AsyncToSync.launch_map[context_id]
  59. context_is_async = False
  60. else:
  61. # Threads have a source task in SyncToAsync
  62. context_id = SyncToAsync.launch_map[context_id]
  63. context_is_async = True
  64. except KeyError:
  65. break
  66. else:
  67. # Catch infinite loops (they happen if you are screwing around
  68. # with AsyncToSync implementations)
  69. raise RuntimeError("Infinite launch_map loops")
  70. return context_id
  71. def _get_storage(self):
  72. context_obj = self._get_context_id()
  73. if not hasattr(context_obj, self._attr_name):
  74. setattr(context_obj, self._attr_name, {})
  75. self._context_refs.add(context_obj)
  76. return getattr(context_obj, self._attr_name)
  77. def __del__(self):
  78. try:
  79. for context_obj in self._context_refs:
  80. try:
  81. delattr(context_obj, self._attr_name)
  82. except AttributeError:
  83. pass
  84. except TypeError:
  85. # WeakSet.__iter__ can crash when interpreter is shutting down due
  86. # to _IterationGuard being None.
  87. pass
  88. def __getattr__(self, key):
  89. with self._thread_lock:
  90. storage = self._get_storage()
  91. if key in storage:
  92. return storage[key]
  93. else:
  94. raise AttributeError(f"{self!r} object has no attribute {key!r}")
  95. def __setattr__(self, key, value):
  96. if key in ("_context_refs", "_thread_critical", "_thread_lock", "_attr_name"):
  97. return super().__setattr__(key, value)
  98. with self._thread_lock:
  99. storage = self._get_storage()
  100. storage[key] = value
  101. def __delattr__(self, key):
  102. with self._thread_lock:
  103. storage = self._get_storage()
  104. if key in storage:
  105. del storage[key]
  106. else:
  107. raise AttributeError(f"{self!r} object has no attribute {key!r}")