From 5dd6a5323c530e135aec907493f89e7687157879 Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Wed, 1 May 2024 22:39:18 +0200 Subject: [PATCH] [core] Add locking to prevent concurrent rule executions refs #59 ensures counter actions are not triggered concurrently by two persons Signed-off-by: Knut Ahlers --- actions.go | 5 ++++ internal/locker/locker.go | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 internal/locker/locker.go diff --git a/actions.go b/actions.go index e28d9d3..3c5632f 100644 --- a/actions.go +++ b/actions.go @@ -1,6 +1,7 @@ package main import ( + "path" "sync" "github.com/pkg/errors" @@ -8,6 +9,7 @@ import ( "gopkg.in/irc.v4" "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/locker" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -79,6 +81,9 @@ func handleMessage(c *irc.Client, m *irc.Message, event *string, eventData *fiel } func handleMessageRuleExecution(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection) { + locker.LockByKey(path.Join("rule-execution", r.MatcherID())) + defer locker.UnlockByKey(path.Join("rule-execution", r.MatcherID())) + var ( ruleEventData = fieldcollection.NewFieldCollection() preventCooldown bool diff --git a/internal/locker/locker.go b/internal/locker/locker.go new file mode 100644 index 0000000..644e9a8 --- /dev/null +++ b/internal/locker/locker.go @@ -0,0 +1,50 @@ +// Package locker contains a way to interact with arbitrary locks +package locker + +import "sync" + +var ( + locks = map[string]*sync.RWMutex{} + locksOLocks sync.RWMutex +) + +// LockByKey takes a key to lock and locks the corresponding RWMutex +func LockByKey(key string) { getLockByKey(key).Lock() } + +// RLockByKey takes a key to lock and read-locks the corresponding RWMutex +func RLockByKey(key string) { getLockByKey(key).RLock() } + +// RUnlockByKey takes a key to lock and read-unlocks the corresponding RWMutex +func RUnlockByKey(key string) { getLockByKey(key).RUnlock() } + +// UnlockByKey takes a key to lock and unlocks the corresponding RWMutex +func UnlockByKey(key string) { getLockByKey(key).Unlock() } + +// WithLock takes a key to lock and a function to execute during the +// lock of this key +func WithLock(key string, fn func()) { + LockByKey(key) + defer UnlockByKey(key) + + fn() +} + +// WithRLock takes a key to lock and a function to execute during the +// read-lock of this key +func WithRLock(key string, fn func()) { + RLockByKey(key) + defer RUnlockByKey(key) + + fn() +} + +func getLockByKey(key string) *sync.RWMutex { + locksOLocks.Lock() + defer locksOLocks.Unlock() + + if locks[key] == nil { + locks[key] = new(sync.RWMutex) + } + + return locks[key] +}