// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exsync import ( "context" "fmt" "sync" "time" ) // Event is a wrapper around a channel that can be used to notify multiple waiters that some event has happened. // // It's modelled after Python's asyncio.Event: https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event type Event struct { ch chan empty set bool waiting bool l sync.RWMutex } // NewEvent creates a new event. It will initially be unset. func NewEvent() *Event { return &Event{ ch: make(chan empty), } } type EventChan = <-chan empty // GetChan returns the channel that will be closed when the event is set. func (e *Event) GetChan() EventChan { e.l.RLock() defer e.l.RUnlock() e.waiting = true return e.ch } // Wait waits for either the event to happen or the given context to be done. // If the context is done first, the error is returned, otherwise the return value is nil. func (e *Event) Wait(ctx context.Context) error { select { case <-e.GetChan(): return nil case <-ctx.Done(): return ctx.Err() } } // WaitTimeout waits for the event to happen within the given timeout. // If the timeout expires first, the return value is false, otherwise it's true. func (e *Event) WaitTimeout(timeout time.Duration) bool { select { case <-e.GetChan(): return true case <-time.After(timeout): return false } } // WaitTimeoutCtx waits for the event to happen, the timeout to expire, or the given context to be done. // If the context or timeout is done first, an error is returned, otherwise the return value is nil. func (e *Event) WaitTimeoutCtx(ctx context.Context, timeout time.Duration) error { select { case <-e.GetChan(): return nil case <-ctx.Done(): return ctx.Err() case <-time.After(timeout): return fmt.Errorf("exsync.Event: wait timeout") } } // IsSet returns true if the event has been set. func (e *Event) IsSet() bool { e.l.RLock() defer e.l.RUnlock() return e.set } // Set sets the event, notifying all waiters. func (e *Event) Set() { e.l.Lock() defer e.l.Unlock() if !e.set { close(e.ch) e.set = true } } // Notify notifies all waiters, but doesn't set the event. // // Calling Notify when the event is already set is a no-op. func (e *Event) Notify() { e.l.Lock() defer e.l.Unlock() if !e.set && e.waiting { close(e.ch) e.ch = make(chan empty) e.waiting = false } } // Clear clears the event, making it unset. Future calls to Wait will now block until Set is called again. // If the event is not already set, this is a no-op and existing calls to Wait will keep working. func (e *Event) Clear() { e.l.Lock() defer e.l.Unlock() if e.set { e.ch = make(chan empty) e.set = false e.waiting = false } }