쿼카러버의 기술 블로그

[golang] time.After이란? (memory leak 가능성) 본문

[Golang]

[golang] time.After이란? (memory leak 가능성)

quokkalover 2021. 8. 30. 23:27

context를 사용해서 웹서버가 2초 안에 꺼지게 되면 request가 cancel되는 코드를 짜는 예제를 보면 time.After가 많이 쓰인다.

다른 branch가 더 빨리 run하게 되면, timer는 release되지 않는 식으로 select 문에 많이 쓰인다.

그리고 어떻게 작동하는지 확인 하기 위해 아래 예제를 보면,
time.After(2*time.Second)는 select문이 도달한 이후 시점 부터 계산되고, function이 끝나는 것을 확인할 수 있다.

 

 

Memory Leak이 발생하지 않는 케이스

select {
  case <-time.After(time.Second):
     // do something after 1 second.
}

 

위 코드의 경우에는 select statement이 끝날 한 가지 경우밖에 없기 때문에 memory leak이 발생하지 않는다.

timer.After()함수에 의해 생성된 timer는  select statement가 끝나면서 같이 멈추고, resource도 free된다.

 

]

Memory Leak이 발생할 수 있는 time.After() function

select {
  case <-time.After(time.Second):
     // do something after 1 second.
  case <-ctx.Done():
     // do something when context is finished.
     // resources created by the time.After() will not be garbage collected
  }

위와 같이 코드를 작성하는 경우가 매우 많다.

만약 타이머가 먼저 끝나게되면 GC가 정상적으로 작동하지만, ctx.Done()이 먼저 발생할 경우, time.After()에 의해 생성된 timer는 멈추지 않고, 리소스는 release되지 않는다. = memory leak

 

 

 

Improved usage of the time.After() function

따라서, timer.After()을 사용해서 타이머를 만드는 것 보다는 아래처럼 하는 것이 가장 바람직 하다.

  delay := time.NewTimer(time.Second)


  select {
  case <-delay.C:
     // do something after one second.
  case <-ctx.Done():
     // do something when context is finished and stop the timer.
     if !delay.Stop() {
        // if the timer has been stopped then read from the channel.
        <-delay.C
     }    
  }

위와 같은 경우는 어떤 case가 됐든, 타이머가 GC에 의해 수거된다. 

ctx.Done()이 먼저 발생해도, delay.Stop()에 의해서 resource가 release될 것이기 때문이다. 

또한 ctx.Done()이 발생하자마자 timer가 expire되는 경우도 있을 수 있기 때문에, 한번더 확인하는 코드도 추가된다. 

 

 

 

 

https://www.arangodb.com/2020/09/a-story-of-a-memory-leak-in-go-how-to-properly-use-time-after/

Comments