Home > Enterprise >  Go - The most optimal way to implement a timeout
Go - The most optimal way to implement a timeout

Time:11-11

I have a service that is deployed asynchronously and I need to wait a specified amount of time for it to come online. If the specified amount of time elapses and we still aren't able to find the service, then we error out. What is the most optimal way of writing this in go? I was looking into using context.WithTimeout but not sure exactly how this would work. Thanks for the help!

func (c *Client) WaitForServiceToComeAlive(ctx context.Context, name string, timeout time.Duration) error {
    
    var mysvc *Service
    var err error

    endtime := time.Now().Add(timeout)

    for time.Now().Before(endtime) {
        mysvc, err = c.GetService(ctx, name)
        if err != nil {
            return err
        }

        if mysvc != nil {
            break
        }

        time.Sleep(time.Second * 10)
    }

    if mysvc == nil {
        return fmt.Errorf("svc %s did not register", name)
    }

    return nil
}

CodePudding user response:

Never use time.Sleep especially if it's a long period - as it's uninterruptible. Why does this matter? If its in a goroutine and that task is not going to complete (i.e. context was canceled), then you would rather abort immediately.

So to create a polling-wait with cancelation:

select {
case <-ctx.Done():               // cancel early if context is canceled
    return ctx.Err()
case <-time.After(pollInterval): // wait for pollInterval duration
}

Put your larger timeout within the input context:

ctx := context.TODO() // <- outer request context goes here or context.Background()

// wrap context with a timeout
ctx, cancel := context.WithTimeout(ctx, 1 * time.Minute)
defer cancel() // avoid leaks

err := c.WaitForServiceToComeAlive(ctx, "job", 10*time.Second /* poll interval */)

then your service-wait function simplifies to:

func (c *Client) WaitForServiceToComeAlive(ctx context.Context, name string, pollInterval time.Duration) error {

    var mysvc *Service
    var err error

    for {
        mysvc, err = c.GetService(name) // <- this should take a ctx too - if possible for early cancelation
        if err != nil {
            return err
        }
        if mysvc != nil {
            return nil
        }

        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-time.After(pollInterval):
        }
    }

}

https://play.golang.org/p/JwH5CMyY0I2

  • Related