From 892c5e0ea9b11e1605ad7364e8b0b6ee6cafc819 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 13 Aug 2024 12:46:22 +0300 Subject: [PATCH] exsync: add Event which works like Python's asyncio.Event --- exsync/event.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 exsync/event.go diff --git a/exsync/event.go b/exsync/event.go new file mode 100644 index 0000000..32ddee9 --- /dev/null +++ b/exsync/event.go @@ -0,0 +1,86 @@ +// 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" + "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 + l sync.RWMutex +} + +// NewEvent creates a new event. It will initially be unset. +func NewEvent() *Event { + return &Event{ + ch: make(chan empty), + } +} + +// GetChan returns the channel that will be closed when the event is set. +func (e *Event) GetChan() <-chan empty { + e.l.RLock() + defer e.l.RUnlock() + 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 either 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 + } +} + +// 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 + } +} + +// 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 + } +}