0

I am trying to do some CRUD operations which takes longer time. I have come up with the sample playground to demonstrate my problem:

package main

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

func call(s int) {
    fmt.Println(s)
}

func get() int {
    num := rand.Intn(20-10) + 5
    return num
}

func main() {
    call(1)
    ticker := time.NewTicker(1000 * time.Millisecond)
    stop := make(chan bool, 1)
    check := make(chan string, 1)
    go func() {
        for {
            select {
            case <-stop:
                check <- "done" 
                    fmt.Println("stopped")
                return
            case <-ticker.C:
                randInt := get()
                if randInt == 11 {
                    call(randInt)
                    stop <- true
                } else {
                    call(randInt)
                }
            }
        }
    }()
    
    //fmt.Println(<-stop)
}
  1. It's a http request
  2. At the end of the request, I do return with 202 http then fire a go routine.
  3. Purpose of go routine, to check whether the requested entity is created/deleted/updated/failed than in progress
  4. Demo program runs until it gets random number 11 i.e similar to getting one of the desired status as in point 3.
  5. I feel that there could be a chance where random number never meets 11 for quite long time.(if range is 1 million) So I want to cancel the ticker after 10 func calls.

How do I do this?

Are correct things used i.e ticker, goroutine. Kindly suggest.

Unfortunately, I couldn't decode after referring several forums, posts. Confused more with context, timer and all.

peterh
  • 11,875
  • 18
  • 85
  • 108
Gibbs
  • 21,904
  • 13
  • 74
  • 138
  • After step 2 how do you notify the client? – Eduard Hasanaj Jan 28 '21 at 15:03
  • Good question, via notifications. Notification will be added to db, from db notification ll be displayed – Gibbs Jan 28 '21 at 15:04
  • 1
    You have 2 things reading from `stop`, but only one value sent. Besides the obvious deadlock possibility, are you attempting to make this cancellable? `check` doesn't seem to do anything, what is the purpose of that too? – JimB Jan 28 '21 at 15:09
  • Without the last receive operation, the program does not wait for the goroutine to complete. Is this what you're trying to do? https://play.golang.org/p/PYeat1qVgJB – JimB Jan 28 '21 at 15:19
  • Yes @JimB, is there any way to achieve this than `for` loop with limit 5 or is this the correct approach? And can't we do that with only channels than waitGroup? Sorry for too many questions. – Gibbs Jan 28 '21 at 15:21
  • Of course you can do it with only channels, but a `WaitGroup` is the standard way to wait for goroutines to complete. However as demonstrated by your example, using channels for this is often done incorrectly leading to deadlocks or incorrect behavior. If you want to count the number of iterations, then count them however you'd like. The syntax is irrelevant, you just need to count something. – JimB Jan 28 '21 at 15:30

1 Answers1

1

In order to limit the number of tries, we simply need to count the attempts made, which is trivial with the existing for loop.

It appears with the stop channel that you intend to make this cancellable as well, but the usage here will not work as expected. You can use a context.Context for this, which can be later incorporated into other calls that accept a context. Otherwise a sync.WaitGroup is the expected method to wait for completion.

Waiting for the goroutine to return can be done with a channel, but you should not rely on sending a single value. As shown in your example, multiple readers (which may have been added later due to refactoring) will cause the other to not receive the signal. If you do use a channel, closing the channel is the canonical way to broadcast a signal.

Using that information, we can come up with this modified example: https://play.golang.org/p/hZiRXtMm-SB

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

maxAttempts := 5

var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()
    ticker := time.NewTicker(1000 * time.Millisecond)

    call(1)

    for i := 1; ; i++ {
        if i >= maxAttempts {
            fmt.Println("too many tries")
            return
        }

        select {
        case <-ctx.Done():
            fmt.Println("cancelled")
            return

        case <-ticker.C:
            randInt := get()
            call(randInt)
            if randInt == 11 {
                fmt.Println("OK")
                return
            }
        }
    }
}()

wg.Wait()
JimB
  • 104,193
  • 13
  • 262
  • 255
  • Thanks @JimB, I'll try this and come back asap. Thanks for your valuable time and thoughts on `wait for completion`, `channels`. – Gibbs Jan 28 '21 at 16:00
  • Is [this](https://stackoverflow.com/questions/6807590/how-to-stop-a-goroutine#:~:text=first%20demo) better approach if I don't need to wait but let the routine do the job. – Gibbs Jan 28 '21 at 17:07
  • @Gibbs: you need to provide more information to answer that. In your example you _must_ wait, otherwise the program just exists and does nothing. – JimB Jan 28 '21 at 17:09
  • ok, let's say. API returns 202 then fires a go routine. Go routine needs to check for a status. status-check-request request may fail/succeed/takes-more-time. The mentioned link `fires another go routine` to set a value in the `channel` which eventually closes the channel after few seconds. So in this case, can I use that? I mean do you see any problem with that? – Gibbs Jan 28 '21 at 17:12
  • 1
    You can structure the asynchronous task however you want, but we cannot tell you how without _all_ the details of what you are doing. The linked example is just another form of waiting for the goroutine to complete, but using the channel to communicate this. If you need to communicate between goroutines, then yes, you may want to use a channel. – JimB Jan 28 '21 at 17:18
  • Thanks for the detailed explanations. :) – Gibbs Jan 28 '21 at 17:19