I am trying to make a test for my program that will save to the disk over a set interval. My problem is when I add time.Sleep to the test, no matter the duration, the program hangs.
My expected behavior would be that I allow it to sleep, and the go routine should run in the background for that duration saving to the disk.
Here is the function in question that is being run as a goroutine from the test.
nodeservice.go
func runRegistryBackup(reg *NodeRegistry, interval int, killchan chan bool) {
log.SetPrefix("[Registry Backup]\t")
log.Printf("Intializing backup every %d seconds", interval)
select {
case <-time.Tick(time.Duration(interval) * time.Second):
log.Println("Backing up node registry to disk")
reg.write()
case <-killchan:
log.Println("Flushing data to disk")
reg.write()
defer func() { killchan <- true }()
//defer close(killchan)
return
}
}
And here is the test. I commented the line that when added will freeze the program
nodeservice_test.go
func TestRunRegistry(t *testing.T) {
// need registry, interval, bool chan
// setup
fi, _ := ioutil.TempFile("", "msg-serv-test-")
defer os.Remove(fi.Name())
reg := MakeNodeRegistry(fi.Name())
name := "test"
ip := "10.0.0.1"
port := "0"
tls := false
node1 := MakeNode(name, ip, port, tls)
reg.addNode(node1)
interval := 5
kill := make(chan bool)
// test the interval
go runRegistryBackup(reg, interval, kill)
// let run for a little
time.Sleep(time.Minute) // adding this line hangs the whole program
// test kill
kill <- true
<-kill
actReg := BuildRegistry(fi.Name())
for key, eval := range reg.Nodes {
val := actReg.Nodes[key]
if eval != nil && val != nil {
assert(t, *val, *eval)
} else {
t.Logf("Expected Hash: %d \t Expected Node: %v", key, eval)
t.Logf("Key %d not found for Node %v", key, val)
t.Fail()
}
}
}
Here is the output of the log that shows that the interval is only being run once and then hanging.
[Registry Backup] 2019/07/19 13:29:51 Intializing backup every 5 seconds
[Registry Backup] 2019/07/19 13:29:56 Backing up node registry to disk
Any insight as to the cause of this problem would be greatly appreciated.
The function runRegistryBackup
calls write()
once and then exits. The main goroutine then blocks on sending to kill
.
Fix by adding a loop to the function. Create the ticker once before the loop and stop the ticker when returning.
func runRegistryBackup(reg *NodeRegistry, interval int, killchan chan bool) {
log.SetPrefix("[Registry Backup]\t")
log.Printf("Intializing backup every %d seconds", interval)
t := time.NewTicker(time.Duration(interval) * time.Second)
defer t.Stop()
for {
select {
case <-t.C:
log.Println("Backing up node registry to disk")
reg.write()
case <-killchan:
log.Println("Flushing data to disk")
reg.write()
defer func() { killchan <- true }()
return
}
}
}
The unbuffered kill
channel adds some fragility to the program. For example, if runRegistryBackup
is modified to exit on a write error and main attempts to kill the backup after that error, then main will block forever. Fix by making the channel buffered.
kill := make(chan bool, 1)
go runRegistryBackup(reg, interval, kill)
return with case <-time.Tick
Nothing can read from kill channel, so main routine blocks on kill <- true
. I don't exactly understand your program's logic, so the easiest way to get things work is to make kill
channel buffered
kill := make(chan bool, 1)