diff --git a/MXLFABRICS_PLAN.md b/MXLFABRICS_PLAN.md new file mode 100644 index 0000000..86e599c --- /dev/null +++ b/MXLFABRICS_PLAN.md @@ -0,0 +1,224 @@ +# MXL Fabrics ingress plan + +## Goal + +Receive remote MXL audio and video through `go-mxl/fabrics`, place the received data into local MXL storage, and feed it into the existing player without changing playback, retry, session, synchronization, playlist, renderer, or audio-output semantics. + +Start with the SHM provider. TCP, verbs, and EFA follow after the lifecycle and media paths are stable. + +## Correct architecture + +`go-mxl/fabrics` is not a second reader backend. A Fabrics target receives remote transfers into memory owned by a local `mxl.Writer`: + +```text +remote fabrics.Initiator + | + | remote transfer + v + fabrics.Target + | + | writes into a local mxl.Writer ring + v + local MXL flow + | + v + VideoFrame / AudioSamples -> playback workers -> output +``` + +Consequences: + +- the local MXL media path remains the reference implementation; +- Fabrics is an ingress/transport concern, not a playback backend; +- playback and playlist code must not know about providers or endpoints; +- each remotely transferred flow requires its own target; +- target completions must be matched to the exact local grain/sample range; +- do not add a blind polling loop that could return stale or overwritten media. + +## Confirmed API facts + +The project pins `github.com/qvest-digital/go-mxl v1.1.0-rc.1`. It includes `github.com/qvest-digital/go-mxl/fabrics` and providers `shm`, `tcp`, `verbs`, `efa`, and `any`. + +The receive-side flow is: + +1. create a local `mxl.Instance`; +2. create an `mxl.Writer` from the flow definition; +3. create a `fabrics.Instance` from the local MXL instance; +4. create a `fabrics.Target`; +5. call `Target.Setup` with provider, node/service, writer, and options; +6. publish the returned `TargetInfo` to the remote initiator; +7. consume completions with `ReadGrain` or `ReadSamples`. + +`TargetInfo` is control-plane data. Initially serialize it to a file for the known sender; automatic discovery is out of scope. + +`fabrics.ErrNotReady` means no completion is ready before the timeout. `fabrics.ErrInterrupted` can be caused by Go runtime signals and means retry the read operation. Neither is a feed failure by itself. + +## Current native-library state + +The installed pkg-config metadata reports `libmxl 1.2.0.0` and `libmxl-fabrics 1.2.0.0`. The pinned Go module records native MXL `v1.1.0-rc1` as its corresponding version. Resolving `libmxl-fabrics` currently fails because pkg-config cannot find `libfabric`. + +This does not prove 1.2 is incompatible. Compare the stack with the user's known-working C++ sender/receiver and choose one tested version set. Do not conceal ABI/API mismatches with casts or copied declarations. + +## Configuration boundary + +Fabrics code should live in `internal/adapter/mxlfabrics`. A provisional configuration is: + +```go +type IngressConfig struct { + Provider fabrics.Provider + Node string + Service string + Options json.RawMessage + TargetInfoPath string + Flow mxl.FlowDefinition +} +``` + +Use the exact flow-definition type exposed by the selected library version. Video and audio have independent configurations and targets. They may use different remote endpoints/providers while landing in the same local MXL domain. + +Do not add `--backend local|fabrics`. The useful distinction is whether a feed is already local or needs Fabrics ingress. Choose final per-feed CLI names after the configuration spike. + +## Implementation stages + +### Stage 0 — Align and prove the native stack + +- Record exact versions/commits of `libfabric`, `libmxl`, `libmxl-fabrics`, and `go-mxl` used by the working C++ pair. +- Decide whether to retain Go/native 1.1 RC or move the complete set together. +- Make all pkg-config and dynamic-linker checks pass. +- Run the known C++ pair, then the matching upstream Go SHM target/initiator examples. +- Enumerate interfaces/providers through the Go API. +- Preserve the current player test baseline. + +```sh +pkg-config --modversion libfabric libmxl libmxl-fabrics +pkg-config --cflags --libs libfabric libmxl libmxl-fabrics +ldd /usr/local/lib/libmxl-fabrics.so +go test ./... +``` + +Acceptance: one documented compatible version set; repeatable Go SHM transfer; clean cancellation/shutdown; no player production changes. + +### Stage 1 — Minimal SHM ingress spike + +- Create a local MXL instance and writer from a supplied flow definition. +- Set up one SHM target and serialize its `TargetInfo` to an explicit file. +- Receive video grains and audio sample ranges from the known sender in isolated experiments. +- Verify completion indices against the corresponding local ring contents. +- Document buffer lifetime, overwrite behavior, and sender-absent behavior. + +Acceptance: video and audio transfers work independently; no busy loop; shutdown promptly releases resources. + +### Stage 2 — Define the ingress lifecycle + +Create a narrow adapter owned by the composition root. It must: + +- validate before allocating native resources; +- create the writer before target setup and publish info only after setup; +- classify timeouts, interruptions, cancellation, and terminal errors; +- remain context-cancellable although native reads use timeouts; +- allow only one completion reader per target; +- close every resource exactly once and unwind partial setup safely. + +Confirm the precise close order against the chosen version. Expected order: + +1. cancel and join the completion loop; +2. close target; +3. close returned `TargetInfo`; +4. close the Fabrics instance after all targets; +5. close local readers/writers while respecting the target's writer reference; +6. close the local MXL instance last. + +Acceptance: allocation-failure tests, prompt idle/active cancellation, race-safe repeated start/stop, and no resource leaks. + +### Stage 3 — Video ingress + +- Receive completion indices and obtain the exact completed local grain. +- Map metadata/payload into `playback.VideoFrame`. +- Preserve borrowed-buffer lifetime, or make one deliberate bounded copy if required for safety. +- Reject unsupported formats explicitly. +- Track received, delivered, overwritten, and malformed grains. + +Never independently poll for the newest local frame after a completion; it may be a different index. + +Acceptance: correct V210 playback; monotonic indices except documented restart; stable 25/50/59.94/high-rate playback; clean disappearance/restoration. + +### Stage 4 — Audio ingress + +- Use `ReadSamples` completion ranges and read exactly the reported head/count. +- Map rate, channels, format, label, UUID, and index into the audio contract. +- Preserve SDL backpressure and queue policy. +- Track samples, discontinuities, overruns, and queue depth. + +Acceptance: correct mono/stereo/multichannel and common rates; no corruption/drift; no duplicated/skipped ranges; no stale queued audio after stop/reconnect. + +### Stage 5 — Independent and synchronized A/V + +Independent A/V uses two independent targets/lifecycles; one failure must not stop the other. + +For synchronized A/V: + +- land both flows in a compatible local MXL domain; +- retain native local-MXL synchronization where supported; +- coordinate completion availability so sync never consumes data not yet received; +- verify different endpoints/providers and fractional frame rates such as 59.94. + +Fabrics transports media; it is not a reason to invent manual synchronization. If native synchronization cannot safely use received rings, write a separate sync design first. + +Acceptance: failure isolation when independent; one retry lifecycle when synchronized; runtime sync toggle retains current semantics; long playback stays aligned. + +### Stage 6 — Player integration + +- Add per-feed local/Fabrics transport configuration. +- Construct ingress outside `internal/playback` and connect it to existing workers/slots. +- Preserve stop, resume, replace, remove, sync toggle, retry, and playlist behavior. +- Keep playlist entries provider-neutral where possible. +- Add provider, endpoint, ingress state, completion age, and counters to stats. + +Acceptance: local behavior is unchanged; Fabrics supports video-only, audio-only, independent A/V, and sync A/V; bad config fails before native goroutines; feeds remain independently controllable. + +### Stage 7 — Provider rollout + +Enable and validate in order: + +1. SHM; +2. TCP; +3. verbs; +4. EFA if deployment requires it. + +Provider selection should affect only Fabrics interface configuration and deployment prerequisites. Playback controllers must not branch on provider names. + +### Stage 8 — Playlist and resilience validation + +Test empty startup; audio/video only; independent and synchronized A/V; either sender disappearing/restoring; finite/infinite retry; stop/resume/remove/replace; sync toggling during playback/backoff; `wait`/`next` playlists; timed looping/manual navigation; 59.94/high-rate video; and repeated long-running startup/shutdown. + +Record CPU, memory, received/displayed FPS, completion delta, dropped/overwritten media, audio queue, reconnect latency, and native resource counts. + +## Test layers + +1. Unit tests for configuration, error classification, and lifecycle transitions. +2. Adapter tests with injected wrappers around native allocations and reads. +3. Real SHM initiator/target integration tests. +4. Player smoke tests for all playback combinations. +5. Race and repeated lifecycle tests. +6. Provider-specific TCP/verbs/EFA live tests. + +Native/live tests must be explicit integration tests and skip with a useful reason when prerequisites are absent. + +## Out of scope for the first pass + +- player as Fabrics initiator/sender; +- automatic endpoint or TargetInfo discovery; +- changing provider on an active target; +- manual A/V sync without a reviewed design; +- new media formats bundled into transport work; +- provider-specific logic in playback, playlist, renderer, or GUI; +- unsafe workarounds for native version mismatches. + +## First action tomorrow + +1. Build/install the exact known-working native stack. +2. Record every native version/commit and the C++ reference commands. +3. Make all pkg-config checks pass. +4. Run the C++ sender/receiver baseline. +5. Run the matching upstream Go SHM target/initiator example. +6. Capture its flow definition and `TargetInfo` exchange. +7. Start Stage 1 only when transfer and clean shutdown are repeatable. diff --git a/cmd/mxl-player/main.go b/cmd/mxl-player/main.go index 92eea0a..42a261f 100644 --- a/cmd/mxl-player/main.go +++ b/cmd/mxl-player/main.go @@ -97,12 +97,6 @@ func checkMXLargs(args appArgs) { } func main() { - // timelapse video: 5fbec3b1-1b0f-417d-9059-8b94a47197ed - // timelapse audio: 5fbec3b1-1b0f-417d-9059-8b94a47197ec - // f1 video: 5fbec3b1-1b0f-417d-9059-8b94a47197ef - // f1 audio: 5fbec3b1-1b0f-417d-9059-8b94a47197eb - // sync video: 2618979d-76a5-45e0-83cb-0f192978d1cd - // sync audio: 9d2a041b-01cf-4ee4-bffa-188fe093c99b var args appArgs flagSet := pflag.NewFlagSet(APP_NAME, pflag.ContinueOnError) flagSet.SortFlags = false