fix(plugin): f64 pick coords + harden out-of-process lifecycle

Follow-ups after merging the out-of-process plugin isolation work:

- Convert the out-of-process interactive adapter's `on_point` /
  `on_entity_pick` to `glam::DVec3`, matching the `CadCommand` trait,
  which moved to f64 pick coordinates. The branch predated that change
  and no longer compiled against the trait.
- Contain plugin constructor panics in `export_plugin!`: the constructor
  runs across the C ABI boundary in `ocs_plugin_register`, where an
  unwinding panic is undefined behavior. Catch it and return null, which
  the loader already treats as a failed registration.
- Reap killed runner processes. The spawn-timeout and disconnect paths
  called `child.kill()` without `wait()`, leaving a zombie on Unix until
  the host exited. Route both — and `shutdown()` — through a shared
  `reap()` that kills and waits on a detached thread.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-25 00:16:08 +03:00
1 parent deec2a8fd3
commit b4093724f2
4 files changed
+220 -18

No files matched your search

+15 -8
View File
@@ -77,7 +77,7 @@ impl PluginProcess {
// Create the listener before spawning so the runner can connect immediately.
let listener = ListenerOptions::new().name(socket_name_ref).create_sync()?;
let mut child = Command::new(&runner_path)
let child = Command::new(&runner_path)
.arg("--ocs-plugin-runner")
.arg(&socket_name)
.arg(cdylib_path)
@@ -99,11 +99,11 @@ impl PluginProcess {
}
Ok(Err(e)) => return Err(e.into()),
Err(mpsc::RecvTimeoutError::Timeout) => {
let _ = child.kill();
reap(child);
return Err(PluginError::SpawnTimeout(spawn_timeout()));
}
Err(mpsc::RecvTimeoutError::Disconnected) => {
let _ = child.kill();
reap(child);
return Err(PluginError::RunnerExited);
}
};
@@ -244,11 +244,8 @@ impl PluginProcess {
pub fn shutdown(&self) {
let (stream, child) = self.take_resources();
drop(stream);
if let Some(mut child) = child {
let _ = child.kill();
std::thread::spawn(move || {
let _ = child.wait();
});
if let Some(child) = child {
reap(child);
}
}
@@ -287,6 +284,16 @@ impl PluginProcess {
}
}
/// Kill a child process and reap it without blocking the caller. The blocking
/// `wait()` runs in a detached thread so the host never stalls on a plugin, and
/// the child is reaped rather than left as a zombie on Unix.
fn reap(mut child: Child) {
let _ = child.kill();
std::thread::spawn(move || {
let _ = child.wait();
});
}
fn shutdown_error() -> PluginError {
PluginError::Io(std::io::Error::new(
std::io::ErrorKind::NotConnected,