Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Job Queue in Golang

Bo-Yi Wu
October 19, 2019

Job Queue in Golang

1. what is the different unbuffered and buffered channel?
2. how to implement a job queue in golang?
3. how to stop the worker in a container?
4. Shutdown with Sigterm Handling
5. Canceling Workers without Context
6. Graceful shutdown with worker
7. How to auto-scaling build agent?
8. How to cancel the current Job?

Bo-Yi Wu

October 19, 2019
Tweet

More Decks by Bo-Yi Wu

Other Decks in Technology

Transcript

  1. Job Queue in Golang
    Bo-Yi Wu
    2019.10.19

    View Slide

  2. About me
    • Software Engineer in Mediatek
    • Member of Drone CI/CD Platform
    • Member of Gitea Platform
    • Member of Gin Golang Framework
    • Maintain Some GitHub Actions Plugins.
    • Teacher of Udemy Platform: Golang + Drone

    View Slide

  3. View Slide

  4. Queue (Open Source)
    3BCCJU.2
    /42

    View Slide

  5. Why not
    use Open Source?

    View Slide

  6. Single Service
    • Build in SQLite (LDAP, Job List)
    • Build in Cache (LRU)
    • Build for single binary
    • No third-party dependency

    View Slide

  7. Before talking about
    Job Queue

    View Slide

  8. Buffered
    vs
    Unbuffered
    http://bit.ly/understand-channel

    View Slide

  9. Goroutine
    func main() {
    go func() {
    fmt.Println("GO GO GO")
    }()
    time.Sleep(1 * time.Second)
    }

    View Slide

  10. Unbuffered
    make(chan bool)

    View Slide

  11. Unbuffered Channel
    func main() {
    c := make(chan bool)
    go func() {
    fmt.Println("GO GO GO")
    c }()
    }

    View Slide

  12. Unbuffered Channel
    func main() {
    c := make(chan bool)
    go func() {
    fmt.Println("GO GO GO")
    c }()
    }

    View Slide

  13. Unbuffered Channel
    func main() {
    c := make(chan bool)
    go func() {
    fmt.Println("GO GO GO")
    }()
    c }

    View Slide

  14. func main() {
    c := make(chan bool)
    go func() {
    fmt.Println("GO GO GO")
    c c }()
    time.Sleep(1 * time.Second)
    }
    Unbuffered Channel

    View Slide

  15. buffered
    make(chan bool, 1)

    View Slide

  16. Buffered channel
    func main() {
    c := make(chan bool, 1)
    go func() {
    fmt.Println("GO GO GO")
    }()
    c }

    View Slide

  17. Buffered channel
    func main() {
    c := make(chan bool, 1)
    go func() {
    fmt.Println("GO GO GO")
    }()
    c }

    View Slide

  18. Buffered channel
    func main() {
    c := make(chan bool, 1)
    go func() {
    fmt.Println("GO GO GO")
    c }()
    }

    View Slide

  19. How to implement
    Job Queue in Go

    View Slide

  20. Sometimes you don’t need
    A job queue
    go process("job01")

    View Slide

  21. func worker(jobChan for job := range jobChan {
    process(job)
    }
    }
    // make a channel with a capacity of 1024.
    jobChan := make(chan Job, 1024)
    // start the worker
    go worker(jobChan)
    // enqueue a job
    jobChan

    View Slide

  22. func worker(jobChan for job := range jobChan {
    process(job)
    }
    }
    // make a channel with a capacity of 1024.
    jobChan := make(chan Job, 1024)
    // start the worker
    go worker(jobChan)
    // enqueue a job
    jobChan

    View Slide

  23. Block if there already are 1024 jobs
    jobChan := make(chan Job, 1024)

    View Slide

  24. Enqueue without blocking

    View Slide

  25. func Enqueue(job Job, jobChan chanselect {
    case jobChan return true
    default:
    return false
    }
    }
    if !Enqueue(job, job100) {
    Error(
    http.StatusServiceUnavailable,
    "max capacity reached",
    )
    return
    }

    View Slide

  26. Stopping the worker?

    View Slide

  27. func main() {
    ch := make(chan int, 2)
    go func() {
    ch ch }()
    for n := range ch {
    fmt.Println(n)
    }
    }

    View Slide

  28. func main() {
    ch := make(chan int, 2)
    go func() {
    ch ch close(ch)
    }()
    for n := range ch {
    fmt.Println(n)
    }
    }

    View Slide

  29. func main() {
    ch := make(chan int, 2)
    go func() {
    ch ch }()
    go func() {
    for n := range ch {
    fmt.Println(n)
    }
    }()
    time.Sleep(1 * time.Second)
    }

    View Slide

  30. View Slide

  31. Setup Consumer

    View Slide

  32. type Consumer struct {
    inputChan chan int
    jobsChan chan int
    }
    const PoolSize = 2
    func main() {
    // create the consumer
    consumer := Consumer{
    inputChan: make(chan int, 1),
    jobsChan: make(chan int, PoolSize),
    }
    }

    View Slide

  33. View Slide

  34. func (c *Consumer) queue(input int) {
    fmt.Println("send input value:", input)
    c.jobsChan }
    func (c *Consumer) worker(num int) {
    for job := range c.jobsChan {
    fmt.Println("worker:", num, " job value:", job)
    }
    }
    for i := 0; i < PoolSize; i++ {
    go consumer.worker(i)
    }

    View Slide

  35. consumer.queue(1)
    consumer.queue(2)
    consumer.queue(3)
    consumer.queue(4)
    blocking by poll size = 2

    View Slide

  36. Output (Poll Size = 2)
    send input value: 1
    send input value: 2
    send input value: 3
    worker: 0 job value: 1
    send input value: 4
    worker: 0 job value: 2
    worker: 0 job value: 3
    worker: 0 job value: 4

    View Slide

  37. rewrite queue func
    func (c *Consumer) queue(input int) bool {
    fmt.Println("send input value:", input)
    select {
    case c.jobsChan return true
    default:
    return false
    }
    }

    View Slide

  38. Output (Poll Size = 2)
    send input value: 1
    send input value: 2
    send input value: 3
    send input value: 4
    worker: 0 job value: 1
    worker: 0 job value: 2

    View Slide

  39. Shutdown with
    Sigterm Handling

    View Slide

  40. func WithContextFunc(ctx context.Context, f func()) context.Context {
    ctx, cancel := context.WithCancel(ctx)
    go func() {
    c := make(chan os.Signal)
    signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
    defer signal.Stop(c)
    select {
    case case f()
    cancel()
    }
    }()
    return ctx
    }

    View Slide

  41. View Slide

  42. func WithContextFunc(ctx context.Context, f func()) context.Context {
    ctx, cancel := context.WithCancel(ctx)
    go func() {
    c := make(chan os.Signal)
    signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
    defer signal.Stop(c)
    select {
    case case f()
    cancel()
    }
    }()
    return ctx
    }

    View Slide

  43. func (c Consumer) startConsumer(ctx context.Context) {
    for {
    select {
    case job := if ctx.Err() != nil {
    close(c.jobsChan)
    return
    }
    c.jobsChan case close(c.jobsChan)
    return
    }
    }
    }

    View Slide

  44. Cancel by ctx.Done() event
    func (c *Consumer) worker(num int) {
    for job := range c.jobsChan {
    fmt.Println("worker:", num, " job value:", job)
    }
    }

    View Slide

  45. Canceling Workers
    without Context

    View Slide

  46. cancelChan := make(chan struct{})
    go worker(jobChan, cancelChan)
    func worker(jobChan for {
    select {
    case return
    case job := process(job)
    }
    }
    }
    // to cancel the worker, close the cancel channel
    close(cancelChan)
    Create a cancel channel

    View Slide

  47. cancelChan := make(chan struct{})
    go worker(jobChan, cancelChan)
    func worker(jobChan for {
    select {
    case return
    case job := process(job)
    }
    }
    }
    // to cancel the worker, close the cancel channel
    close(cancelChan)
    Create a cancel channel
    close(cancelChan)

    View Slide

  48. Graceful shutdown
    with worker
    sync.WaitGroup

    View Slide

  49. View Slide

  50. wg := &sync.WaitGroup{}
    wg.Add(numberOfWorkers)
    // Start [PoolSize] workers
    for i := 0; i < PoolSize; i++ {
    go consumer.worker(i)
    }

    View Slide

  51. WaitGroup
    WaitGroup
    WaitGroup
    WaitGroup

    View Slide

  52. func (c Consumer) worker(wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range c.jobsChan {
    // handle the job event
    }
    }

    View Slide

  53. Add WaitGroup
    after Cancel Function

    View Slide

  54. func WithContextFunc(ctx context.Context, f func()) context.Context {
    ctx, cancel := context.WithCancel(ctx)
    go func() {
    c := make(chan os.Signal)
    signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
    defer signal.Stop(c)
    select {
    case case cancel()
    f()
    }
    }()
    return ctx
    }
    Add WaitGroup after Cancel Function

    View Slide

  55. wg := &sync.WaitGroup{}
    wg.Add(numberOfWorkers)
    ctx := signal.WithContextFunc(
    context.Background(),
    func() {
    wg.Wait()
    close(finishChan)
    },
    )
    go consumer.startConsumer(ctx)

    View Slide

  56. End of Program
    select {
    case case err := if err != nil {
    return err
    }
    }

    View Slide

  57. How to auto-scaling
    build agent?

    View Slide

  58. View Slide

  59. Communicate between
    server and agent

    View Slide

  60. View Slide

  61. Jobs Schema

    View Slide

  62. r := e.Group("/rpc")
    r.Use(rpc.Check())
    {
    r.POST("/v1/healthz", web.RPCHeartbeat)
    r.POST("/v1/request", web.RPCRquest)
    r.POST("/v1/accept", web.RPCAccept)
    r.POST("/v1/details", web.RPCDetails)
    r.POST("/v1/updateStatus", web.RPCUpdateStatus)
    r.POST("/v1/upload", web.RPCUploadBytes)
    r.POST("/v1/reset", web.RPCResetStatus)
    }
    Check RPC Secret

    View Slide

  63. /rpc/v1/accept
    Update jobs set version = (oldVersion + 1)
    where machine = "fooBar" and version = oldVersion

    View Slide

  64. Create multiple worker

    View Slide

  65. if r.Capacity != 0 {
    var g errgroup.Group
    for i := 0; i < r.Capacity; i++ {
    g.Go(func() error {
    return r.start(ctx, 0)
    })
    time.Sleep(1 * time.Second)
    }
    return g.Wait()
    }

    View Slide

  66. Break for and select loop
    func (r *Runner) start(ctx context.Context, id int64) error {
    LOOP:
    for {
    select {
    case return ctx.Err()
    default:
    r.poll(ctx, id)
    if r.Capacity == 0 {
    break LOOP
    }
    }
    time.Sleep(1 * time.Second)
    }
    return nil
    }

    View Slide

  67. How to cancel the current Job?

    View Slide

  68. View Slide

  69. View Slide

  70. View Slide

  71. Context with Cancel or Timeout
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    timeout, cancel := context.WithTimeout(ctx, 60*time.Minute)
    defer cancel()
    Job03 context

    View Slide

  72. Context with Cancel or Timeout
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    timeout, cancel := context.WithTimeout(ctx, 60*time.Minute)
    defer cancel()
    Job03 context
    Job05 context

    View Slide

  73. Watch the Cancel event
    go func() {
    done, _ := r.Manager.Watch(ctx, id)
    if done {
    cancel()
    }
    }()

    View Slide

  74. Handle cancel event on Server
    subscribers: make(map[chan struct{}]int64),
    cancelled: make(map[int64]time.Time),

    View Slide

  75. User cancel running job
    c.Lock()
    c.cancelled[id] = time.Now().Add(time.Minute * 5)
    for subscriber, build := range c.subscribers {
    if id == build {
    close(subscriber)
    }
    }
    c.Unlock()

    View Slide

  76. Agent subscribe the cancel event
    for {
    select {
    case return false, ctx.Err()
    case c.Lock()
    _, ok := c.cancelled[id]
    c.Unlock()
    if ok {
    return true, nil
    }
    case return true, nil
    }
    }

    View Slide

  77. case c.Lock()
    _, ok := c.cancelled[id]
    c.Unlock()
    if ok {
    return true, nil
    }

    View Slide

  78. case c.Lock()
    _, ok := c.cancelled[id]
    c.Unlock()
    if ok {
    return true, nil
    }
    1 Cancel

    View Slide

  79. case c.Lock()
    _, ok := c.cancelled[id]
    c.Unlock()
    if ok {
    return true, nil
    }
    1
    2 Reconnect Server
    Cancel

    View Slide

  80. https://www.udemy.com/course/golang-fight/?couponCode=GOLANG201911

    View Slide

  81. https://www.udemy.com/course/devops-oneday/?couponCode=DEVOPS201911

    View Slide

  82. Any Question?

    View Slide