diff --git a/manager.go b/manager.go index cb8195c53c1f401c724af2e0c9da28c1634b3b02..06214358e76b62f490a94d4540452cc274ac39f9 100644 --- a/manager.go +++ b/manager.go @@ -249,12 +249,23 @@ func (m *Manager) loop(ctx context.Context) { // Updates are long-term jobs, so they should be // interruptible. We run updates in a separate goroutine, and // cancel them when the configuration is reloaded or on exit. + // A simple channel is used as a semaphore, so that only one + // update goroutine can be running at any given time (without + // other ones piling up). var upCancel context.CancelFunc var wg sync.WaitGroup + sem := make(chan struct{}, 1) startUpdate := func(certs []*certInfo) context.CancelFunc { - // Ensure the previous update has finished. - wg.Wait() + // Acquire the semaphore, return if we fail to. + select { + case sem <- struct{}{}: + default: + return nil + } + defer func() { + <-sem + }() upCtx, cancel := context.WithCancel(ctx) wg.Add(1) @@ -270,6 +281,7 @@ func (m *Manager) loop(ctx context.Context) { cancelUpdate := func() { if upCancel != nil { upCancel() + upCancel = nil } wg.Wait() } @@ -278,17 +290,21 @@ func (m *Manager) loop(ctx context.Context) { tick := time.NewTicker(5 * time.Minute) defer tick.Stop() for { + var c func() select { case <-tick.C: - upCancel = startUpdate(m.certs) + c = startUpdate(m.certs) case <-testUpdateCh: - upCancel = startUpdate(m.certs) + c = startUpdate(m.certs) case certDomains := <-m.configCh: cancelUpdate() m.certs = m.loadConfig(certDomains) case <-ctx.Done(): return } + if c != nil { + upCancel = nil + } } }