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

  1. Read the %v panic value in the log to locate the panicking teardown code
  2. Ensure stop() is only called once per plugin instance and only from PluginStateRunning
  3. Guard concurrent cleanup with the watcher/context mutexes so sockets and the goja runtime are closed exactly once
  4. 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

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


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/ce64ebae13df81b5. Report an issue: GitHub.