jesseduffield / jesseduffield/lazydocker

POTENTIAL DEADLOCK: Recursive locking crash in RefreshContainersAndServices / ServiceMutex

Open
#840 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
52.8k
Forks
1.7k
PR merge metrics
No merged PRs in 30d

Description

### 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?
- [x] 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

Contributor guide

Open the contributing guide

Research direction

Start with pkg/commands/docker.go at RefreshContainersAndServices and GetContainers, then trace the goroutine creation in pkg/gui/gui.go and the caller in pkg/gui/containers_panel.go. Reproduce or instrument the intermittent crash and verify whether refresh goroutine reuse causes a false recursive-lock report. Done means the normal refresh path no longer crashes and the root cause is covered by a regression test or documented reproduction.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, go
Domain
cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.