sinelaw/fresh · error

Plugin thread not responding

Error message

Plugin thread not responding

What it means

load_plugin() sends its LoadPlugin request through a bounded channel to the plugin worker thread. This error is raised when `sender.send(...)` fails, which for a channel paired with a oneshot reply means the worker's receiver end has been dropped — the thread is no longer processing requests even though the sender handle still exists. It is distinct from "Plugin thread shut down" (sender is None): here the handle looks alive but the worker is gone.

Solutions

  1. Verify no code path terminates the plugin thread while loads are in flight; sequence shutdown after all load calls complete.
  2. Retry once after confirming the runtime is still running — if the thread panicked, restart the plugin thread and re-issue the load.
  3. Log at the call site whether the runtime was concurrently shut down to find the racy caller.
  4. Wrap the runtime in an Arc and keep it alive for all callers so the worker isn't dropped mid-request.

Example fix

// before
runtime.load_plugin(&path)?; // may race with shutdown

// after
match runtime.load_plugin(&path) {
    Ok(()) => info!("plugin loaded"),
    Err(e) if e.to_string().contains("not responding") => {
        warn!("plugin thread went away during load; restarting runtime");
        runtime.restart_thread()?;
        runtime.load_plugin(&path)?;
    }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: retry

Validate before calling

// Send-probe before real work
fn thread_alive(rt: &PluginRuntimeHandle) -> bool {
    // e.g. a cheap status request that round-trips through the channel
    rt.ping().is_ok()
}

Type guard

fn channel_open(r: &Result<(), SendError<PluginRequest>>) -> bool { r.is_ok() }

Try / catch

match rt.load_plugin(&path) {
    Err(e) if e.to_string().contains("not responding") => {
        warn!("plugin worker dropped receiver; restarting once");
        rt.restart_thread()?;
        rt.load_plugin(&path)
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling `load_plugin(path)` at the moment the plugin thread's request receiver has been dropped — typically during or immediately after shutdown, or after the worker loop exited via panic/error while the handle still holds a sender clone.

Common situations: Race between an editor shutdown sequence and a concurrently running plugin loader; a plugin thread crash that closed the channel without clearing the handle's sender; calling into the runtime from another thread right as the worker exits.

Related errors


AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13). Data as JSON: /api/errors/7c3306b8ea6da7b3. Report an issue: GitHub.

Appendix: source

Thrown at crates/fresh-plugin-runtime/src/thread.rs:579

                fire_and_forget(sender.send(PluginRequest::TrackAsyncResource {
                    plugin_name,
                    resource,
                }));
            }
        }
    }

    /// Load a plugin from a file (blocking)
    pub fn load_plugin(&self, path: &Path) -> Result<()> {
        let (tx, rx) = oneshot::channel();
        self.request_sender
            .as_ref()
            .ok_or_else(|| anyhow!("Plugin thread shut down"))?
            .send(PluginRequest::LoadPlugin {
                path: path.to_path_buf(),
                response: tx,
            })
            .map_err(|_| anyhow!("Plugin thread not responding"))?;

        rx.recv().map_err(|_| anyhow!("Plugin thread closed"))?
    }

    /// Load all plugins from a directory (blocking)
    pub fn load_plugins_from_dir(&self, dir: &Path) -> Vec<String> {
        let (tx, rx) = oneshot::channel();
        let Some(sender) = self.request_sender.as_ref() else {
            return vec!["Plugin thread shut down".to_string()];
        };
        if sender
            .send(PluginRequest::LoadPluginsFromDir {
                dir: dir.to_path_buf(),
                response: tx,
            })
            .is_err()
        {
            return vec!["Plugin thread not responding".to_string()];

View on GitHub (pinned to 67894ca546)