POTENTIAL DEADLOCK: Recursive locking crash in RefreshContainersAndServices / ServiceMutex

Author: dobrejakcypCreated Sep 8, 2026Updated Sep 8, 2026

Describe the bug

lazydocker crashes intermittently with a "POTENTIAL DEADLOCK: Recursive locking" report from go-deadlock, pointing at ServiceMutex in RefreshContainersAndServices (docker.go:226-227), with ContainerMutex (docker.go:374-375, inside the GetContainers call it makes) shown as a lock held by the same goroutine.

Both the "current" and "previous" lock-grab entries in the report point at the same call site (docker.go:227), which is the single c.ServiceMutex.Lock() at the top of RefreshContainersAndServices — that function has no recursive call into itself, so this doesn't look like a genuine self-relock in the code path itself.

My guess (not confirmed, flagging for maintainer input) is that this is a false positive triggered by goroutine-ID reuse: gui.refresh() in gui.go spawns a brand-new goroutine (go func(){...}()) on every refresh, and refresh is triggered frequently (ticker + throttled trigger). go-deadlock's tracking is keyed by goroutine ID, and Go reuses goroutine IDs once a goroutine exits — so under enough refresh churn, a new goroutine can inherit the ID of a just-finished one and get flagged as "the same goroutine re-locking a mutex it already holds," even though the previous holder already unlocked and exited cleanly.

To Reproduce

Not reliably reproducible on demand yet — happens intermittently during normal use (not rapid view-switching, unlike #585). Environment at time of crash:

  • 3 containers running, default local Docker socket (no DOCKER_HOST, no remote context)
  • Docker Engine 29.8.0
  • Running inside WSL2

Expected behavior

lazydocker should not crash from its own internal deadlock-detection instrumentation on a normal, low-load setup.

Crash log

POTENTIAL DEADLOCK: Recursive locking
current goroutine 2 lock 0xc000377240
/home/runner/work/lazydocker/lazydocker/pkg/commands/docker.go:227 commands.(*DockerCommand).RefreshContainersAndServices ??? <<<<<
/home/runner/work/lazydocker/lazydocker/pkg/commands/docker.go:226 commands.(*DockerCommand).RefreshContainersAndServices ???
/home/runner/work/lazydocker/lazydocker/pkg/gui/containers_panel.go:280 gui.(*Gui).refreshContainersAndServices ???
/home/runner/work/lazydocker/lazydocker/pkg/gui/gui.go:301 gui.(*Gui).refresh.func1 ???

Previous place where the lock was grabbed (same goroutine)
/home/runner/work/lazydocker/lazydocker/pkg/commands/docker.go:227 commands.(*DockerCommand).RefreshContainersAndServices ??? <<<<<
/home/runner/work/lazydocker/lazydocker/pkg/commands/docker.go:226 commands.(*DockerCommand).RefreshContainersAndServices ???
/home/runner/work/lazydocker/lazydocker/pkg/gui/containers_panel.go:280 gui.(*Gui).refreshContainersAndServices ???
/home/runner/work/lazydocker/lazydocker/pkg/gui/gui.go:301 gui.(*Gui).refresh.func1 ???

Other goroutines holding locks:
goroutine 2 lock 0xc000377238
/home/runner/work/lazydocker/lazydocker/pkg/commands/docker.go:375 commands.(*DockerCommand).GetContainers ??? <<<<<
/home/runner/work/lazydocker/lazydocker/pkg/commands/docker.go:374 commands.(*DockerCommand).GetContainers ???
/home/runner/work/lazydocker/lazydocker/pkg/commands/docker.go:230 commands.(*DockerCommand).RefreshContainersAndServices ???
/home/runner/work/lazydocker/lazydocker/pkg/gui/containers_panel.go:280 gui.(*Gui).refreshContainersAndServices ???
/home/runner/work/lazydocker/lazydocker/pkg/gui/gui.go:301 gui.(*Gui).refresh.func1 ???

Screenshots

N/A (text crash, log above)

Which OS are you using?

  • Linux (WSL2, kernel 6.6.87.2-microsoft-standard-WSL2)

Setup

  • lazydocker version: 0.25.2 (commit 7e7aadc, built 2026-04-19)
  • Docker Engine 29.8.0, OSType linux, default local socket

Source: jesseduffield/lazydocker