siyuan-note/siyuan · error
panic during plugin stop: %v
Error message
panic during plugin stop: %v
What it means
The plugin's `stop()` recovered from a Go panic raised while shutting down (closing sockets, freeing the goja runtime). The deferred recover captures the panic value, marks the stop as not ok, and returns this error instead of crashing the process; the plugin's state is left inconsistent because cleanup did not finish.
Source
Thrown at kernel/plugin/plugin.go:297
}
p.onLoad()
p.updateState(PluginStateRunning)
p.onRunning()
p.bus.Publish(EventBusTopicRuntime, createEventMessage("start", nil))
logging.LogDebugf("[plugin:%s] started", p.Name)
return
}
// stop cleanly shuts down the plugin: closes sockets, frees goja runtime.
func (p *KernelPlugin) stop() (ok bool, err error) {
defer func() {
if r := recover(); r != nil {
p.error()
ok = false
err = fmt.Errorf("panic during plugin stop: %v", r)
}
}()
if p.State() != PluginStateRunning {
ok = false
return
}
p.bus.Publish(EventBusTopicRuntime, createEventMessage("stop", nil))
p.updateState(PluginStateStopping)
p.onUnload()
p.Clear()
p.cancel()
p.closeStorageWatcher()View on GitHub (pinned to 8641553a1f)
Solutions
- Read the %v panic value in the log to locate the panicking teardown code
- Ensure stop() is only called once per plugin instance and only from PluginStateRunning
- Guard concurrent cleanup with the watcher/context mutexes so sockets and the goja runtime are closed exactly once
- Fix the nil-dereference or double-free in the plugin's unload/socket-close code, then restart the plugin
Example fix
// before
func (p *KernelPlugin) stop() { p.sock.Close(); p.rt.Free() } // panics if already closed
// after
func (p *KernelPlugin) stop() {
if p.sock != nil { _ = p.sock.Close(); p.sock = nil }
if p.rt != nil { p.rt.Free(); p.rt = nil }
} Defensive patterns
Strategy: try-catch
Validate before calling
if p.State() != PluginStateRunning {
return // nothing to stop
} Try / catch
ok, err := p.stop()
if err != nil {
logging.LogErrorf("plugin %s stop failed: %v", p.Name, err)
// force-reset state so a later start is possible
p.updateState(PluginStateStopped)
} Prevention
- Make stop() idempotent: guard socket/runtime teardown with nil checks and sync.Once
- Never share the goja runtime across goroutines without a mutex
- Track stopped state before closing sockets to avoid double-free
- Recover in teardown paths so one bad cleanup cannot wedge the supervisor
When it happens
Trigger: Calling stop() on a running KernelPlugin whose teardown path panics - e.g. nil map/pointer dereference in a socket close, goja runtime freed twice, or a concurrent access to the runtime during stop.
Common situations: Disabling a plugin whose cleanup goroutine races with stop; double-stop of the same plugin; a goja runtime already closed by an earlier failure; nil handler registered for onUnload.
Related errors
- panic during lifecycle hook invocation: %v
- panic during siyuan.client.event: %v
- injectServer: %v
- injectStorage: %v
- panic during siyuan.storage.get: %v
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/ce64ebae13df81b5.
Report an issue: GitHub.