Skip to content

Commit 467367e

Browse files
docs: Global config read-only (not blocked) in thread-safe mode
- thread_safe=True: global dj.config becomes read-only - conn.config copies from global config, always mutable - Simpler: global config still readable for defaults Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 697ec6d commit 467367e

1 file changed

Lines changed: 20 additions & 27 deletions

File tree

docs/design/thread-safe-mode.md

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ DataJoint uses global state (`dj.config`, `dj.conn()`) that is not thread-safe.
66

77
## Solution
88

9-
Add `thread_safe` mode that blocks global state access and requires explicit connection configuration.
9+
Add `thread_safe` mode that makes global config read-only and requires explicit connections with mutable connection-scoped settings.
1010

1111
## API
1212

@@ -25,7 +25,7 @@ export DJ_THREAD_SAFE=true
2525

2626
### Connection.from_config()
2727

28-
Creates a connection with explicit configuration. Works in both `thread_safe=True` and `thread_safe=False` modes.
28+
Creates a connection with explicit configuration. Works in both modes.
2929

3030
```python
3131
conn = dj.Connection.from_config(
@@ -43,61 +43,54 @@ schema = dj.Schema("my_schema", connection=conn)
4343
- `user` (required): Database username
4444
- `password` (required): Database password
4545
- `port`: Database port (default: 3306)
46-
- Any other setting from `dj.config` (e.g., `safemode`, `display_limit`, `stores`)
46+
- Any other setting (e.g., `safemode`, `display_limit`, `stores`)
4747

48-
**Config creation:** Uses the same `Config` class as global `dj.config`. Each connection gets its own `Config` instance via `conn.config`.
49-
50-
**Read-only after connection:** Database connection settings become read-only after connection is established:
51-
- `host`, `port`, `user`, `password`, `use_tls`, `backend`
52-
53-
**Mutable settings:** All other settings remain mutable per-connection:
54-
- `safemode`, `display_limit`, `stores`, etc.
48+
**Config creation:** Copies global `dj.config`, then applies kwargs. Creates `conn.config` which is always mutable.
5549

5650
```python
5751
conn = dj.Connection.from_config(host="localhost", user="u", password="p")
58-
conn.config.safemode # True (default)
59-
conn.config.display_limit # 12 (default)
60-
61-
conn.config.safemode = False # OK: modify for this connection
62-
conn.config.host = "other" # Error: read-only after connection
52+
conn.config.safemode = False # Always OK: conn.config is mutable
53+
conn.config.display_limit = 25 # Always OK
6354
```
6455

6556
## Behavior
6657

6758
| Operation | `thread_safe=False` | `thread_safe=True` |
6859
|-----------|--------------------|--------------------|
69-
| `dj.config.X` | Works | Raises `ThreadSafetyError` |
60+
| `dj.config` read | Works | Works (read-only) |
61+
| `dj.config` write | Works | Raises `ThreadSafetyError` |
7062
| `dj.conn()` | Works | Raises `ThreadSafetyError` |
7163
| `dj.Schema("name")` | Works | Raises `ThreadSafetyError` |
7264
| `Connection.from_config()` | Works | Works |
65+
| `conn.config` read/write | Works | Works |
7366
| `Schema(..., connection=conn)` | Works | Works |
7467

7568
## Read-Only Settings
7669

77-
- `thread_safe`: Read-only after global config initialization (set via env var or config file only)
78-
- `host`, `port`, `user`, `password`, `use_tls`, `backend`: Read-only on `conn.config` after connection is established
70+
- `thread_safe`: Always read-only after initialization (set via env var or config file only)
71+
- All of `dj.config`: Read-only when `thread_safe=True`
7972

8073
## Implementation
8174

8275
1. Add `thread_safe: bool = False` field to `Config` with `DJ_THREAD_SAFE` env alias
83-
2. Make `thread_safe` read-only after `Config` initialization
84-
3. Add guards to `Config.__getattr__`, `Config.__setattr__`, `Config.__getitem__`, `Config.__setitem__`
76+
2. Make `thread_safe` always read-only after initialization
77+
3. When `thread_safe=True`, make all `dj.config` writes raise `ThreadSafetyError`
8578
4. Add guard to `dj.conn()`
8679
5. Add guard to `Schema.__init__` when `connection=None`
8780
6. Add `Connection.from_config()` class method that:
88-
- Accepts all connection params and settings as kwargs
89-
- Creates a new `Config` instance for `conn.config`
90-
- Marks connection settings as read-only after connection
81+
- Copies global `dj.config`
82+
- Applies kwargs overrides
83+
- Creates mutable `conn.config`
9184
7. Add `ThreadSafetyError` exception
9285

9386
## Exceptions
9487

9588
```python
9689
class ThreadSafetyError(DataJointError):
97-
"""Raised when accessing global state in thread-safe mode."""
90+
"""Raised when modifying global state in thread-safe mode."""
9891
```
9992

10093
Error messages:
101-
- Config access: `"Global config is inaccessible in thread-safe mode. Use Connection.from_config() with explicit configuration."`
102-
- `dj.conn()`: `"dj.conn() is disabled in thread-safe mode. Use Connection.from_config() with explicit configuration."`
103-
- Schema without connection: `"Schema requires explicit connection in thread-safe mode. Use Schema(..., connection=conn)."`
94+
- Config write: `"Global config is read-only in thread-safe mode. Use conn.config for connection-scoped settings."`
95+
- `dj.conn()`: `"dj.conn() is disabled in thread-safe mode. Use Connection.from_config()."`
96+
- Schema without connection: `"Schema requires explicit connection in thread-safe mode."`

0 commit comments

Comments
 (0)