Is there a resource leak here?

func First(query string, replicas ...Search) Result { c := make(chan Result) searchReplica := func(i int) { c <- replicas[i](query) } for i := range replicas { go searchReplica(i) } return <-c } 

This feature is from Rob Pike slides on go concurrency templates in 2012. I think there is a resource leak in this function. When the function returns after the first send and receive pair occurs on channel c, other go programs will try to send to channel c. So there is a resource leak. Does anyone know that a golang can well confirm this? And how can I detect this leak using what golang tool?

+5
source share
1 answer

Yes, you are right (the link here is a link to the slide ). In the above code, only one running goroutine will be completed, the rest will hang when trying to send on channel c .

Detailing:

  • c - unbuffered channel
  • there is only one receive operation in the return ,
  • A new goroutine is launched for each replicas item.
  • every running goroutine sends a value on channel c
  • since only 1 gets from it, one goroutine will be able to send a value to it, the rest will be blocked forever

Note that depending on the number of replicas elements (which is len(replicas) ):

  • if it is 0 : First() will block forever (no one sends anything to c )
  • if it is 1 : will work as expected
  • if it is > 1 : then it is a resource leak

The following modified version will not flow goroutines using non-blocking send (using select with default branch):

 searchReplica := func(i int) { select { case c <- replicas[i](query): default: } } 

The first goroutine ready for the result will send it via channel c , which will be received by goroutine under the control of First() in the return . All other goroutines, when they receive the result, will try to send over the channel and "see" that it is not ready (sending is blocked because no one is ready to accept from it), the default branch will be selected, and thus the goroutine will end normally .

Another way to fix this would be to use a buffer channel:

 c := make(chan Result, len(replicas)) 

And thus, send operations will not be blocked. And, of course, only one (first sent) value will be received from the channel.

Note that a solution with any of the above fixes will still block if len(replicas) is 0 . To avoid this, First() should explicitly check this, for example:

 func First(query string, replicas ...Search) Result { if len(replicas) == 0 { return Result{} } // ...rest of the code... } 

Some tools / resources for leak detection:

+11
source

Source: https://habr.com/ru/post/1276247/


All Articles