Skip to content

Commit 845efc0

Browse files
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>
1 parent 0011cd6 commit 845efc0

File tree

1 file changed

+34
-0
lines changed

1 file changed

+34
-0
lines changed

docs/design/thread-safe-mode.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,40 @@ All internal code uses `self.connection._config` instead of global `config`:
253253
- Tables access config via `self.connection._config`
254254
- This works uniformly for both singleton and isolated instances
255255

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+
256290
## Error Messages
257291

258292
- Singleton access: `"Global DataJoint state is disabled in thread-safe mode. Use dj.Instance() to create an isolated instance."`

0 commit comments

Comments
 (0)