util.go (2739B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cache 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "time" 25 26 "codeberg.org/gruf/go-cache/v3/result" 27 errorsv2 "codeberg.org/gruf/go-errors/v2" 28 "github.com/superseriousbusiness/gotosocial/internal/log" 29 ) 30 31 // SentinelError is returned to indicate a non-permanent error return, 32 // i.e. a situation in which we do not want a cache a negative result. 33 var SentinelError = errors.New("BUG: error should not be returned") //nolint:revive 34 35 // ignoreErrors is an error ignoring function capable of being passed to 36 // caches, which specifically catches and ignores our sentinel error type. 37 func ignoreErrors(err error) bool { 38 return errorsv2.Comparable( 39 err, 40 SentinelError, 41 context.DeadlineExceeded, 42 context.Canceled, 43 ) 44 } 45 46 // nocopy when embedded will signal linter to 47 // error on pass-by-value of parent struct. 48 type nocopy struct{} 49 50 func (*nocopy) Lock() {} 51 52 func (*nocopy) Unlock() {} 53 54 // tryStart will attempt to start the given cache only if sweep duration > 0 (sweeping is enabled). 55 func tryStart[ValueType any](cache *result.Cache[ValueType], sweep time.Duration) { 56 if sweep > 0 { 57 var z ValueType 58 msg := fmt.Sprintf("starting %T cache", z) 59 tryUntil(msg, 5, func() bool { 60 return cache.Start(sweep) 61 }) 62 } 63 } 64 65 // tryStop will attempt to stop the given cache only if sweep duration > 0 (sweeping is enabled). 66 func tryStop[ValueType any](cache *result.Cache[ValueType], sweep time.Duration) { 67 if sweep > 0 { 68 var z ValueType 69 msg := fmt.Sprintf("stopping %T cache", z) 70 tryUntil(msg, 5, cache.Stop) 71 } 72 } 73 74 // tryUntil will attempt to call 'do' for 'count' attempts, before panicking with 'msg'. 75 func tryUntil(msg string, count int, do func() bool) { 76 for i := 0; i < count; i++ { 77 if do() { 78 // success. 79 return 80 } 81 82 // Sleep for a little before retry (a bcakoff). 83 time.Sleep(time.Millisecond * 1 << (i + 1)) 84 } 85 86 // panic on total failure as this shouldn't happen. 87 log.Panicf(nil, "failed %s after %d tries", msg, count) 88 }