From 6c58e9976c41d3e641c0aa59c585a1bd523525a6 Mon Sep 17 00:00:00 2001 From: rkonfj Date: Thu, 30 Nov 2023 14:16:22 +0800 Subject: [PATCH] oauthutil: add Shutdown method Before this change, calling the `oauthutil.NewRenew` func may cause goroutine leaks. This change adds a `Shutdown` method to allow the caller to exit the goroutine to avoid leaks. Signed-off-by: rkonfj --- lib/oauthutil/renew.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/oauthutil/renew.go b/lib/oauthutil/renew.go index 7f98491e8..e3652ee9c 100644 --- a/lib/oauthutil/renew.go +++ b/lib/oauthutil/renew.go @@ -1,6 +1,7 @@ package oauthutil import ( + "sync" "sync/atomic" "github.com/rclone/rclone/fs" @@ -8,10 +9,12 @@ import ( // Renew allows tokens to be renewed on expiry if uploads are in progress. type Renew struct { - name string // name to use in logs - ts *TokenSource // token source that needs renewing - uploads atomic.Int32 // number of uploads in progress - run func() error // a transaction to run to renew the token on + name string // name to use in logs + ts *TokenSource // token source that needs renewing + uploads atomic.Int32 // number of uploads in progress + run func() error // a transaction to run to renew the token on + done chan any // channel to end the go routine + shutdown sync.Once } // NewRenew creates a new Renew struct and starts a background process @@ -24,6 +27,7 @@ func NewRenew(name string, ts *TokenSource, run func() error) *Renew { name: name, ts: ts, run: run, + done: make(chan any), } go r.renewOnExpiry() return r @@ -36,7 +40,11 @@ func NewRenew(name string, ts *TokenSource, run func() error) *Renew { func (r *Renew) renewOnExpiry() { expiry := r.ts.OnExpiry() for { - <-expiry + select { + case <-expiry: + case <-r.done: + return + } uploads := r.uploads.Load() if uploads != 0 { fs.Debugf(r.name, "Token expired - %d uploads in progress - refreshing", uploads) @@ -72,3 +80,15 @@ func (r *Renew) Invalidate() { func (r *Renew) Expire() error { return r.ts.Expire() } + +// Shutdown stops the timer and no more renewal will take place. +func (r *Renew) Shutdown() { + if r == nil { + return + } + // closing a channel can only be done once + r.shutdown.Do(func() { + r.ts.expiryTimer.Stop() + close(r.done) + }) +}