-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
This is a theoretical issue I have not yet encountered in practice (but think could actually happen) that I came across while thinking about the source code, in particular these bits:
// Entries returns a snapshot of the cron entries.
func (c *Cron) Entries() []*Entry {
if c.running {
c.snapshot <- nil // A
x := <-c.snapshot // B
return x
}
return c.entrySnapshot()
}
case <-c.snapshot: // C
c.snapshot <- c.entrySnapshot() // D
Let's say Goroutine 1 is executing the run() loop containing C and D. Now Goroutine 2 executes Entries() until A, where it blocks. Goroutine 1 executes C, but before it gets a chance to execute D the scheduler decides to preempt it in favour of Goroutine 3, which executes Entries() until A, where it puts nil into the snapshot channel, leading Goroutine 2 to receive nil in B!
Since entrySnapshot() is a non-trivial function I feel like there's a good chance the Goroutine might get preempted during its execution.
The issue lies in using the same channel for communication in both directions. You could instead have a channel of channels over which you send a result channel created in Entries().