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:
2026-09-12 23:10:43 +08:00
parent 4337559ed0
commit fec6e9dba7
14 changed files with 2054 additions and 27 deletions
+100 -1
View File
@@ -37,6 +37,8 @@
#include <stdarg.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
/* Windows 上插件 DLL 带自己的 CRT 环境块:宿主 Rust 侧的
@@ -373,6 +375,90 @@ static OfxStatus actionRender(OfxImageEffectHandle inst, OfxPropertySetHandle in
return kOfxStatOK;
}
/* ---------- 慢速变体(M3):取消 e2e 测试 ---------- */
/* progressUpdate 之间的睡眠(宿主取消在渲染中置位,下一次
* progressUpdate 返回非 OK,插件立即中止)。 */
static void slow_sleep_ms(int ms)
{
#ifdef _WIN32
Sleep((DWORD)ms);
#else
usleep((useconds_t)ms * 1000);
#endif
}
/* 100 次 progressUpdate x 20ms(约 2 秒):把输出填成常量 0.25。
* 任一次 progressUpdate 返回非 OK(宿主取消)→ kOfxStatFailed。 */
static OfxStatus actionRenderSlow(OfxImageEffectHandle inst, OfxPropertySetHandle inArgs)
{
double time = 0.0;
propGetDouble(inArgs, kOfxPropTime, 0, &time);
OfxImageClipHandle clip = NULL;
OfxPropertySetHandle clipProps = NULL;
OfxStatus st = g_imageEffectSuite->clipGetHandle(inst, "Output", &clip, &clipProps);
if (st != kOfxStatOK)
return st;
OfxPropertySetHandle image = NULL;
st = g_imageEffectSuite->clipGetImage(clip, time, NULL, &image);
if (st != kOfxStatOK)
return st;
void *data = NULL;
int rowBytes = 0;
int bounds[4] = { 0, 0, 0, 0 };
propGetPointer(image, kOfxImagePropData, 0, &data);
propGetInt(image, kOfxImagePropRowBytes, 0, &rowBytes);
propGetIntN(image, kOfxImagePropBounds, 4, bounds);
if (g_progressSuite) {
g_progressSuite->progressStart((OfxImageEffectHandle)inst, "slow-render");
for (int i = 0; i < 100; i++) {
OfxStatus ps = g_progressSuite->progressUpdate((OfxImageEffectHandle)inst,
(double)(i + 1) / 100.0);
if (ps != kOfxStatOK) {
g_progressSuite->progressEnd((OfxImageEffectHandle)inst);
return kOfxStatFailed;
}
slow_sleep_ms(20);
}
}
int w = bounds[2] - bounds[0];
int h = bounds[3] - bounds[1];
if (data && w > 0 && h > 0) {
for (int y = 0; y < h; y++) {
float *row = (float *)((char *)data + (size_t)y * (size_t)rowBytes);
for (int x = 0; x < w; x++) {
row[x * 4 + 0] = 0.25f;
row[x * 4 + 1] = 0.25f;
row[x * 4 + 2] = 0.25f;
row[x * 4 + 3] = 1.0f;
}
}
}
g_imageEffectSuite->clipReleaseImage(image);
if (g_progressSuite) {
g_progressSuite->progressEnd((OfxImageEffectHandle)inst);
}
return kOfxStatOK;
}
/* 慢速变体的效果入口:render 走慢速路径,其余 action 委托基础实现。 */
static OfxStatus mainEntry(const char *action, const void *handle,
OfxPropertySetHandle inArgs, OfxPropertySetHandle outArgs);
static OfxStatus mainEntrySlow(const char *action, const void *handle,
OfxPropertySetHandle inArgs, OfxPropertySetHandle outArgs)
{
if (strcmp(action, kOfxImageEffectActionRender) == 0) {
return actionRenderSlow((OfxImageEffectHandle)handle, inArgs);
}
return mainEntry(action, handle, inArgs, outArgs);
}
/* ---------- ofxColour(M11 §4):GetOutputColourspace ---------- */
static OfxStatus actionGetOutputColourspace(OfxPropertySetHandle inArgs,
@@ -827,9 +913,20 @@ static const OfxPlugin test_plugin_interact = {
/* mainEntry */ mainEntryInteractEffect,
};
/* M3 取消 e2e:慢速 filter(progressUpdate x20ms 循环,取消即中止)。 */
static const OfxPlugin test_plugin_slow = {
/* pluginApi */ kOfxImageEffectPluginApi,
/* apiVersion */ kOfxImageEffectPluginApiVersion,
/* pluginIdentifier */ "org.oak.test-plugin.slow",
/* pluginVersionMajor */ 1,
/* pluginVersionMinor */ 0,
/* setHost */ setHost,
/* mainEntry */ mainEntrySlow,
};
OfxExport int OfxGetNumberOfPlugins(void)
{
return 4;
return 5;
}
OfxExport OfxPlugin *OfxGetPlugin(int nth)
@@ -842,5 +939,7 @@ OfxExport OfxPlugin *OfxGetPlugin(int nth)
return (OfxPlugin *)&test_plugin_id;
if (nth == 3)
return (OfxPlugin *)&test_plugin_interact;
if (nth == 4)
return (OfxPlugin *)&test_plugin_slow;
return NULL;
}