// Copyright 2013 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.
// Package singleflight provides a duplicate function call suppression// mechanism.
package singleflight // import "golang.org/x/sync/singleflight"import ()// errGoexit indicates runtime.Goexit was called in// the user-given function.varerrGoexit = errors.New("runtime.Goexit was called")// A panicError is an arbitrary value recovered from a panic// with the stack trace during the execution of the given function.typepanicErrorstruct {valueanystack []byte}// Error implements error interface.func ( *panicError) () string {returnfmt.Sprintf("%v\n\n%s", .value, .stack)}func ( *panicError) () error { , := .value.(error)if ! {returnnil }return}func ( any) error { := debug.Stack()// The first line of the stack trace is of the form "goroutine N [status]:" // but by the time the panic reaches Do the goroutine may no longer exist // and its status will have changed. Trim out the misleading line.if := bytes.IndexByte([:], '\n'); >= 0 { = [+1:] }return &panicError{value: , stack: }}// call is an in-flight or completed singleflight.Do calltypecallstruct {wgsync.WaitGroup// These fields are written once before the WaitGroup is done // and are only read after the WaitGroup is done.valanyerrerror// These fields are read and written with the singleflight // mutex held before the WaitGroup is done, and are read but // not written after the WaitGroup is done.dupsintchans []chan<- Result}// Group represents a class of work and forms a namespace in// which units of work can be executed with duplicate suppression.typeGroupstruct {musync.Mutex// protects mmmap[string]*call// lazily initialized}// Result holds the results of Do, so they can be passed// on a channel.typeResultstruct {ValanyErrerrorSharedbool}// Do executes and returns the results of the given function, making// sure that only one execution is in-flight for a given key at a// time. If a duplicate comes in, the duplicate caller waits for the// original to complete and receives the same results.// The return value shared indicates whether v was given to multiple callers.func ( *Group) ( string, func() (any, error)) ( any, error, bool) { .mu.Lock()if .m == nil { .m = make(map[string]*call) }if , := .m[]; { .dups++ .mu.Unlock() .wg.Wait()if , := .err.(*panicError); {panic() } elseif .err == errGoexit {runtime.Goexit() }return .val, .err, true } := new(call) .wg.Add(1) .m[] = .mu.Unlock() .doCall(, , )return .val, .err, .dups > 0}// DoChan is like Do but returns a channel that will receive the// results when they are ready.//// The returned channel will not be closed.func ( *Group) ( string, func() (any, error)) <-chanResult { := make(chanResult, 1) .mu.Lock()if .m == nil { .m = make(map[string]*call) }if , := .m[]; { .dups++ .chans = append(.chans, ) .mu.Unlock()return } := &call{chans: []chan<- Result{}} .wg.Add(1) .m[] = .mu.Unlock()go .doCall(, , )return}// doCall handles the single call for a key.func ( *Group) ( *call, string, func() (any, error)) { := false := false// use double-defer to distinguish panic from runtime.Goexit, // more details see https://golang.org/cl/134395deferfunc() {// the given function invoked runtime.Goexitif ! && ! { .err = errGoexit } .mu.Lock()defer .mu.Unlock() .wg.Done()if .m[] == {delete(.m, ) }if , := .err.(*panicError); {// In order to prevent the waiting channels from being blocked forever, // needs to ensure that this panic cannot be recovered.iflen(.chans) > 0 {gopanic()select {} // Keep this goroutine around so that it will appear in the crash dump. } else {panic() } } elseif .err == errGoexit {// Already in the process of goexit, no need to call again } else {// Normal returnfor , := range .chans { <- Result{.val, .err, .dups > 0} } } }()func() {deferfunc() {if ! {// Ideally, we would wait to take a stack trace until we've determined // whether this is a panic or a runtime.Goexit. // // Unfortunately, the only way we can distinguish the two is to see // whether the recover stopped the goroutine from terminating, and by // the time we know that, the part of the stack trace relevant to the // panic has been discarded.if := recover(); != nil { .err = newPanicError() } } }() .val, .err = () = true }()if ! { = true }}// Forget tells the singleflight to forget about a key. Future calls// to Do for this key will call the function rather than waiting for// an earlier call to complete.func ( *Group) ( string) { .mu.Lock()delete(.m, ) .mu.Unlock()}
The pages are generated with Goldsv0.8.4. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.