I have a program which periodically checks an external mailbox for messages and which has a user view which allows them to view messages and to terminate the program.
Stripped to minimal features it looks like this
package main
import (
"log"
"time"
)
func main() {
log.Println("Hello, playground")
quit := make(chan bool)
data := make(chan string)
go func() {
for {
select {
case <-quit:
log.Println("Quitting")
close(data)
return
case data <- fetch():
// Wait until e.g. exactly 0,10,20,30,40 or 50 mins past the hour
interval := time.Second * 5
now := time.Now()
time.Sleep(now.Truncate(interval).Add(interval).Sub(now))
}
}
}()
go func() {
time.Sleep(12 * time.Second) // actually user presses a "quit" button
quit <- true
}()
loop:
for {
select {
case info, ok := <-data:
if !ok {
break loop
}
log.Println("Fetched", info)
}
}
log.Println("Goodbye, playground")
}
func fetch() string {
log.Println("Fetching")
return "message"
}
You can run this in the Go Playground
Output is
2009/11/10 23:00:00 Hello, playground
2009/11/10 23:00:00 Fetching
2009/11/10 23:00:00 Fetched message
2009/11/10 23:00:05 Fetching
2009/11/10 23:00:05 Fetched message
2009/11/10 23:00:10 Fetching
2009/11/10 23:00:10 Fetched message
2009/11/10 23:00:15 Fetching
2009/11/10 23:00:15 Quitting
2009/11/10 23:00:15 Goodbye, playground
Notice that
"23:00:15 Fetching"
is something I didn't expect.The latter would be a problem in my program because it uses a 10 minute sleep between checking for messages. Delaying a quit for that long would make the program seem pretty unresponsive.
From this answer I have learned that you can use time.After()
to create a loop delay that can be interrupted.
How should I best apply that to my program?
"23:00:15 Fetching" is something I didn't expect.
This is not surprising, this is the intended working. Quoting from Spec: Select statements:
Execution of a "select" statement proceeds in several steps:
- For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement.
[...]
So the select
statement evaluates the communication operations before it decides on which branch to proceed / execute.
This means that
case data <- fetch():
fetch()
will be called, even if sending on data
would not be possible and even if receiving from quit
can proceed immediately.
Since you have the sleep in one of the case
branches, it doesn't matter that quit
becomes ready to receive from, checking that (and optionally deciding to go on that branch) has to wait until time.Sleep()
–and the whole case
branch– completes.
So the communication operation should be a receive operation from the channel returned by time.After()
, and only call fetch()
in the body of this case branch.
You can make it work like this:
for {
// Wait until e.g. exactly 0,10,20,30,40 or 50 mins past the hour
interval := time.Second * 5
now := time.Now()
delay := now.Truncate(interval).Add(interval).Sub(now)
select {
case <-quit:
log.Println("Quitting")
close(data)
return
case <-time.After(delay):
data <- fetch()
}
}
And now the output (try it on the Go Playground):
2009/11/10 23:00:00 Hello, playground
2009/11/10 23:00:05 Fetching
2009/11/10 23:00:05 Fetched message
2009/11/10 23:00:10 Fetching
2009/11/10 23:00:10 Fetched message
2009/11/10 23:00:12 Quitting
2009/11/10 23:00:12 Goodbye, playground