render: the M3 OFX host — one oak-worker --ofx-host process for every plugin job
docs/zh/plans/render-pipeline-threads.md M3 (design 3.2): OpenFX crash isolation moves from "every worker hosts plugins" to a single dedicated host process, served over NDJSON + shared memory. - oak-worker --ofx-host mode (src/ofx_host.rs): loads every plugin once, resolves jobs by the cross-process-stable OFX identifier, and renders through the same in-process executor the workers used to install. - oak-render/ofxhost.rs: the single-host client. The render manager creates and installs it for the Pipeline backend (lazy spawn on the first plugin job); eval::process_plugin_job prefers it and falls back to the in-process executor otherwise, so the process backend keeps its current behavior until M4. - Data plane: input/output FrameSlotPool pairs (the handshake's input_* fields are used for the first time). Named clips and the source frame are written to input slots after the explicit CPU readback; the plugin output returns through an output slot. Pool size/capacity grow by a host restart when a job needs more (safe: submissions are serialized and one job is in flight). - Crash loop: reader EOF fails the in-flight submit, which respawns the host and re-posts the same job (frames are read back once); after three consecutive crashes the client is permanently dead and the evaluator falls back to a purple frame. The dead child is reaped immediately, and a submit mutex enforces the one-job-in-flight contract. - Progress/cancel: the host flushes plugin_progress immediately (live progress), and reads stdin on its own thread so plugin_cancel takes effect mid-render at the plugin's next progressUpdate; the sticky flag resets at progressStart and request_plugin_cancel_all broadcasts to both the worker pool and the host. - JobSpec::Plugin / PluginJobPayload carry the plugin type_id (stable across processes); `--ofx-crash-once` / `--ofx-crash-always` are the deterministic crash hooks, matching the worker's env hooks. - Tests: wire round-trips; host unit tests (crash budget, cancel-flag reset through the factory, source mapping); oak-worker integration tests against the real host + bundled test plugin (render + progress, crash respawn and re-post, three-crash give-up, mid-render cancel on the new slow variant, concurrent submits); eval's purple fallback.
This commit is contained in:
@@ -72,3 +72,24 @@ pub mod property;
|
||||
pub mod render;
|
||||
pub mod render_driver;
|
||||
pub mod suites;
|
||||
|
||||
/// The minimal test plugin shared library built by this crate's build
|
||||
/// script (`$OUT_DIR/oak_test_plugin.{so,dylib}`), for integration tests
|
||||
/// that exercise the real plugin path — including the single OFX host
|
||||
/// process (M3) — without a system plugin. The build script always
|
||||
/// compiles it, so this is `None` only if the build directory was
|
||||
/// tampered with (tests treat that as a hard failure, not a skip).
|
||||
pub fn bundled_test_plugin() -> Option<std::path::PathBuf> {
|
||||
let dir = std::path::Path::new(env!("OUT_DIR"));
|
||||
for name in [
|
||||
"oak_test_plugin.dylib",
|
||||
"oak_test_plugin.so",
|
||||
"oak_test_plugin.dll",
|
||||
] {
|
||||
let path = dir.join(name);
|
||||
if path.exists() {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -837,6 +837,7 @@ fn execute_plugin_job(
|
||||
|
||||
let oak_render::eval::JobSpec::Plugin {
|
||||
instance,
|
||||
type_id: _,
|
||||
time,
|
||||
effect_input_id,
|
||||
inputs,
|
||||
@@ -1167,6 +1168,7 @@ mod tests {
|
||||
}
|
||||
let spec = oak_render::eval::JobSpec::Plugin {
|
||||
instance,
|
||||
type_id: "org.oak.test-plugin".to_string(),
|
||||
time: 0.0,
|
||||
effect_input_id: Some("Source".to_string()),
|
||||
inputs: Vec::new(),
|
||||
|
||||
Reference in New Issue
Block a user