github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/syncutil/singleflight/singleflight.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  // Copyright 2013 The Go Authors. All rights reserved.
    12  // Use of this source code is governed by a BSD-style
    13  // license that can be found in licenses/BSD-golang.txt.
    14  
    15  // This code originated in Go's internal/singleflight package.
    16  
    17  // Package singleflight provides a duplicate function call suppression
    18  // mechanism.
    19  package singleflight
    20  
    21  import (
    22  	"sync"
    23  
    24  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    25  )
    26  
    27  // call is an in-flight or completed singleflight.Do call
    28  type call struct {
    29  	wg sync.WaitGroup
    30  
    31  	// These fields are written once before the WaitGroup is done
    32  	// and are only read after the WaitGroup is done.
    33  	val interface{}
    34  	err error
    35  
    36  	// These fields are read and written with the singleflight
    37  	// mutex held before the WaitGroup is done, and are read but
    38  	// not written after the WaitGroup is done.
    39  	dups  int
    40  	chans []chan<- Result
    41  }
    42  
    43  // Group represents a class of work and forms a namespace in
    44  // which units of work can be executed with duplicate suppression.
    45  type Group struct {
    46  	mu syncutil.Mutex   // protects m
    47  	m  map[string]*call // lazily initialized
    48  }
    49  
    50  // Result holds the results of Do, so they can be passed
    51  // on a channel.
    52  type Result struct {
    53  	Val    interface{}
    54  	Err    error
    55  	Shared bool
    56  }
    57  
    58  // Do executes and returns the results of the given function, making
    59  // sure that only one execution is in-flight for a given key at a
    60  // time. If a duplicate comes in, the duplicate caller waits for the
    61  // original to complete and receives the same results.
    62  // The return value shared indicates whether v was given to multiple callers.
    63  func (g *Group) Do(
    64  	key string, fn func() (interface{}, error),
    65  ) (v interface{}, shared bool, err error) {
    66  	g.mu.Lock()
    67  	if g.m == nil {
    68  		g.m = make(map[string]*call)
    69  	}
    70  	if c, ok := g.m[key]; ok {
    71  		c.dups++
    72  		g.mu.Unlock()
    73  		c.wg.Wait()
    74  		return c.val, true, c.err
    75  	}
    76  	c := new(call)
    77  	c.wg.Add(1)
    78  	g.m[key] = c
    79  	g.mu.Unlock()
    80  
    81  	g.doCall(c, key, fn)
    82  	return c.val, c.dups > 0, c.err
    83  }
    84  
    85  // DoChan is like Do but returns a channel that will receive the results when
    86  // they are ready. The method also returns a boolean specifying whether the
    87  // caller's fn function will be called or not. This return value lets callers
    88  // identify a unique "leader" for a flight.
    89  //
    90  // NOTE: DoChan makes it possible to initiate or join a flight while holding a
    91  // lock without holding it for the duration of the flight. A common usage
    92  // pattern is:
    93  // 1. Check some datastructure to see if it contains the value you're looking
    94  // for.
    95  // 2. If it doesn't, initiate or join a flight to produce it.
    96  //
    97  // Step one is expected to be done while holding a lock. Modifying the
    98  // datastructure in the callback is expected to need to take the same lock. Once
    99  // a caller proceeds to step two, it likely wants to keep the lock until
   100  // DoChan() returned a channel, in order to ensure that a flight is only started
   101  // before any modifications to the datastructure occurred (relative to the state
   102  // observed in step one). Were the lock to be released before calling DoChan(),
   103  // a previous flight might modify the datastructure before our flight began.
   104  func (g *Group) DoChan(key string, fn func() (interface{}, error)) (<-chan Result, bool) {
   105  	ch := make(chan Result, 1)
   106  	g.mu.Lock()
   107  	if g.m == nil {
   108  		g.m = make(map[string]*call)
   109  	}
   110  	if c, ok := g.m[key]; ok {
   111  		c.dups++
   112  		c.chans = append(c.chans, ch)
   113  		g.mu.Unlock()
   114  		return ch, false
   115  	}
   116  	c := &call{chans: []chan<- Result{ch}}
   117  	c.wg.Add(1)
   118  	g.m[key] = c
   119  	g.mu.Unlock()
   120  
   121  	go g.doCall(c, key, fn)
   122  
   123  	return ch, true
   124  }
   125  
   126  // doCall handles the single call for a key.
   127  func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
   128  	c.val, c.err = fn()
   129  	c.wg.Done()
   130  
   131  	g.mu.Lock()
   132  	delete(g.m, key)
   133  	for _, ch := range c.chans {
   134  		ch <- Result{c.val, c.err, c.dups > 0}
   135  	}
   136  	g.mu.Unlock()
   137  }
   138  
   139  var _ = (*Group).Forget
   140  
   141  // Forget tells the singleflight to forget about a key.  Future calls
   142  // to Do for this key will call the function rather than waiting for
   143  // an earlier call to complete.
   144  func (g *Group) Forget(key string) {
   145  	g.mu.Lock()
   146  	delete(g.m, key)
   147  	g.mu.Unlock()
   148  }
   149  
   150  // NumCalls returns the number of in-flight calls for a given key.
   151  func (g *Group) NumCalls(key string) int {
   152  	g.mu.Lock()
   153  	defer g.mu.Unlock()
   154  	if c, ok := g.m[key]; ok {
   155  		return c.dups + 1
   156  	}
   157  	return 0
   158  }