lardon3d/docs/development/concurrency.md

412 lines
11 KiB
Markdown

# Concurrency
## Status
```text
NCURSES_OWNER=MAIN_THREAD_ONLY
ACTIVE_HEAVY_QUEUE_CALLBACKS=1
TASK_CANCELLATION=COOPERATIVE
INTERNAL_PARALLELISM=BOUNDED
OWNER_ONLY_PUBLICATION=CANONICAL_WHERE_REQUIRED
TSAN_PORTABLE_PROJECT_MATRIX=QUALIFIED_PASS
TSAN_EXTERNAL_OPENCV_TBB=QUALIFIED
VULKAN_CONCURRENCY_VALIDATION=SEPARATE
RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT
SERIALISM_REQUIRES_PROOF=CANONICAL
```
Lardon3D separates UI ownership from heavy processing.
The main thread owns ncurses. The Task Queue owns one active heavy callback.
Individual validated Task Kinds may create bounded internal participants inside
that callback.
An SSD controller operation may also use at most one bounded joinable operation
thread under its own ownership contract.
## Execution model
```text
main thread
input
ncurses
TUI orchestration
Task Queue worker
one active heavy callback
admitted Task sequence
optional bounded internal participants
deterministic owner publication
SSD operation thread
zero or one bounded joinable controller operation
no ncurses
no Task callback
```
Internal participants are not a second global scheduler or Queue.
## Fundamental rules
### ncurses ownership
Only the main thread calls ncurses.
Workers publish observable state through protected data. They never call
`mvprintw`, `wrefresh`, or other ncurses APIs.
### Shared mutable state
Shared mutable state must have an explicit synchronization owner:
```text
mutex
condition variable
atomic primitive where the contract explicitly permits it
single-thread ownership
immutable-after-publication
```
Do not rely on timing or "normally only one caller".
### Condition variables
Always test the predicate in a loop around `pthread_cond_wait()`.
A signal is not durable state; the protected predicate is.
### Cooperative cancellation
Production Task cancellation is cooperative.
Do not use `pthread_cancel()` to stop a Task.
Task-specific non-preemptible operations finish their current atomic boundary
before pause/cancel is observed.
### Reservation before callback
No Task callback runs without the Resource Governor admission/reservation
required by its installed sequence contract.
Fixed-resource Tasks do not bypass the Governor.
### Terminal lifetime
Terminal callback completion precedes destruction of Task userdata.
Queue/Task ownership must ensure no observer dereferences freed userdata.
## Lock ordering
When multiple locks are required, the owning subsystem must define and preserve
one order.
Never add a reverse-order path to solve a local problem.
Avoid holding one subsystem mutex while calling into another subsystem that may
call back.
Where practical:
```text
copy bounded state under lock
release lock
perform I/O / expensive work
reacquire only for publication
```
## Queue ingress lifetime
The Queue owner closes ingress before destruction.
Shutdown waits for:
- active worker completion;
- registered in-flight API calls covered by the ownership contract;
- terminal callbacks.
This cannot make a raw C pointer safe if a caller begins a new call after the
object has already been freed. Callers must obey lifetime ownership.
## Bounded internal parallelism
A validated Task Kind may use internal participants while the Queue callback
remains the sole Task owner.
Required shape:
```text
one admitted Task owner
-> bounded participant count
-> bounded private work
-> join all participants
-> owner-only deterministic publication when required
-> Task-specific durable cursor
-> generic checkpoint
-> sequence_break
```
Participant count and memory must fit the admitted Resource Governor contract.
No participant may silently exceed the installed sequence contract.
## Atomicity does not imply serialism
Per-item scientific atomicity and cross-item execution width are separate.
```text
PER_ITEM_ATOMICITY_REQUIRES_CROSS_ITEM_SERIALISM=NO
OWNER_ONLY_PUBLICATION_REQUIRES_SERIAL_PREPARATION=NO
```
Current examples include selected RAW, selected Feature extraction, Candidate
Pair source work and outer Geometric Verification preparation.
Serialization is valid only where the subsystem's scientific, persistence,
library or measured-throughput contract proves it necessary.
## CPU/batch coupling
CPU and batch/window are not globally independent dimensions.
For a Task whose additional participants cannot do useful work while the
admitted item window remains one, a Task-specific capability may couple those
dimensions.
Current validated examples include:
```text
candidate_pair.generate/1
features.extract.batch/1
```
This is not a universal rule for all Task Kinds.
## Project lifetime boundary
Before closing a project:
```text
views release Project DB borrows
-> Queue is cancelled/joined/destroyed
-> Project DB closes
-> fresh empty Queue may be created for the next project
```
A terminal callback must never observe a Project DB already destroyed.
Project-specific runtime history must not leak into the next project.
## Global shutdown boundary
The current shutdown order preserves ownership across:
```text
Task Queue and Task leases
-> project close
-> join/unregister SSD binding
-> SSD controller
-> Resource Governor
```
A scratch unregister blocked by a real outstanding lease is an observable
failure, not permission to abandon a live pointer.
## SSD lease ownership
A scratch lease belongs to the exact caller-owned lease object used for the
operation.
Its mutable fields are controlled under the SSD controller mutex.
The caller must not:
- copy a live lease;
- move a live lease;
- present the same lease object to two controllers;
- release through a different ownership path.
Production acquire/release uses the Resource Governor wrappers.
The Governor releases its own mutex before entering the controller, and the
controller does not callback into the Governor while holding its mutex.
Scratch remains storage capacity, never RAM admission.
## Visual Index readers
Visual Index query readers do not share mutable query state.
A query obtains a bounded segment snapshot under the Project DB boundary, then
performs hash/read/accumulation after release.
A concurrent update makes a new segment visible only at its canonical commit
boundary. An already running query continues with its retained snapshot.
## OpenCV process-wide state
OpenCV thread configuration is process-wide.
The active heavy Queue callback owns temporary mutation of that setting where a
Task contract requires it and restores the previous/baseline value on all exit
paths.
Internal participants must not independently race `cv::setNumThreads()`.
For Feature batch, cross-image participants are used while internal OpenCV
threading is controlled explicitly.
## Vulkan boundary
ORB Vulkan concurrency is validated separately from portable TSan.
The production AUTO contract currently uses:
```text
normal inflight depth = 1
private validated safety depth = 2
helpers = 0
```
Depth 2 is a private safety/benchmark capability and was rejected as the normal
useful setting by measured throughput.
A Vulkan backend failure produces complete CPU fallback before publication.
Partial GPU scientific output is never published.
## TSan policy
For project concurrency changes, use a dedicated TSan build that matches the
supported proof boundary.
The retained global maintenance matrix used:
```text
GCC/G++
Vulkan disabled
selected concurrent targets
deterministic repetitions
```
and completed the retained 14/14 target matrix plus 220 repetitions.
The narrow suppression file is:
```text
tests/tsan-opencv.supp
```
It covers external non-instrumented OpenCV/TBB objects only.
It must not suppress Lardon3D frames.
Therefore the correct retained claim is not "TSan proves all concurrency".
It is:
```text
portable project concurrency matrix passed under the documented qualification
external OpenCV/TBB reports are narrowly qualified
Vulkan concurrency has a separate validation boundary
```
## What TSan does not prove
TSan is useful for instrumented conflicting memory access and some
synchronization misuse.
It does not prove absence of:
- deadlock;
- lost wakeup caused by incorrect predicate design;
- lifetime bugs outside the exercised paths;
- races hidden inside non-instrumented external libraries;
- Vulkan driver/runtime correctness;
- scientific determinism.
Lock-order review, ownership reasoning and deterministic tests remain required.
## Sanitizer command policy
Do not encode fixed `-j8` as canonical validation.
Example configuration:
```sh
CC=gcc CXX=g++ meson setup build-tsan -Db_sanitize=thread -Db_lundef=false -Dvulkan_orb=disabled
meson compile -C build-tsan
meson test -C build-tsan --print-errorlogs
```
Use host-aware compile/test parallelism unless the proof itself requires
serialization.
Do not repeatedly wipe an unchanged TSan tree.
## Concurrency review checklist
Before closing a concurrency-sensitive change, verify:
- ncurses remains main-thread-only;
- every shared mutable field has an explicit synchronization owner;
- condition predicates are checked in loops;
- lock order remains consistent;
- no Task uses forced asynchronous cancellation;
- Queue callbacks have an active reservation;
- internal participants stay within the admitted contract;
- all children join on every exit path;
- owner-only publication remains ordered where required;
- project-close ordering prevents DB use-after-close;
- SSD lease ownership remains exact;
- Task userdata outlives terminal notification;
- appropriate deterministic concurrency tests pass;
- portable TSan qualification is preserved;
- Vulkan validation is reported separately;
- ASan/UBSan is run when the change also affects lifetime/memory.
## Current retained evidence
The canonical global-maintenance record is:
```text
docs/architecture/global_maintenance_audit.md
GLOBAL_MAINTENANCE_AUDIT=PASS/FROZEN
```
The current A6000 checkpoint is later:
```text
real-a6000-pre-sfm-2026-09-02
REAL_A6000_PRE_SFM=PASS/FROZEN
```
The later A6000 proof exercised current bounded parallel paths through selected
Feature batch, Candidate, Matcher, Geometric Verifier v3 and Tracks without
changing the historical TSan qualification.
Historical evidence remains historical; new changes require validation scoped
to their actual concurrency surface.
## Summary
```text
NCURSES_OWNER=MAIN_THREAD_ONLY
ACTIVE_HEAVY_QUEUE_CALLBACKS=1
TASK_CANCELLATION=COOPERATIVE
INTERNAL_PARALLELISM=BOUNDED
PER_ITEM_ATOMICITY_REQUIRES_CROSS_ITEM_SERIALISM=NO
OWNER_ONLY_PUBLICATION_REQUIRES_SERIAL_PREPARATION=NO
TSAN_PORTABLE_PROJECT_MATRIX=QUALIFIED_PASS
TSAN_EXTERNAL_OPENCV_TBB=QUALIFIED
VULKAN_CONCURRENCY_VALIDATION=SEPARATE
NO_FIXED_GLOBAL_J8=YES
NO_REPEATED_UNCHANGED_WIPE=YES
RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT
SERIALISM_REQUIRES_PROOF=CANONICAL
```