# Concurrency Guide
This document describes par-term's concurrency model: when to use each mutex type, how
the state hierarchy maps onto the threading model, and how to add new shared state
correctly.
For the low-level mutex API details and anti-patterns, see
[MUTEX_PATTERNS.md](MUTEX_PATTERNS.md).
## Table of Contents
- [Overview](#overview)
- [Threading Model](#threading-model)
- [State Hierarchy](#state-hierarchy)
- [Mutex Selection Rules](#mutex-selection-rules)
- [State Ownership by Layer](#state-ownership-by-layer)
- [Adding New Shared State](#adding-new-shared-state)
- [try_read/try_write Telemetry](#try_readtry_write-telemetry)
- [Related Documentation](#related-documentation)
## Overview
par-term runs two concurrent execution environments that share state:
| **Main thread** — OS event loop, rendering, UI | winit `EventLoop` | `parking_lot::Mutex` |
| **Tokio runtime** — PTY I/O, timers, clipboard sync | `Arc<tokio::runtime::Runtime>` | `tokio::sync::Mutex` |
The rule is simple: use `tokio::sync::Mutex` when a value is shared with any async task;
use `parking_lot::Mutex` when all callers are sync threads.
## Threading Model
```mermaid
graph TD
subgraph "Main Thread"
EventLoop[winit EventLoop]
Render[GPU Renderer]
Input[Input Handler]
EguiUI[egui UI]
end
subgraph "Tokio Runtime Pool"
PTYReader[PTY Reader Task]
InputSender[Input Sender Task]
ResizeHandler[Resize Handler Task]
Timers[Cursor Blink / Bell Timers]
ClipboardSync[Clipboard Sync Task]
AgentTask[ACP Agent Task]
end
subgraph "Std Background Threads"
SystemMonitor[System Monitor]
GitCheck[Git Status Check]
ShaderWatcher[Shader File Watcher]
AudioBell[Audio Bell Player]
end
EventLoop --> Render
EventLoop --> Input
EventLoop --> EguiUI
PTYReader -- "tokio::sync::RwLock" --> TerminalManager
InputSender -- "tokio::sync::RwLock" --> TerminalManager
EventLoop -- "try_read() / try_write() / blocking_write()" --> TerminalManager
SystemMonitor -- "parking_lot::Mutex" --> SystemMonitorData
EventLoop -- "parking_lot::Mutex" --> SystemMonitorData
style EventLoop fill:#e65100,stroke:#ff9800,stroke-width:3px,color:#ffffff
style PTYReader fill:#1b5e20,stroke:#4caf50,stroke-width:2px,color:#ffffff
style InputSender fill:#1b5e20,stroke:#4caf50,stroke-width:2px,color:#ffffff
style ResizeHandler fill:#1b5e20,stroke:#4caf50,stroke-width:2px,color:#ffffff
style Timers fill:#1b5e20,stroke:#4caf50,stroke-width:2px,color:#ffffff
style ClipboardSync fill:#1b5e20,stroke:#4caf50,stroke-width:2px,color:#ffffff
style AgentTask fill:#4a148c,stroke:#9c27b0,stroke-width:2px,color:#ffffff
style SystemMonitor fill:#37474f,stroke:#78909c,stroke-width:2px,color:#ffffff
style GitCheck fill:#37474f,stroke:#78909c,stroke-width:2px,color:#ffffff
style ShaderWatcher fill:#37474f,stroke:#78909c,stroke-width:2px,color:#ffffff
style AudioBell fill:#37474f,stroke:#78909c,stroke-width:2px,color:#ffffff
style Render fill:#0d47a1,stroke:#2196f3,stroke-width:2px,color:#ffffff
style Input fill:#0d47a1,stroke:#2196f3,stroke-width:2px,color:#ffffff
style EguiUI fill:#0d47a1,stroke:#2196f3,stroke-width:2px,color:#ffffff
```
## State Hierarchy
The per-window state follows a strict ownership hierarchy. Each layer is owned directly
(not behind a mutex) by its parent; only the leaf types that cross into async tasks carry
their own mutex.
```
WindowManager ← owns all WindowState instances
└─ WindowState ← per-window state (sync main thread only)
├─ TabManager ← owns all Tab instances (sync only)
│ └─ Tab ← per-tab state
│ ├─ terminal: Arc<tokio::sync::RwLock<TerminalManager>>
│ │ ← shared with async PTY/input tasks; RwLock for concurrent reads
│ └─ PaneManager ← sync only, owns all Pane instances
│ └─ Pane
│ └─ terminal: Arc<tokio::sync::RwLock<TerminalManager>>
│ ← shared with async PTY/input tasks
└─ AgentState
└─ agent: Option<Arc<tokio::sync::Mutex<Agent>>>
← shared with async ACP prompt tasks
```
**Key rules from this hierarchy:**
1. `WindowManager`, `WindowState`, `TabManager`, and `PaneManager` are only ever accessed
from the main thread's sync event loop. They carry no mutex.
2. `Tab.terminal` and `Pane.terminal` are `Arc<tokio::sync::RwLock<TerminalManager>>`
because the PTY reader task and input sender task share the same `TerminalManager`.
`RwLock` allows concurrent reads from multiple async tasks.
3. `AgentState.agent` is `tokio::sync::Mutex<Agent>` because ACP prompt processing runs
in a spawned async task.
## Mutex Selection Rules
| Is the value ever accessed from `runtime.spawn(async { ... })`? | Use `tokio::sync::Mutex` or `tokio::sync::RwLock` |
| Is the value held across an `.await` point? | Use `tokio::sync::Mutex` |
| Async shared with mostly reads? | Use `tokio::sync::RwLock` (allows concurrent readers) |
| All callers are sync threads or `std::thread::spawn`? | Use `parking_lot::Mutex` |
| High-frequency reads, infrequent writes, all sync? | Consider `parking_lot::RwLock` |
## State Ownership by Layer
### tokio::sync::Mutex / RwLock — async-shared state
| `Tab.terminal` | `Arc<tokio::sync::RwLock<TerminalManager>>` | PTY reader + input sender tasks; RwLock allows concurrent reads |
| `Pane.terminal` | `Arc<tokio::sync::RwLock<TerminalManager>>` | Same — each pane has its own PTY |
| `AgentState.agent` | `Option<Arc<tokio::sync::Mutex<Agent>>>` | ACP prompt tasks |
### parking_lot::Mutex / RwLock — sync-only shared state
| `SharedSessionLogger` (type alias) | `Arc<parking_lot::Mutex<Option<SessionLogger>>>` | Sync event loop + std output callback thread |
| `SystemMonitor.data` | `Arc<parking_lot::Mutex<SystemMonitorData>>` | Background std thread writes, render thread reads |
| `GitBranchPoller.status` | `Arc<parking_lot::Mutex<GitStatus>>` | Git-check std thread + render thread |
| `DebugLogger` (static) | `OnceLock<parking_lot::Mutex<DebugLogger>>` | Any-thread log writes |
| `AudioBell.sink` | `Option<Arc<parking_lot::Mutex<Player>>>` | Rodio std thread |
| `BadgeState.variables` | `Arc<parking_lot::RwLock<SessionVariables>>` | Frequent reads, infrequent writes, all sync |
| `ActiveUpload.error` | `Arc<parking_lot::Mutex<Option<String>>>` | Upload std thread writes, event loop reads |
## Adding New Shared State
When introducing a new value that must be shared across threads:
1. **Determine callers.** List every place that reads or writes the value and whether
each caller is sync (main thread, std thread) or async (Tokio task).
2. **Select mutex type.** If any caller is async or the lock must be held across
`.await`, use `tokio::sync::Mutex`. Otherwise use `parking_lot::Mutex`.
3. **Wrap in `Arc`.** Both mutex types should be wrapped in `Arc` for shared ownership:
`Arc<tokio::sync::Mutex<T>>` or `Arc<parking_lot::Mutex<T>>`.
4. **Use the correct access pattern** from sync contexts:
```rust
// For tokio::sync::RwLock from the winit event loop:
// Non-blocking read (safe to skip if contended):
if let Ok(guard) = terminal.try_read() {
// use guard for reading
} else {
crate::debug::record_try_lock_failure("my_site");
}
// Non-blocking write:
if let Ok(mut guard) = terminal.try_write() {
// use guard for writing
} else {
crate::debug::record_try_lock_failure("my_site_write");
}
// Blocking write (must not be skipped, but never call from a Tokio worker thread):
let mut guard = terminal.blocking_write();
```
5. **Document the mutex choice** with an inline comment explaining which tasks share
the value, following the pattern in `Tab` and `Pane`.
## try_read/try_write Telemetry
`try_read()` and `try_write()` misses are tracked globally via `crate::debug::record_try_lock_failure(site)`.
This call increments an atomic counter and emits a `CONCURRENCY` category debug log entry
at `DEBUG_LEVEL >= 3`. Periodic summaries appear in the `about_to_wait` handler.
A high miss rate at a specific site means an async task is holding the lock longer than
a single frame, which warrants investigation.
```rust
if let Ok(term) = tab.terminal.try_read() {
// use term for reading
} else {
crate::debug::record_try_lock_failure("resize_propagation");
}
```
See [LOGGING.md](LOGGING.md) for how to enable `DEBUG_LEVEL` logging.
## Related Documentation
- [Mutex Patterns](MUTEX_PATTERNS.md) — full mutex API reference, access patterns, and anti-patterns
- [Architecture](ARCHITECTURE.md) — high-level component and threading model overview
- [Logging](LOGGING.md) — enabling debug logging including CONCURRENCY category entries