Sure, here is the bigger picture. I have a Call function for RPC over HTTP. Many goroutines may make calls concurrently, and I want to merge multiple requests into one bulk request automatically, so only one HTTP request is needed for them. The RPC target device can not handle that many simultaneous HTTP connections. While a bulk request is running, more calls can build up for the next one. When starting a new bulk request, I want to collect all available calls to merge them into one request.
func (a *Agent) Call(ctx context.Context /*more args*/) (any, error) {
request := // ... build request from args
a.once.Do(func() {
a.startSem = make(chan struct{}, 1)
a.more = make(chan methodCall)
})
ch := make(chan callReturnValues, 1)
call := methodCall{request: request, ctx: ctx, retCh: ch}
select {
case a.startSem <- struct{}{}:
go a.performCalls(call)
case a.more <- call:
case <-ctx.Done():
return nil, ctx.Err()
}
resp := <-ch
return resp.result, resp.err
}
func (a *Agent) performCalls(call0 methodCall) {
defer func() { <-a.startSem }()
calls := []methodCall{call0}
// collect more ready calls
func() {
for {
select {
case call := <-a.more:
calls = append(calls, call)
default:
return
}
}
}()
// while the remaining function body is running, new requests get ready.
// one of them will acquire startSem, others will get passed to more
// create a bulk request from calls
// do a single HTTP request for it
// return results to each retCh
}