github.com/opcr-io/oras-go/v2@v2.0.0-20231122155130-eb4260d8a0ae/internal/syncutil/once.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package syncutil 17 18 import "context" 19 20 // Once is an object that will perform exactly one action. 21 // Unlike sync.Once, this Once allowes the action to have return values. 22 type Once struct { 23 result interface{} 24 err error 25 status chan bool 26 } 27 28 // NewOnce creates a new Once instance. 29 func NewOnce() *Once { 30 status := make(chan bool, 1) 31 status <- true 32 return &Once{ 33 status: status, 34 } 35 } 36 37 // Do calls the function f if and only if Do is being called first time or all 38 // previous function calls are cancelled, deadline exceeded, or panicking. 39 // When `once.Do(ctx, f)` is called multiple times, the return value of the 40 // first call of the function f is stored, and is directly returned for other 41 // calls. 42 // Besides the return value of the function f, including the error, Do returns 43 // true if the function f passed is called first and is not cancelled, deadline 44 // exceeded, or panicking. Otherwise, returns false. 45 func (o *Once) Do(ctx context.Context, f func() (interface{}, error)) (bool, interface{}, error) { 46 defer func() { 47 if r := recover(); r != nil { 48 o.status <- true 49 panic(r) 50 } 51 }() 52 for { 53 select { 54 case inProgress := <-o.status: 55 if !inProgress { 56 return false, o.result, o.err 57 } 58 result, err := f() 59 if err == context.Canceled || err == context.DeadlineExceeded { 60 o.status <- true 61 return false, nil, err 62 } 63 o.result, o.err = result, err 64 close(o.status) 65 return true, result, err 66 case <-ctx.Done(): 67 return false, nil, ctx.Err() 68 } 69 } 70 }