2

I have created an API, which after processing the request sends a response and starts a background goroutine which logs some messages. I have used 'defer' to execute goroutine after the API request has been handled.

Following are the code snippets:

sendResponse Pseudocode:

{
  ...
  res.Header().Add("Content-Type", "application/json")
  json.NewEncoder(res).Encode(response)
}

Response struct:

type Response struct {
   StatusCode int16  `json:"statusCode"`
   Message    string `json:"message"`
}

API Body:

{ 
 ...
 defer logMsgs()
 sendAPIResponse()
 return
}

logMsgs Pseudocode:

func logMsgs(){
   time.Sleep(10*time.Seconds)
   wg := sync.Waitgroup{}
   wg.Add(1)
   go func(){
      for i = 1 to 10
         print(i)
      wg.Done()
   }()
   wg.Wait()
}

Expected Outcome is to receive the API response and after few seconds(here 10s) value of 'i' is printed from 1 to 10.

But in Actual Outcome, API response is received after 10s.

As per my understanding defer functions are called after the surrounding (or current/enclosing) function terminates or returns.

So I must first receive the API response and later values of 'i' must be logged. But in my case I am receiving the response after some seconds(10s) are elapsed. This would be a major issue if value of 'i' is quite large.

It will be helpful if someone explains this abnormal behavior and provides possible solutions. Thanks

qa805542
  • 65
  • 4

1 Answers1

1

I have used 'defer' to execute goroutine after the API request has been handled.

defer does not launch a goroutine. It executes the deferred function on the calling goroutine. And it's very much possible that the response is cached and data written to it is not flushed until you return from your handler.

If you want to execute the logging in a new goroutine, use the go keyword like this:

defer func() {
    go logMsgs()
}()
sendAPIResponse()
return

Note however that after returning from the handler, launched goroutines cannot access request and response specific objects as they may be reused for new requests. So save / pass everything that the logger needs in order to safely use them after returning from the handler.

icza
  • 389,944
  • 63
  • 907
  • 827
  • Thanks for the soln. It worked. But I still didn't understand, why it worked. Why we have to call logMsgs() as goroutine? – qa805542 Apr 16 '22 at 16:34
  • 1
    @qa805542 If you don't run it in a goroutine, the enclosing handler will not end until `logMsgs()` runs, effectively blocking the response (the response will not be flushed until the handler returns). If you run it in a goroutine (using `go`), then the handler can return while `logMsgs()` will run in a new goroutine. When the handler returns, the response can be sent (flushed) to the client. – icza Apr 16 '22 at 17:00
  • I wanted to confirm that if we defer a goroutine, then after enclosing function returns the deferred goroutine acts as an independent thread and the enclosing function is flushed from the memory, ie it doesn't exist in memory stack. – qa805542 Jun 03 '22 at 06:54