gogoroutinepoller

Stop for loop by passing empty struct down channel Go


I am attempting to create a poller in Go that spins up and every 24 hours executes a function.

I want to also be able to stop the polling, I'm attempting to do this by having a done channel and passing down an empty struct to stop the for loop.

In my tests, the for just loops infinitely and I can't seem to stop it, am I using the done channel incorrectly? The ticker case works as expected.

Poller struct {
    HandlerFunc HandlerFunc
    interval    *time.Ticker
    done        chan struct{}
}

func (p *Poller) Start() error {
    for {
        select {
        case <-p.interval.C:
            err := p.HandlerFunc()
            if err != nil {
                return err
            }
        case <-p.done:
            return nil
        }
    }
}

func (p *Poller) Stop() {
    p.done <- struct{}{}
}

Here is the test that's exeuting the code and causing the infinite loop.

poller := poller.NewPoller(
    testHandlerFunc,
    time.NewTicker(1*time.Millisecond),
)

err := poller.Start()
assert.Error(t, err)
poller.Stop()

Solution

  • Seems like problem is in your use case, you calling poller.Start() in blocking maner, so poller.Stop() is never called. It's common, in go projects to call goroutine inside of Start/Run methods, so, in poller.Start(), i would do something like that:

    func (p *Poller) Start() <-chan error {
        errc := make(chan error, 1 )
    
        go func() {
            defer close(errc)
    
            for {
                select {
                case <-p.interval.C:
                    err := p.HandlerFunc()
                    if err != nil {
                        errc <- err
                        return
                    }
                case <-p.done:
                    return
                }
            }
        }
    
        return errc
    }
    

    Also, there's no need to send empty struct to done channel. Closing channel like close(p.done) is more idiomatic for go.