jackhammer

Utilities for Go
Log | Files | Refs | README | LICENSE

channel.go (3521B)


      1 package channel
      2 
      3 import (
      4 	"context"
      5 	"reflect"
      6 	"slices"
      7 	"time"
      8 )
      9 
     10 // Drain the provided channel.
     11 func Drain[T any](ch <-chan T) {
     12 	for range ch {
     13 	}
     14 }
     15 
     16 // TryReceive selects on a channel returning true if there was a value.
     17 func TryReceive[T any](ch <-chan T) (t T, ok bool) {
     18 	select {
     19 	case t = <-ch:
     20 		ok = true
     21 	default:
     22 	}
     23 	return t, ok
     24 }
     25 
     26 // TrySend attempts a sends on a channel returning true if success.
     27 func TrySend[T any](ch chan<- T, t T) bool {
     28 	select {
     29 	case ch <- t:
     30 		return true
     31 	default:
     32 		return false
     33 	}
     34 }
     35 
     36 // ReceiveIn selects on the provided channel up to duration `d` and returns the value.
     37 // If the duration elapses, the function returns the zero value of `T` and false.
     38 func ReceiveIn[T any](ch <-chan T, d time.Duration) (T, bool) {
     39 	timer := time.NewTimer(d)
     40 	select {
     41 	case t := <-ch:
     42 		return t, true
     43 	case <-timer.C:
     44 		return *new(T), false
     45 	}
     46 }
     47 
     48 // Collect all values from a channel into a slice.
     49 func Collect[T any](in <-chan T) []T {
     50 	out := []T{}
     51 	for t := range in {
     52 		out = append(out, t)
     53 	}
     54 	return out
     55 }
     56 
     57 // CollectAllCtx buffers every value received value into a slice until all input channels are closed
     58 // or the context is cancelled.
     59 func CollectAllCtx[T any](ctx context.Context, chs ...chan T) []T {
     60 	out := []T{}
     61 	for v := range FanInCtx(ctx, chs...) {
     62 		out = append(out, v)
     63 	}
     64 	return out
     65 }
     66 
     67 // CollectAll buffers every value received value into a slice until all input channels are closed.
     68 func CollectAll[T any](chs ...chan T) (out []T) {
     69 	return CollectAllCtx(context.Background(), chs...)
     70 }
     71 
     72 // BufferedSend uses slice allocation to buffer items providing a dynamically
     73 // buffered channel send. This send will never block.
     74 //
     75 // If the channel is open, we send on it.
     76 // If the channel is full, we select off the slice, append to it and send it
     77 // back onto the channel.
     78 //
     79 // The loop will drain the channel until it becomes available to send. After
     80 // the function returns, the value v is guaranteed to be sent on the channel,
     81 // however it is not guaranteed that this is the only value sent.
     82 //
     83 // The input channel MUST have capacity of 1.
     84 func BufferedSend[S ~[]E, E any](in chan S, v E) {
     85 	if cap(in) != 1 {
     86 		panic("BufferedSend is only valid for single buffered channels")
     87 	}
     88 
     89 	var items S = []E{v}
     90 
     91 	for {
     92 		select {
     93 		case buf := <-in:
     94 			items = append(buf, items...)
     95 		case in <- items:
     96 			return
     97 		}
     98 	}
     99 }
    100 
    101 // FanIn returns a channel that emits every value received from the input channel set.
    102 // The output channel is closed when all input channels are closed.
    103 func FanIn[T any](chs ...chan T) chan T {
    104 	return FanInCtx(context.Background(), chs...)
    105 }
    106 
    107 // FanInCtx returns a channel that emits every value received from the input channel set.
    108 // The output channel is closed when all input channels are closed, or when the context
    109 // is cancelled.
    110 func FanInCtx[T any](ctx context.Context, chs ...chan T) chan T {
    111 	out := make(chan T)
    112 
    113 	go func() {
    114 		defer close(out)
    115 
    116 		cases := make([]reflect.SelectCase, 0, len(chs)+1)
    117 
    118 		cases = append(cases, reflect.SelectCase{
    119 			Dir:  reflect.SelectRecv,
    120 			Chan: reflect.ValueOf(ctx.Done()),
    121 		})
    122 
    123 		for _, ch := range chs {
    124 			cases = append(cases, reflect.SelectCase{
    125 				Dir:  reflect.SelectRecv,
    126 				Chan: reflect.ValueOf(ch),
    127 			})
    128 		}
    129 
    130 		for len(cases) > 1 {
    131 			ii, recv, ok := reflect.Select(cases)
    132 			if ok {
    133 				out <- recv.Interface().(T)
    134 			} else {
    135 				cases = slices.Delete(cases, ii, ii+1)
    136 			}
    137 			if ii == 0 {
    138 				// This is always the ctx.Done channel.
    139 				return
    140 			}
    141 		}
    142 	}()
    143 
    144 	return out
    145 }