ooni-probe-cli/pkg/oonimkall/eventemitter.go

51 lines
1.4 KiB
Go
Raw Normal View History

package oonimkall
[forwardport] fix(oonimkall): don't close channel to signal end of task (#619) (#620) This diff forward ports 90bf0629b957c912a5a6f3bb6c98ad3abb5a2ff6 to `master`. If we close the channel to signal the end of a task we may panic when some background goroutine tries to post on the channel. This bug is rare but may happen. See for example https://github.com/ooni/probe/issues/1438. How can we improve? First, let us add a timeout when sending to the channel. Given that the channel is buffered and we have a generous timeout (1/4 of a second), it's unlikely we will really block. But, in the event in which a late message appears, we'll eventually _unblock_ when sending with a timeout. So, now we don't have to worry anymore about leaking forever a goroutine. Then, let us change the protocol with which we signal that a task is done. We used to close the channel. Now, instead we just synchronously post a nil on the channel when done. In turn, we interpret this nil to mean that the task is done when we receive messages. The _main_ different with respect to before is that now we are asking the consumer of our API to drain the channel. Because before we had a blocking channel, it seems to me we were already requiring the consumer of the API to do that. Which means, I think in practical terms it did not change much. Finally, acknowledge that we don't need a specific state variable to tell us we're done and simplify a little bit the API by just making isRunning private and using the "we're done" signal to determine whether we've stopped running the task. All these changes should be enough to close https://github.com/ooni/probe/issues/1438.
2021-11-26 22:18:45 +01:00
import "time"
// eventEmitter emits event on a channel
type eventEmitter struct {
disabled map[string]bool
out chan<- *event
}
// newEventEmitter creates a new Emitter
func newEventEmitter(disabledEvents []string, out chan<- *event) *eventEmitter {
ee := &eventEmitter{out: out}
ee.disabled = make(map[string]bool)
for _, eventname := range disabledEvents {
ee.disabled[eventname] = true
}
return ee
}
// EmitFailureStartup emits the failureStartup event
func (ee *eventEmitter) EmitFailureStartup(failure string) {
ee.EmitFailureGeneric(failureStartup, failure)
}
// EmitFailureGeneric emits a failure event
func (ee *eventEmitter) EmitFailureGeneric(name, failure string) {
ee.Emit(name, eventFailure{Failure: failure})
}
// EmitStatusProgress emits the status.Progress event
func (ee *eventEmitter) EmitStatusProgress(percentage float64, message string) {
ee.Emit(statusProgress, eventStatusProgress{Message: message, Percentage: percentage})
}
// Emit emits the specified event
func (ee *eventEmitter) Emit(key string, value interface{}) {
if ee.disabled[key] {
return
}
[forwardport] fix(oonimkall): don't close channel to signal end of task (#619) (#620) This diff forward ports 90bf0629b957c912a5a6f3bb6c98ad3abb5a2ff6 to `master`. If we close the channel to signal the end of a task we may panic when some background goroutine tries to post on the channel. This bug is rare but may happen. See for example https://github.com/ooni/probe/issues/1438. How can we improve? First, let us add a timeout when sending to the channel. Given that the channel is buffered and we have a generous timeout (1/4 of a second), it's unlikely we will really block. But, in the event in which a late message appears, we'll eventually _unblock_ when sending with a timeout. So, now we don't have to worry anymore about leaking forever a goroutine. Then, let us change the protocol with which we signal that a task is done. We used to close the channel. Now, instead we just synchronously post a nil on the channel when done. In turn, we interpret this nil to mean that the task is done when we receive messages. The _main_ different with respect to before is that now we are asking the consumer of our API to drain the channel. Because before we had a blocking channel, it seems to me we were already requiring the consumer of the API to do that. Which means, I think in practical terms it did not change much. Finally, acknowledge that we don't need a specific state variable to tell us we're done and simplify a little bit the API by just making isRunning private and using the "we're done" signal to determine whether we've stopped running the task. All these changes should be enough to close https://github.com/ooni/probe/issues/1438.
2021-11-26 22:18:45 +01:00
const maxSendTimeout = 250 * time.Millisecond
timer := time.NewTimer(maxSendTimeout)
defer timer.Stop()
select {
case ee.out <- &event{Key: key, Value: value}:
// good, we've been able to send the new event
case <-timer.C:
// oops, we've timed out sending
}
}