diff --git a/cmd/speedle-ads/main.go b/cmd/speedle-ads/main.go index 04711a02..87e739dc 100644 --- a/cmd/speedle-ads/main.go +++ b/cmd/speedle-ads/main.go @@ -122,7 +122,7 @@ func main() { // Do not close errChan: the second goroutine may still send to it. case <-intChan: log.Info("Interrupt signal") - close(intChan) + signal.Stop(intChan) } log.Info("Stopping servers...") diff --git a/cmd/speedle-pms/main.go b/cmd/speedle-pms/main.go index 8e33711a..dece79bb 100644 --- a/cmd/speedle-pms/main.go +++ b/cmd/speedle-pms/main.go @@ -119,7 +119,7 @@ func main() { // Do not close errChan: the second goroutine may still send to it. case <-intChan: log.Info("Interrupt signal") - close(intChan) + signal.Stop(intChan) } log.Info("Stopping servers...") diff --git a/pkg/store/etcd/etcdStore.go b/pkg/store/etcd/etcdStore.go index 3f2699d8..3311bf34 100644 --- a/pkg/store/etcd/etcdStore.go +++ b/pkg/store/etcd/etcdStore.go @@ -623,7 +623,9 @@ func (s *Store) Watch() (pms.StorageChangeChannel, error) { log.Errorf("panic in Watch outer goroutine: %v", r) } close(evalChan) - close(s.stop) + // Note: s.stop is closed by StopWatch (guarded by stopOnce), not + // here, so a panic in this goroutine cannot leave a later + // StopWatch sending on an already-closed channel. close(errChan) close(stopChan) s.wg.Done() @@ -762,7 +764,10 @@ func watch(evalChan chan pms.StoreChangeEvent, s *Store, errChan chan error, sto func (s *Store) StopWatch() { s.stopOnce.Do(func() { if s.stop != nil { - s.stop <- struct{}{} + // Close (not send) so every inner watch goroutine's + // `case <-s.stop` fires; a single send would only wake one of + // them. stopOnce makes this idempotent and panic-safe. + close(s.stop) } }) } diff --git a/pkg/store/file/fileStore.go b/pkg/store/file/fileStore.go index ab5b1fa6..9bc3076f 100644 --- a/pkg/store/file/fileStore.go +++ b/pkg/store/file/fileStore.go @@ -356,7 +356,9 @@ func (s *Store) Watch() (pms.StorageChangeChannel, error) { } watcher.Close() close(storeChangeChan) - close(s.stop) + // Note: s.stop is closed by StopWatch (guarded by stopOnce), not + // here, so a panic in this goroutine cannot leave a later + // StopWatch sending on an already-closed channel. }() for { select { @@ -406,7 +408,9 @@ func (s *Store) Watch() (pms.StorageChangeChannel, error) { func (s *Store) StopWatch() { s.stopOnce.Do(func() { if s.stop != nil { - s.stop <- struct{}{} + // Close (not send) so the watch goroutine's `case <-s.stop` + // fires immediately. stopOnce makes this idempotent and safe. + close(s.stop) } }) }