From b86e6294de90f633602b4adb53b738460072a467 Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Mon, 5 Dec 2022 14:01:53 +0100 Subject: [PATCH] ingredient/locker: Unlock even after cancel --- internal/wisski/ingredient/locker/lock.go | 7 ++- pkg/cancel/anyways.go | 67 +++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 pkg/cancel/anyways.go diff --git a/internal/wisski/ingredient/locker/lock.go b/internal/wisski/ingredient/locker/lock.go index cf38515..38ce798 100644 --- a/internal/wisski/ingredient/locker/lock.go +++ b/internal/wisski/ingredient/locker/lock.go @@ -2,9 +2,11 @@ package locker import ( "context" + "time" "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" + "github.com/FAU-CDI/wisski-distillery/pkg/cancel" "github.com/tkw1536/goprogram/exit" ) @@ -34,8 +36,11 @@ func (lock *Locker) TryLock(ctx context.Context) bool { } // TryUnlock attempts to unlock this WissKI and reports if it succeeded. -// An unlock can only +// An Unlock is also attempted when ctx is cancelled. func (lock *Locker) TryUnlock(ctx context.Context) bool { + ctx, close := cancel.Anyways(ctx, time.Second) + defer close() + table, err := lock.Malt.SQL.QueryTable(ctx, true, models.LockTable) if err != nil { return false diff --git a/pkg/cancel/anyways.go b/pkg/cancel/anyways.go new file mode 100644 index 0000000..49b69d7 --- /dev/null +++ b/pkg/cancel/anyways.go @@ -0,0 +1,67 @@ +package cancel + +import ( + "context" + "time" +) + +// Anyways behaves like context.WithTimeout, except that if the Done() channel of ctx is closed before Anyways is called, the returned context's Done() channel is only closed after timeout. +func Anyways(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { + // context is not yet cancelled => return as-is + if err := ctx.Err(); err == nil { + return context.WithTimeout(ctx, timeout) + } + + // create a new anyways + any := &anyways{ + done: make(chan struct{}), + parent: ctx, + deadline: time.Now().Add(timeout), + } + + // start waiting for the timer (or the cancel to be called) + finish := make(chan struct{}) + go func() { + t := time.NewTimer(timeout) + defer t.Stop() + + defer close(any.done) + + select { + case <-t.C: + case <-finish: + } + }() + + return any, func() { + close(finish) + } + +} + +type anyways struct { + done chan struct{} + + parent context.Context + deadline time.Time +} + +func (a anyways) Deadline() (deadline time.Time, ok bool) { + return a.deadline, true +} + +func (a anyways) Done() <-chan struct{} { + return a.done +} +func (a anyways) Err() error { + select { + case <-a.done: + return context.DeadlineExceeded + default: + return nil + } +} + +func (a anyways) Value(key any) any { + return a.parent.Done() +}