Skip to content

plugin-hostd-probe: run an LV2 plugin through lv2_measure's passes (#76) - #90

Merged
linuxnow merged 4 commits into
mainfrom
fix/76-lv2-child
Oct 18, 2026
Merged

linuxnow merged 4 commits into
mainfrom
fix/76-lv2-child

Conversation

@linuxnow

@linuxnow linuxnow commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Refs #76. This is the quick pass; the deep lifecycle and soak passes are still open, see below.

plugin-hostd-probe run lv2:<uri> now measures an LV2 plugin the way plugin-qualify's LV2 hosting pass does today.

  • The child. plugin-qualify's lv2_measure, looked up at run time in /usr/libexec/plugin-qualify, or the program --child names. plugin-qualify is not a package dependency, because it builds against hostd and a dependency back would be a cycle.
  • The passes. The runner drives them, each as a child of its own under the same limits and one wall clock.
    • Pin pass: --pin <uri>, nothing instantiated. Its integrity block becomes binary: the library is the binary, every bundle file is listed, and the layout pin is copied.
    • Measurement: <uri> <highest of --rates> <--quantum> 30 3 with LD_PRELOAD=rt_interpose.so (found beside the child) and OMX_STIM=noise. These are plugin-qualify's arguments today. The 30 and 3 are declared in protocol.h as PHD_PROBE_LV2_LIFECYCLES and PHD_PROBE_LV2_REPEATS.
  • The translation.
    • The measurement line becomes one grid point: the rate, the quantum, and rtSafety (interposer, swept, blocks, allocations, locks, syscalls, events as "kind module symbol").
    • It also fills behaviour: lifecycle, threads, required features.
    • Steps: hash, instantiate, rt-safety, lifecycle.
  • Exit codes. A plugin no bundle holds exits 3 with no document. A plugin of which nothing could be pinned exits 2 with no document. The refusal is its code on stderr, <plugin>: probe.unpinnable: <why> (ruling 2026-10-18), because the schema requires a binary sha256 and the runner never invents one.
  • Refactor. run_child takes its argv and the deadline. How a child ended is classified in one function used by every pass. The child's log is appended to across passes.

Tests. The LV2 fixture gets a stress plugin that breaks on cue, as the CLAP stress plugin does. tests/lv2_probe_child.c is a fake lv2_measure that loads it with dlopen, with no lilv. Each test fails on main:

  • a measured run (validates; pins the files; one point at the highest rate; measured under the interposer);
  • a crash that keeps the pin and names SIGABRT;
  • a hang ended at the wall clock;
  • a process left in its own session that gets killed;
  • a plugin no bundle holds (exit 3).

When the real /usr/libexec/plugin-qualify/lv2_measure is installed, a further test measures the gain fixture with it and checks that the interposer ran. That test is skipped elsewhere. Seven sabotage arms each turn their test red: measurement skipped, not-found ignored, wall clock ignored, measurement's end not the run's, no interposer, no noise stimulus, refusal without its code. The CI test job now installs lv2-devel for the fixture and the fake child.

Open, so this does not close #76.

Local checks in the Fedora worker image: test-pin, test-format, test-daemon, check-generated, check-schema, test-probe-schema, test-probe (the real lv2_measure test included), test-consumer, test-perturbation, check-readme, check-no-pins, and the six new sabotage arms.

The LV2 fixture gets "stress": its gain plugin, which breaks on cue (STRESS_FAULT crash, hang or fork), as
tests/stress_plugin.c does for CLAP.

tests/lv2_probe_child.c takes plugin-qualify's lv2_measure arguments and prints lines of the same shape:
`--pin <uri>` gives the integrity block of the bundle on LV2_PATH, and `<uri> rate block cycles repeats`
gives one measurement line. It needs no lilv and no interposer. It loads the plugin's library with dlopen,
so the plugin's own code runs in it. It refuses a measurement without OMX_STIM=noise, and it reports the
interposer as loaded when LD_PRELOAD names rt_interpose.so. tests/rt_interpose.so is an empty library of
that name, built beside it.

The fixture and the child need the LV2 headers, so the CI test job installs lv2-devel.
`plugin-hostd-probe run lv2:<uri>` measures an LV2 plugin the way plugin-qualify's LV2 hosting pass does
today, so stage 1 of the probe output covers LV2 as well as CLAP. The child is plugin-qualify's lv2_measure,
looked up at run time in /usr/libexec/plugin-qualify, or the one --child names. plugin-qualify builds
against this package, so it is not a dependency of it. The runner drives the passes itself, each a child of
its own under the same limits and the same wall clock:

- the pin: `lv2_measure --pin <uri>`, nothing instantiated. The bundle's files and the layout pin become the
  document's binary: the library is the binary, and every file is listed. A plugin no bundle holds exits 3
  with no document. A plugin of which nothing could be pinned exits 2 with no document, because the
  document has no binary to name.
- the measurement: `lv2_measure <uri> <highest rate> <quantum> 30 3`, under rt_interpose.so beside the
  child and OMX_STIM=noise, as plugin-qualify runs it (PHD_PROBE_LV2_LIFECYCLES and PHD_PROBE_LV2_REPEATS in
  protocol.h). Its line becomes the rate's grid point with rtSafety, and the behaviour block with
  lifecycle, threads and required features. Nothing is judged.

run_child now takes its argv and the deadline, and the classification of how a child ended is one function,
used for each pass. The child's log is appended to across passes.

The tests run the stress fixture through the fake child: a measured run, a crash that keeps the pin, a hang
ended at the wall clock, a process left in its own session that is killed, and a plugin no bundle holds.
When plugin-qualify's lv2_measure is installed, it measures the gain fixture under its interposer. Six
sabotage arms each turn a test red.

Refs #76
…ed by its code

With no pinned file the document would have no binary to name, so there is no document (exit 2), and the
refusal now starts with its outcome code on stderr: "<plugin>: probe.unpinnable: <why>", or the code of
how the pin pass ended. The fake LV2 child gets an unpinnable variant of the stress plugin; the test fails
before this change and has a sabotage arm. The README says that version 1 runs the quick measurement only.

Refs #76
@linuxnow
linuxnow merged commit a210616 into main Oct 18, 2026
16 checks passed
@linuxnow
linuxnow deleted the fix/76-lv2-child branch October 18, 2026 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Probe runner: the LV2 child, so lv2_measure runs under the runner like clap-qualify

1 participant