detached 200ec03

fix: prevent mutex deadlock on panic in config watcher

commit
200ec03cdfacac1a547f7a06c7013ea8bf021b03
author
brice · · 2026-01-24 16:36 UTC
parent
677e3c0
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +11 -2
M internal/infrastructure/config/watcher.go +11 -2
@@ -24,6 +24,14 @@ func (m *Manager) Watch() error {
2424 // Acquire write lock before reload (reload modifies m.config)
2525 m.mu.Lock()
2626
27+ // Track whether notifyCallbacksLocked will handle the unlock
28+ willNotify := false
29+ defer func() {
30+ if !willNotify {
31+ m.mu.Unlock()
32+ }
33+ }()
34+
2735 // Skip reload if this was triggered by our own Save() - the in-memory
2836 // config is already correct and viper may have stale cached data
2937 if m.skipNextReload {
@@ -34,6 +42,7 @@ func (m *Manager) Watch() error {
3442 log.Warn().Err(err).Msg("failed to sync viper config after Save")
3543 // Continue anyway - in-memory config is correct, just viper internal state is off
3644 }
45+ willNotify = true
3746 m.notifyCallbacksLocked()
3847 return
3948 }
@@ -41,9 +50,9 @@ func (m *Manager) Watch() error {
4150 log.Debug().Msg("reloading config from external change")
4251 if err := m.reload(); err != nil {
4352 log.Warn().Err(err).Msg("failed to reload config")
44- m.mu.Unlock()
45- return
53+ return // defer will unlock
4654 }
55+ willNotify = true
4756 m.notifyCallbacksLocked()
4857 })
4958
@@ -24,6 +24,14 @@ func (m *Manager) Watch() error {
24 // Acquire write lock before reload (reload modifies m.config)24 // Acquire write lock before reload (reload modifies m.config)
25 m.mu.Lock()25 m.mu.Lock()
26 26
27+ // Track whether notifyCallbacksLocked will handle the unlock
28+ willNotify := false
29+ defer func() {
30+ if !willNotify {
31+ m.mu.Unlock()
32+ }
33+ }()
34+
27 // Skip reload if this was triggered by our own Save() - the in-memory35 // Skip reload if this was triggered by our own Save() - the in-memory
28 // config is already correct and viper may have stale cached data36 // config is already correct and viper may have stale cached data
29 if m.skipNextReload {37 if m.skipNextReload {
@@ -34,6 +42,7 @@ func (m *Manager) Watch() error {
34 log.Warn().Err(err).Msg("failed to sync viper config after Save")42 log.Warn().Err(err).Msg("failed to sync viper config after Save")
35 // Continue anyway - in-memory config is correct, just viper internal state is off43 // Continue anyway - in-memory config is correct, just viper internal state is off
36 }44 }
45+ willNotify = true
37 m.notifyCallbacksLocked()46 m.notifyCallbacksLocked()
38 return47 return
39 }48 }
@@ -41,9 +50,9 @@ func (m *Manager) Watch() error {
41 log.Debug().Msg("reloading config from external change")50 log.Debug().Msg("reloading config from external change")
42 if err := m.reload(); err != nil {51 if err := m.reload(); err != nil {
43 log.Warn().Err(err).Msg("failed to reload config")52 log.Warn().Err(err).Msg("failed to reload config")
44- m.mu.Unlock()53+ return // defer will unlock
45- return
46 }54 }
55+ willNotify = true
47 m.notifyCallbacksLocked()56 m.notifyCallbacksLocked()
48 })57 })
49 58

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close