[{"data":1,"prerenderedAt":30},["ShallowReactive",2],{"note-chains-plugins-in-series-in-one-worker-en":3},{"slug":4,"order":5,"title":6,"summary":7,"measured":8,"topic":9,"toc":10,"html":29},"chains-plugins-in-series-in-one-worker",40,"\"Chains: plugins in series in one worker, and a crashed member quarantined\"","A chain runs a strip's plugins in order inside one worker process, so they wake once per cycle instead of once each; when a member crashes, it alone is quarantined and the rest of the chain goes on.","plugin-hostd with omx-clap-host, October 2026.","host",[11,14,17,20,23,26],{"id":12,"title":13},"why","Why",{"id":15,"title":16},"the-verbs","The verbs",{"id":18,"title":19},"which-members-can-share-a-chain","Which members can share a chain",{"id":21,"title":22},"what-the-worker-is-told","What the worker is told",{"id":24,"title":25},"a-crash","A crash",{"id":27,"title":28},"lv2-chains-under-require-pins","LV2 chains under require_pins","\u003Ch2 id=\"why\">Why\u003C\u002Fh2>\n\u003Cp>omx-clap-host gives every plugin its own JACK client, and every client is a node of the graph with its own wake-up per\ncycle. Most of a cycle's time goes on those wake-ups, not on the plugins (see\n\u003Ca href=\"\u002Fnotes\u002Fone-jack-client-for-a-chain\">One JACK client for a chain\u003C\u002Fa>). A strip that runs four plugins in series wakes four\nnodes. omx-clap-host can run a chain of plugins in one client and call them in order inside one process callback; it must\nbe told which instances form a chain and in what order, and the daemon must put them in one worker.\u003C\u002Fp>\n\u003Cp>A chain shares one process, so one member's crash would take the others down. plugin-hostd exists to isolate crashes: the\nmember that crashed leaves the chain and the rest go on.\u003C\u002Fp>\n\u003Ch2 id=\"the-verbs\">The verbs\u003C\u002Fh2>\n\u003Cpre>\u003Ccode>chain &lt;chain&gt; &lt;instance&gt; [&lt;instance&gt; ...]                resp 0\nchain_clear &lt;chain&gt;                                      resp 0\nchain_get &lt;chain&gt;                                        resp 0 &lt;worker&gt; &lt;declared&gt; &lt;running&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>chain\u003C\u002Fcode> declares the members in order; they must be instances an \u003Ccode>add\u003C\u002Fcode> made. The chain gets one worker, of place\n\u003Ccode>chain:&lt;chain&gt;\u003C\u002Fcode>. Each member leaves the worker it was in: its state is saved, its \u003Ccode>remove\u003C\u002Fcode> goes to that worker, and the new\nworker replays it like a respawn: the add, the state, the verb ledger and the connections, in that order. Declaring a chain\nagain gives it the new order; a member left out goes back to a worker of its own. \u003Ccode>chain_clear\u003C\u002Fcode> gives every member a\nworker of its own and forgets the chain.\u003C\u002Fp>\n\u003Cp>\u003Ccode>chain_get\u003C\u002Fcode> reads a chain back: its worker, its members in the declared order, and the members its worker runs now. A\nquarantined member is in the declared list and not in the running one. It is the controller's check that an add, a move\nor a remove landed.\u003C\u002Fp>\n\u003Cp>A chain declared again while its worker is up keeps that worker, and the members that stay are not restarted. A member\nleft out goes to a worker of its own; a new member leaves its old worker and joins the running one with its add, its state\nalone, its verbs and its connections; and the worker is sent the new order. A move, a new order with no member joining or\nleaving, sends the order and nothing else. The chain is formed again from scratch only when its worker is not up, when a\nmember is split out or rejoining, or when a joining LV2 member needs a bundle the running worker's world was not built\nwith.\u003C\u002Fp>\n\u003Ch2 id=\"which-members-can-share-a-chain\">Which members can share a chain\u003C\u002Fh2>\n\u003Cp>\u003Ccode>clap\u003C\u002Fcode> and \u003Ccode>lv2-clap\u003C\u002Fcode> are two rows of one program, omx-clap-host, and both run chains in one client, so a CLAP member and\nan LV2 member (through the LV2 adapter) form one chain. Members of two programs (LV2 on mod-host with CLAP) are refused\nwith \u003Ccode>CHAIN_MIXED_FORMAT\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>A refusal changes nothing:\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>code\u003C\u002Fth>\n\u003Cth>when\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>CHAIN_UNKNOWN_INSTANCE\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>a member that no \u003Ccode>add\u003C\u002Fcode> made\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>CHAIN_DUPLICATE\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the same instance twice in the list\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>CHAIN_INSTANCE_BUSY\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>a member already in another chain\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>CHAIN_MIXED_FORMAT\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>members that one worker program does not run together\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>PLACEMENT_INVALID\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>a member whose plugin may not share a worker while \u003Ccode>require_pins\u003C\u002Fcode> is on\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>NO_SUCH_CHAIN\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>chain_clear\u003C\u002Fcode> of a chain no \u003Ccode>chain\u003C\u002Fcode> declared\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>A chain of one is valid: one isolated plugin is a chain like any other.\u003C\u002Fp>\n\u003Ch2 id=\"what-the-worker-is-told\">What the worker is told\u003C\u002Fh2>\n\u003Cp>A worker that runs chains in one client is sent the same \u003Ccode>chain\u003C\u002Fcode> line after the adds of its replay. A worker that does not\n(mod-host) holds the members in one process, and its connections give the order. Bypass, parameters and every instance\nverb still name one instance by its own number; the daemon routes them to the chain's worker.\u003C\u002Fp>\n\u003Ch2 id=\"a-crash\">A crash\u003C\u002Fh2>\n\u003Cp>A chain's worker is a pool to the supervisor, which attributes a death the same way:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>When the death names a culprit (the verb in flight, or a recent suspect), the culprit is quarantined\n(\u003Ccode>instance_quarantined &lt;instance&gt; &lt;worker&gt;\u003C\u002Fcode>) and moved to a worker of its own. The chain's worker is respawned with the\nother members in their order, and \u003Ccode>chain_formed &lt;chain&gt; &lt;worker&gt; &lt;instance&gt;,...\u003C\u002Fcode> names the order that runs now.\u003C\u002Fli>\n\u003Cli>When it names none, the members are split, each into a worker of its own. The member that dies again on its own is the\nculprit: it is quarantined, and the others rejoin the chain in their order.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>A quarantined member keeps its place in the declared order. \u003Ccode>quarantine_clear\u003C\u002Fcode> puts it back, and the chain's worker is\nformed again with every member that is not quarantined. \u003Ccode>remove\u003C\u002Fcode> of a member takes it out of the chain; a chain whose last\nmember is removed is forgotten.\u003C\u002Fp>\n\u003Ch2 id=\"lv2-chains-under-require-pins\">LV2 chains under require_pins\u003C\u002Fh2>\n\u003Cp>An LV2 pin holds only in a world narrowed to the plugin's own bundle. A chain's worker therefore gets a world narrowed to\nthe union of its members' bundles, so that each member's pin still holds in it. A member whose bundle is not found leaves\nthe world unbuilt rather than widening it, and \u003Ccode>chain\u003C\u002Fcode> refuses that chain with \u003Ccode>PLACEMENT_INVALID\u003C\u002Fcode> before anything moves.\u003C\u002Fp>\n\u003Cp>A world narrowed to one member's bundle is the failure to avoid: in a bench of eight LV2 plugins chained through the\nadapter, a member in an \u003Ccode>LV2_PATH\u003C\u002Fcode> of a single bundle answered \u003Ccode>resp -101\u003C\u002Fcode> (no plugin of that uri). A test forms a chain of\ntwo LV2 members, checks that the worker's \u003Ccode>LV2_PATH\u003C\u002Fcode> holds exactly the two bundles, and a sabotage that narrows the world to\nthe first bundle must turn it red.\u003C\u002Fp>\n",1793954995813]