You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: Add global state audit to thread-safe mode spec
Catalog all 8 module-level mutable globals with thread-safety
classification: guarded (config, connection), safe by design
(codec registry), or low risk (logging, blob flags, import caches).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/design/thread-safe-mode.md
+34Lines changed: 34 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -253,6 +253,40 @@ All internal code uses `self.connection._config` instead of global `config`:
253
253
- Tables access config via `self.connection._config`
254
254
- This works uniformly for both singleton and isolated instances
255
255
256
+
## Global State Audit
257
+
258
+
All module-level mutable state was reviewed for thread-safety implications.
259
+
260
+
### Guarded (blocked in thread-safe mode)
261
+
262
+
| State | Location | Mechanism |
263
+
|-------|----------|-----------|
264
+
|`config` singleton |`settings.py:979`|`_ConfigProxy` raises `ThreadSafetyError`; use `inst.config` instead |
265
+
|`conn()` singleton |`connection.py:108`|`_check_thread_safe()` guard; use `inst.connection` instead |
266
+
267
+
These are the two globals that carry connection-scoped state (credentials, database settings) and are the primary source of cross-tenant interference.
268
+
269
+
### Safe by design (no guard needed)
270
+
271
+
| State | Location | Rationale |
272
+
|-------|----------|-----------|
273
+
|`_codec_registry`|`codecs.py:47`| Effectively immutable after import. Registration runs in `__init_subclass__` under Python's import lock. Runtime mutation (`_load_entry_points`) is idempotent under the GIL. Codecs are part of the type system, not connection-scoped. |
274
+
|`_entry_points_loaded`|`codecs.py:48`| Bool flag for idempotent lazy loading; worst case under concurrent access is redundant work, not corruption. |
275
+
276
+
### Low risk (no guard needed)
277
+
278
+
| State | Location | Rationale |
279
+
|-------|----------|-----------|
280
+
| Logging side effects |`logging.py:8,17,40-45,56`| Standard Python logging configuration. Monkey-patches `Logger` and replaces `sys.excepthook` at import time. Not DataJoint-specific mutable state. |
281
+
|`use_32bit_dims`|`blob.py:65`| Runtime flag affecting deserialization. Rarely changed; not connection-scoped. |
282
+
|`compression` dict |`blob.py:61`| Decompressor function registry. Populated at import time, effectively read-only thereafter. |
283
+
|`_lazy_modules`|`__init__.py:92`| Import caching via `globals()` mutation. Protected by Python's import lock. |
284
+
|`ADAPTERS` dict |`adapters/__init__.py:16`| Backend registry. Populated at import time, read-only in practice. |
285
+
286
+
### Design principle
287
+
288
+
Only state that is **connection-scoped** (credentials, database settings, connection objects) needs thread-safe guards. State that is **code-scoped** (type registries, import caches, logging configuration) is shared across all threads by design and does not vary between tenants.
289
+
256
290
## Error Messages
257
291
258
292
- Singleton access: `"Global DataJoint state is disabled in thread-safe mode. Use dj.Instance() to create an isolated instance."`
0 commit comments