// 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. var errGoexit = 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. type panicError struct { value any stack []byte } // Error implements error interface. func ( *panicError) () string { return fmt.Sprintf("%v\n\n%s", .value, .stack) } func ( *panicError) () error { , := .value.(error) if ! { return nil } 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 call type call struct { wg sync.WaitGroup // These fields are written once before the WaitGroup is done // and are only read after the WaitGroup is done. val any err error // 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. dups int chans []chan<- Result } // Group represents a class of work and forms a namespace in // which units of work can be executed with duplicate suppression. type Group struct { mu sync.Mutex // protects m m map[string]*call // lazily initialized } // Result holds the results of Do, so they can be passed // on a channel. type Result struct { Val any Err error Shared bool } // 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() } else if .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)) <-chan Result { := make(chan Result, 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/134395 defer func() { // the given function invoked runtime.Goexit if ! && ! { .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. if len(.chans) > 0 { go panic() select {} // Keep this goroutine around so that it will appear in the crash dump. } else { panic() } } else if .err == errGoexit { // Already in the process of goexit, no need to call again } else { // Normal return for , := range .chans { <- Result{.val, .err, .dups > 0} } } }() func() { defer func() { 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() }