github.com/git-amp/amp-sdk-go@v0.7.5/stdlib/utils/channels.go (about)

     1  package utils
     2  
     3  import (
     4  	"context"
     5  	"reflect"
     6  	"sync/atomic"
     7  	"time"
     8  )
     9  
    10  // ContextFromChan creates a context that finishes when the provided channel
    11  // receives or is closed.
    12  func ContextFromChan(chStop <-chan struct{}) (context.Context, context.CancelFunc) {
    13  	ctx, cancel := context.WithCancel(context.Background())
    14  	go func() {
    15  		select {
    16  		case <-chStop:
    17  			cancel()
    18  		case <-ctx.Done():
    19  		}
    20  	}()
    21  	return ctx, cancel
    22  }
    23  
    24  // WaitGroupChan creates a channel that closes when the provided sync.WaitGroup is done.
    25  type WaitGroupChan struct {
    26  	i         int
    27  	x         int
    28  	chAdd     chan wgAdd
    29  	chWait    chan struct{}
    30  	chCtxDone <-chan struct{}
    31  	chStop    chan struct{}
    32  	waitCalls uint32
    33  }
    34  
    35  type wgAdd struct {
    36  	i   int
    37  	err chan string
    38  }
    39  
    40  func NewWaitGroupChan(ctx context.Context) *WaitGroupChan {
    41  	wg := &WaitGroupChan{
    42  		chAdd:  make(chan wgAdd),
    43  		chWait: make(chan struct{}),
    44  		chStop: make(chan struct{}),
    45  	}
    46  	if ctx != nil {
    47  		wg.chCtxDone = ctx.Done()
    48  	}
    49  
    50  	go func() {
    51  		var done bool
    52  		for {
    53  			select {
    54  			case <-wg.chCtxDone:
    55  				if !done {
    56  					close(wg.chWait)
    57  				}
    58  				return
    59  			case <-wg.chStop:
    60  				if !done {
    61  					close(wg.chWait)
    62  				}
    63  				return
    64  			case wgAdd := <-wg.chAdd:
    65  				if done {
    66  					wgAdd.err <- "WaitGroupChan already finished. Do you need to add a bounding wg.Add(1) and wg.Done()?"
    67  					return
    68  				}
    69  				wg.i += wgAdd.i
    70  				if wg.i < 0 {
    71  					wgAdd.err <- "called Done() too many times"
    72  					close(wg.chWait)
    73  					return
    74  				} else if wg.i == 0 {
    75  					done = true
    76  					close(wg.chWait)
    77  				}
    78  				wgAdd.err <- ""
    79  			}
    80  		}
    81  	}()
    82  
    83  	return wg
    84  }
    85  
    86  func (wg *WaitGroupChan) Close() {
    87  	close(wg.chStop)
    88  }
    89  
    90  func (wg *WaitGroupChan) Add(i int) {
    91  	if atomic.LoadUint32(&wg.waitCalls) > 0 {
    92  		panic("cannot call Add() after Wait()")
    93  	}
    94  	ch := make(chan string)
    95  	select {
    96  	case <-wg.chCtxDone:
    97  	case <-wg.chStop:
    98  	case wg.chAdd <- wgAdd{i, ch}:
    99  		err := <-ch
   100  		if err != "" {
   101  			panic(err)
   102  		}
   103  	}
   104  }
   105  
   106  func (wg *WaitGroupChan) Done() {
   107  	ch := make(chan string)
   108  	select {
   109  	case <-wg.chCtxDone:
   110  	case <-wg.chStop:
   111  	case <-wg.chWait:
   112  	case wg.chAdd <- wgAdd{-1, ch}:
   113  		err := <-ch
   114  		if err != "" {
   115  			panic(err)
   116  		}
   117  	}
   118  }
   119  
   120  func (wg *WaitGroupChan) Wait() <-chan struct{} {
   121  	atomic.StoreUint32(&wg.waitCalls, 1)
   122  	return wg.chWait
   123  }
   124  
   125  // CombinedContext creates a context that finishes when any of the provided
   126  // signals finish.  A signal can be a `context.Context`, a `chan struct{}`, or
   127  // a `time.Duration` (which is transformed into a `context.WithTimeout`).
   128  func CombinedContext(signals ...interface{}) (context.Context, context.CancelFunc) {
   129  	ctx, cancel := context.WithCancel(context.Background())
   130  	if len(signals) == 0 {
   131  		return ctx, cancel
   132  	}
   133  	signals = append(signals, ctx)
   134  
   135  	var cases []reflect.SelectCase
   136  	var cancel2 context.CancelFunc
   137  	for _, signal := range signals {
   138  		var ch reflect.Value
   139  
   140  		switch sig := signal.(type) {
   141  		case context.Context:
   142  			ch = reflect.ValueOf(sig.Done())
   143  		case <-chan struct{}:
   144  			ch = reflect.ValueOf(sig)
   145  		case chan struct{}:
   146  			ch = reflect.ValueOf(sig)
   147  		case time.Duration:
   148  			var ctxTimeout context.Context
   149  			ctxTimeout, cancel2 = context.WithTimeout(ctx, sig)
   150  			ch = reflect.ValueOf(ctxTimeout.Done())
   151  		default:
   152  			continue
   153  		}
   154  		cases = append(cases, reflect.SelectCase{Chan: ch, Dir: reflect.SelectRecv})
   155  	}
   156  
   157  	go func() {
   158  		defer cancel()
   159  		if cancel2 != nil {
   160  			defer cancel2()
   161  		}
   162  		_, _, _ = reflect.Select(cases)
   163  	}()
   164  
   165  	return context.WithCancel(ctx)
   166  }
   167  
   168  type ChanContext chan struct{}
   169  
   170  var _ context.Context = ChanContext(nil)
   171  
   172  func (ch ChanContext) Deadline() (deadline time.Time, ok bool) {
   173  	return time.Time{}, false
   174  }
   175  
   176  func (ch ChanContext) Done() <-chan struct{} {
   177  	return ch
   178  }
   179  
   180  func (ch ChanContext) Err() error {
   181  	select {
   182  	case <-ch:
   183  		return context.Canceled
   184  	default:
   185  		return nil
   186  	}
   187  }
   188  
   189  func (ch ChanContext) Value(key interface{}) interface{} {
   190  	return nil
   191  }