학습일지/Language

Go - Context 정리

inspirit941 2022. 5. 26. 01:16
반응형

Context Package

https://youtu.be/mgJMIZsWfB4

 

스크린샷 2022-05-24 오후 5 04 03

  • go의 다양한 패키지에서 사용되고 있는 Context

 

스크린샷 2022-05-24 오후 5 04 30

Context의 기능은 크게 세 가지.

  • Deadlines
  • Concellation Signals
  • Request-scoped values

Deadline - withTimeout, withDeadline

 

스크린샷 2022-05-24 오후 6 31 49

  • WithDeadline: 시작 시간과 끝 시간을 정하고, 그 시간동안만 실행되도록 (endtime)
  • WithTimeout: 시작 시간을 정하고, 얼마의 시간이 지난 뒤 종료되도록. (Duration)
const shortDuration = 1 * time.Millisecond

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
    defer cancel()

    select {
        case <- time.After(1 * time.Second):
            fmt.Println("OverSlept")
        case <- ctx.Done():
            fmt.Println(ctx.Err()) // prints "context deadline exceeded"
    }
}

Cancellation

 

스크린샷 2022-05-24 오후 6 37 36

func main() {
    ch := make(chan struct{})

    run := func(ctx context.Context) {
        n := 1
        for {
            select {
                case <- ctx.Done(): // 2. ctx is canceled. close ch
                    fmt.Println("exiting")
                    close(ch)
                    return
                default:
                    time.Sleep(time.Millisecond * 300)   
                    fmt.Println(n)
                    n ++
            }
        }
    }

    ctx, cancel := context.WithCancel(context.Background())
    go func() {
        time.Sleep(time.Second * 2)
        fmt.Println("goodbye")
        cancel() // 1. cancel ctx
    }()

    go run(ctx)

    fmt.Println("waiting to cancel ...")

    <- ch // 3. ch is closed, exit. (graceful shutdown)
    fmt.Println("bye")
}

Request-scope Values

 

스크린샷 2022-05-24 오후 8 08 05

type jwt string
const auth jwt = "JWT"

func main() {
    ctx := context.WithValue(context.Background(), auth, "Bearer hi") // key - value 형태로 context에 저장.

    bearer := ctx.Value(auth)
    str, ok := bearer.(string) // context의 value 가져와서 타입 캐스팅.
    if !ok {
        log.Fatalln("not a string")
    }
    fmt.Println("values: " , str)
}

Best Practise

 

  • context는 first argument로 선언한다. microservice의 경우 대부분의 request에서 context가 첫번째 argument로 들어가 있음
  • memory leak 방지를 위해 cancel function이 제대로 호출되는지 확인.
  • WithValue를 너무 많이 쓰는 것은 지양한다.

https://youtu.be/GYtZsyT4gKo

에서 제공한 예시코드.

package main

import (
    "math/rand"
    "time"
    "fmt"
)


const (
    minLatency = 10
    maxLatency = 5000
    timeout = 3000
)

func main() {
    // flight routes를 찾는 프로그램
    // mock backend / db 사용 예정

    // context를 사용해서 cancel 로직을 서로 다른 goroutine process에 propagate signaling하는 것이 목표
    // 1. 예컨대 응답이 3초 넘어가면 cancel하도록 만들고 싶다면
    rootCtx := context.Background()
    ctx, cancel = context.WithTimeout(rootCtx, time.Duration(timeout) * time.Millisecond)
    defer cancel()

    // 2. 터미널에서 Interrupt할 경우 context cancel 실행
    sig := make(chan os.Signal)
    signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-sig
        fmt.Println("aborting due to interrupt") 
        cancel()
        exit(0)
    }

    fmt.Println("starting to search from nyc - london...")
    res, err := Search(ctx, "nyc", "london")
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println("got result", res)
}

func Search(ctx context.Context, from, to string) ([]string, error) {
    // slowSearch
    // ctx.Done() 이 close되었는지 확인
    res := make(chan []string)
    go func() {
        result, err := slowSearch(from, to)
        res <- result
        close(res)
    }()

    // wait for 2 events: 응답값은 둘 중 하나다. timeout이거나 리턴값이거나.
    for {
        select {
        case dst := <-res:
            return dst, nil
        case <- ctx.Done():
            return []string{}, ctx.Err()
        }
    }
    return []string{}, nil
}

// search는 굉장히 느린 함수로, 문자열 slice를 리턴한다고 가정한다.
func slowSearch(from, to string) ([]string, error) {
    // sleep for a random period btwn min / max latency

    rand.Seed(time.Now().Unix())
    latency := rand.Intn(maxLatency - minLatency + 1) - minLatency
    fmt.Println(latency)
    time.Sleep(time.Duration(latency) * time.Millisecond)

    return []string{from+"-"+to+"-british airways-11am", from+"-"+to+"-delta airlines-12am"}, nil
}

 

 

반응형