// Copyright 2013 The Go Authors. All rights reserved.1// Use of this source code is governed by a BSD-style2// license that can be found in the LICENSE file.34// Package singleflight provides a duplicate function call suppression5// mechanism.6package singleflight78import (9"bytes"10"errors"11"fmt"12"runtime"13"runtime/debug"14"sync"15)1617// errGoexit indicates the runtime.Goexit was called in18// the user given function.19var errGoexit = errors.New("runtime.Goexit was called")2021// A panicError is an arbitrary value recovered from a panic22// with the stack trace during the execution of given function.23type panicError struct {24value any25stack []byte26}2728// Error implements error interface.29func (p *panicError) Error() string {30return fmt.Sprintf("%v\n\n%s", p.value, p.stack)31}3233func newPanicError(v any) error {34stack := debug.Stack()3536// The first line of the stack trace is of the form "goroutine N [status]:"37// but by the time the panic reaches Do the goroutine may no longer exist38// and its status will have changed. Trim out the misleading line.39if line := bytes.IndexByte(stack[:], '\n'); line >= 0 {40stack = stack[line+1:]41}42return &panicError{value: v, stack: stack}43}4445// call is an in-flight or completed singleflight.Do call46type call[T any] struct {47wg sync.WaitGroup4849// These fields are written once before the WaitGroup is done50// and are only read after the WaitGroup is done.51val T52err error5354// forgotten indicates whether Forget was called with this call's key55// while the call was still in flight.56forgotten bool5758// These fields are read and written with the singleflight59// mutex held before the WaitGroup is done, and are read but60// not written after the WaitGroup is done.61dups int62chans []chan<- Result[T]63}6465// Group represents a class of work and forms a namespace in66// which units of work can be executed with duplicate suppression.67type Group[T any] struct {68mu sync.Mutex // protects m69m map[string]*call[T] // lazily initialized70}7172// Result holds the results of Do, so they can be passed73// on a channel.74type Result[T any] struct {75Val T76Err error77Shared bool78}7980// Do executes and returns the results of the given function, making81// sure that only one execution is in-flight for a given key at a82// time. If a duplicate comes in, the duplicate caller waits for the83// original to complete and receives the same results.84// The return value shared indicates whether v was given to multiple callers.85func (g *Group[T]) Do(key string, fn func() (T, error)) (v T, err error, shared bool) {86g.mu.Lock()87if g.m == nil {88g.m = make(map[string]*call[T])89}90if c, ok := g.m[key]; ok {91c.dups++92g.mu.Unlock()93c.wg.Wait()9495if e, ok := c.err.(*panicError); ok {96panic(e)97} else if c.err == errGoexit {98runtime.Goexit()99}100return c.val, c.err, true101}102c := new(call[T])103c.wg.Add(1)104g.m[key] = c105g.mu.Unlock()106107g.doCall(c, key, fn)108return c.val, c.err, c.dups > 0109}110111// DoChan is like Do but returns a channel that will receive the112// results when they are ready.113//114// The returned channel will not be closed.115func (g *Group[T]) DoChan(key string, fn func() (T, error)) <-chan Result[T] {116ch := make(chan Result[T], 1)117g.mu.Lock()118if g.m == nil {119g.m = make(map[string]*call[T])120}121if c, ok := g.m[key]; ok {122c.dups++123c.chans = append(c.chans, ch)124g.mu.Unlock()125return ch126}127c := &call[T]{chans: []chan<- Result[T]{ch}}128c.wg.Add(1)129g.m[key] = c130g.mu.Unlock()131132go g.doCall(c, key, fn)133134return ch135}136137// doCall handles the single call for a key.138func (g *Group[T]) doCall(c *call[T], key string, fn func() (T, error)) {139normalReturn := false140recovered := false141142// use double-defer to distinguish panic from runtime.Goexit,143// more details see https://golang.org/cl/134395144defer func() {145// the given function invoked runtime.Goexit146if !normalReturn && !recovered {147c.err = errGoexit148}149150c.wg.Done()151g.mu.Lock()152defer g.mu.Unlock()153if !c.forgotten {154delete(g.m, key)155}156157if e, ok := c.err.(*panicError); ok {158// In order to prevent the waiting channels from being blocked forever,159// needs to ensure that this panic cannot be recovered.160if len(c.chans) > 0 {161go panic(e)162select {} // Keep this goroutine around so that it will appear in the crash dump.163} else {164panic(e)165}166} else if c.err == errGoexit {167// Already in the process of goexit, no need to call again168} else {169// Normal return170for _, ch := range c.chans {171ch <- Result[T]{c.val, c.err, c.dups > 0}172}173}174}()175176func() {177defer func() {178if !normalReturn {179// Ideally, we would wait to take a stack trace until we've determined180// whether this is a panic or a runtime.Goexit.181//182// Unfortunately, the only way we can distinguish the two is to see183// whether the recover stopped the goroutine from terminating, and by184// the time we know that, the part of the stack trace relevant to the185// panic has been discarded.186if r := recover(); r != nil {187c.err = newPanicError(r)188}189}190}()191192c.val, c.err = fn()193normalReturn = true194}()195196if !normalReturn {197recovered = true198}199}200201// Forget tells the singleflight to forget about a key. Future calls202// to Do for this key will call the function rather than waiting for203// an earlier call to complete.204func (g *Group[T]) Forget(key string) {205g.mu.Lock()206if c, ok := g.m[key]; ok {207c.forgotten = true208}209delete(g.m, key)210g.mu.Unlock()211}212213214