memlnaut-nisps/vcv/src
monkey-w1n5t0n 1a78ed9597 fix(vcv): close the audio-thread race and remove JSON from process()
Phase 2 (L34, L35). Both findings' line citations were accurate this time.

- L34 data race: process() (Rack's audio thread) called
  imlShadow.get_example_features()/get_example_labels() directly on the WORKER
  thread's private engine — which the file's own THREADING INVARIANT comment
  says only the worker may touch — while workerLoop() concurrently
  clear/refills those same std::vector<std::vector<float>> members via
  load_examples(), train_(), randomise_weights and clear_dataset. Unsynchronised
  reader/writer on a non-atomic vector: undefined behaviour.
  Fix extends the staged handoff the file ALREADY uses for pendingWeights
  rather than adding a second mutex: the worker deep-copies features/labels
  into pendingFeatures/pendingLabels at the same instant it copies
  pendingWeights, immediately before weightsPending.store(true), and the flag is
  now released only after the whole batch is consumed — closing an early-release
  window the old code had. process() no longer references imlShadow at all
  (verified: the only surviving mention is a comment).
- L35: process() ran full jansson serialize on every weight-swap OSC push and
  full json_loads + dataFromJson on incoming OSC state — heap-heavy tree work
  on the audio thread. The plan said to move it to the worker. It is DELETED
  instead: reading the actual transport shows both directions talk to nobody —
  osc-client.ts only ever sends {params|input|feedback}, and bridge.ts has no
  state/weights case and explicitly drops other addresses. Relocating
  heap-heavy work to serve a confirmed-zero consumer is complexity without a
  requirement; removing the cause is the smaller coherent design.
  dataToJson/dataFromJson are UNTOUCHED — they remain the live consumers for
  Rack patch save/load and the .nisps preset menu, both off the audio thread.

Neither is empirically reproduced: a real race needs a live Rack engine under
TSan, which is not available here. Justified by reading, and verified by
`cd vcv && make -j4` (clean) plus the host suite including
test_vcv_iml_parity.cpp, which pins iml.hpp bit-exactly against the core MLP —
iml.hpp was not modified, and parity holds.

Known remaining, pre-existing and out of scope: process() still takes a brief
lock_guard on feedbackMutex to copy a small staged struct, and several config
fields (slewMs, oscPort, output/input range flags) are written by the UI thread
without atomics.
2026-07-21 13:23:11 +02:00
..
iml.hpp feat(vcv): reunify module onto core MLP — thin iml.hpp adapter (P6) 2026-07-18 13:01:28 +02:00
LedRing.hpp feat(vcv): evolve module to 8x16 + LED rings + token palette + WS-OSC bridge 2026-06-28 04:14:30 +02:00
MEMLNaut.cpp fix(vcv): close the audio-thread race and remove JSON from process() 2026-07-21 13:23:11 +02:00
osc_server.hpp fix(vcv): close the audio-thread race and remove JSON from process() 2026-07-21 13:23:11 +02:00
palette.hpp feat(vcv): evolve module to 8x16 + LED rings + token palette + WS-OSC bridge 2026-06-28 04:14:30 +02:00
plugin.cpp feat(nisps-core,vcv): complete Phase 0 + Phase 1 2026-03-28 00:36:46 +02:00
plugin.hpp feat(nisps-core,vcv): complete Phase 0 + Phase 1 2026-03-28 00:36:46 +02:00