fix(concurrency): shadow loop variable in event handler goroutine
Create local copy of loop variable before passing to goroutine to prevent handlers from capturing wrong event due to loop variable mutation.
- commit
- 196ef1eb281e20edb0feb5fa9a3cf4c1e40542be
- author
- brice · · 2026-02-06 05:56 UTC
- parent
- e62cfa8
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +5 -4
M internal/adapters/out/eventbus/inmemory.go +5 -4
| @@ -186,11 +186,12 @@ func (bus *InMemory) handleEvent(event domain.Event) { | |||
| 186 | 186 | ||
| 187 | 187 | for _, handler := range handlers { | |
| 188 | 188 | if handler.CanHandle(event.Type) { | |
| 189 | + | h := handler // shadow loop variable for goroutine capture | |
| 189 | 190 | start := time.Now() | |
| 190 | 191 | ||
| 191 | 192 | done := make(chan error, 1) | |
| 192 | 193 | go func() { | |
| 193 | - | done <- handler.Handle(event) | |
| 194 | + | done <- h.Handle(event) | |
| 194 | 195 | }() | |
| 195 | 196 | ||
| 196 | 197 | select { | |
| @@ -202,7 +203,7 @@ func (bus *InMemory) handleEvent(event domain.Event) { | |||
| 202 | 203 | Err(err). | |
| 203 | 204 | Str("event_id", event.ID). | |
| 204 | 205 | Str(zerowrap.FieldEvent, string(event.Type)). | |
| 205 | - | Str(zerowrap.FieldHandler, fmt.Sprintf("%T", handler)). | |
| 206 | + | Str(zerowrap.FieldHandler, fmt.Sprintf("%T", h)). | |
| 206 | 207 | Msg("error handling event") | |
| 207 | 208 | } else { | |
| 208 | 209 | bus.log.Debug(). | |
| @@ -210,7 +211,7 @@ func (bus *InMemory) handleEvent(event domain.Event) { | |||
| 210 | 211 | Str(zerowrap.FieldAdapter, "eventbus"). | |
| 211 | 212 | Str("event_id", event.ID). | |
| 212 | 213 | Str(zerowrap.FieldEvent, string(event.Type)). | |
| 213 | - | Str(zerowrap.FieldHandler, fmt.Sprintf("%T", handler)). | |
| 214 | + | Str(zerowrap.FieldHandler, fmt.Sprintf("%T", h)). | |
| 214 | 215 | Dur(zerowrap.FieldDuration, time.Since(start)). | |
| 215 | 216 | Msg("event handled successfully") | |
| 216 | 217 | } | |
| @@ -220,7 +221,7 @@ func (bus *InMemory) handleEvent(event domain.Event) { | |||
| 220 | 221 | Str(zerowrap.FieldAdapter, "eventbus"). | |
| 221 | 222 | Str("event_id", event.ID). | |
| 222 | 223 | Str(zerowrap.FieldEvent, string(event.Type)). | |
| 223 | - | Str(zerowrap.FieldHandler, fmt.Sprintf("%T", handler)). | |
| 224 | + | Str(zerowrap.FieldHandler, fmt.Sprintf("%T", h)). | |
| 224 | 225 | Dur(zerowrap.FieldDuration, time.Since(start)). | |
| 225 | 226 | Msg("handler timeout after 30s") | |
| 226 | 227 | } | |
| @@ -186,11 +186,12 @@ func (bus *InMemory) handleEvent(event domain.Event) { | |||||
| 186 | 186 | ||||
| 187 | for _, handler := range handlers { | 187 | for _, handler := range handlers { | ||
| 188 | if handler.CanHandle(event.Type) { | 188 | if handler.CanHandle(event.Type) { | ||
| 189 | + | h := handler // shadow loop variable for goroutine capture | |||
| 189 | start := time.Now() | 190 | start := time.Now() | ||
| 190 | 191 | ||||
| 191 | done := make(chan error, 1) | 192 | done := make(chan error, 1) | ||
| 192 | go func() { | 193 | go func() { | ||
| 193 | - | done <- handler.Handle(event) | 194 | + | done <- h.Handle(event) |
| 194 | }() | 195 | }() | ||
| 195 | 196 | ||||
| 196 | select { | 197 | select { | ||
| @@ -202,7 +203,7 @@ func (bus *InMemory) handleEvent(event domain.Event) { | |||||
| 202 | Err(err). | 203 | Err(err). | ||
| 203 | Str("event_id", event.ID). | 204 | Str("event_id", event.ID). | ||
| 204 | Str(zerowrap.FieldEvent, string(event.Type)). | 205 | Str(zerowrap.FieldEvent, string(event.Type)). | ||
| 205 | - | Str(zerowrap.FieldHandler, fmt.Sprintf("%T", handler)). | 206 | + | Str(zerowrap.FieldHandler, fmt.Sprintf("%T", h)). |
| 206 | Msg("error handling event") | 207 | Msg("error handling event") | ||
| 207 | } else { | 208 | } else { | ||
| 208 | bus.log.Debug(). | 209 | bus.log.Debug(). | ||
| @@ -210,7 +211,7 @@ func (bus *InMemory) handleEvent(event domain.Event) { | |||||
| 210 | Str(zerowrap.FieldAdapter, "eventbus"). | 211 | Str(zerowrap.FieldAdapter, "eventbus"). | ||
| 211 | Str("event_id", event.ID). | 212 | Str("event_id", event.ID). | ||
| 212 | Str(zerowrap.FieldEvent, string(event.Type)). | 213 | Str(zerowrap.FieldEvent, string(event.Type)). | ||
| 213 | - | Str(zerowrap.FieldHandler, fmt.Sprintf("%T", handler)). | 214 | + | Str(zerowrap.FieldHandler, fmt.Sprintf("%T", h)). |
| 214 | Dur(zerowrap.FieldDuration, time.Since(start)). | 215 | Dur(zerowrap.FieldDuration, time.Since(start)). | ||
| 215 | Msg("event handled successfully") | 216 | Msg("event handled successfully") | ||
| 216 | } | 217 | } | ||
| @@ -220,7 +221,7 @@ func (bus *InMemory) handleEvent(event domain.Event) { | |||||
| 220 | Str(zerowrap.FieldAdapter, "eventbus"). | 221 | Str(zerowrap.FieldAdapter, "eventbus"). | ||
| 221 | Str("event_id", event.ID). | 222 | Str("event_id", event.ID). | ||
| 222 | Str(zerowrap.FieldEvent, string(event.Type)). | 223 | Str(zerowrap.FieldEvent, string(event.Type)). | ||
| 223 | - | Str(zerowrap.FieldHandler, fmt.Sprintf("%T", handler)). | 224 | + | Str(zerowrap.FieldHandler, fmt.Sprintf("%T", h)). |
| 224 | Dur(zerowrap.FieldDuration, time.Since(start)). | 225 | Dur(zerowrap.FieldDuration, time.Since(start)). | ||
| 225 | Msg("handler timeout after 30s") | 226 | Msg("handler timeout after 30s") | ||
| 226 | } | 227 | } | ||