The net/http library is the caller in this case,
so that would be consistent with the rule that callers
should start goroutines.
Request handlers are a bit of a special case too, in that
they are a framework for dispatching tasks to be worked on;
what is main() for a command line program is the request
handler for a webserver. It seems fair that there is some
concurrency coordination happening at the top level.
Yeah, but it doesn't spin up a thousand for every request. And it doesn't require you to start them. It starts them under the covers as part of the API.
The article says almost exactly the opposite, and I agree.
Your API should present all of the things it does as synchronous functions/methods. If callers want to run them asynchronously, they can easily provide their own goroutine to do so, including whatever lifecycle management makes sense for them.
The concrete example was
// Do this
func (t *type) Run(ctx context.Context) { ... }
// Not this
func (t *type) Start() { ... }
func (t *type) Cancel() { ... }
This is generally good advice which stops a whole class of extremely common and very tricky to debug orchestration bugs outright.
Request handlers are a bit of a special case too, in that they are a framework for dispatching tasks to be worked on; what is main() for a command line program is the request handler for a webserver. It seems fair that there is some concurrency coordination happening at the top level.