Merge "Prevent hangs in OncePer when the callback panics" am: b7afaf0802

am: bf200f61ac

Change-Id: Iefa4ae2c21c81c486f603ba9d2601e97d5ffd0ac
This commit is contained in:
Colin Cross 2019-05-15 10:52:21 -07:00 committed by android-build-merger
commit 982c6ca76c
2 changed files with 51 additions and 5 deletions

View File

@ -40,7 +40,8 @@ func (once *OncePer) maybeWaitFor(key OnceKey, value interface{}) interface{} {
}
// Once computes a value the first time it is called with a given key per OncePer, and returns the
// value without recomputing when called with the same key. key must be hashable.
// value without recomputing when called with the same key. key must be hashable. If value panics
// the panic will be propagated but the next call to Once with the same key will return nil.
func (once *OncePer) Once(key OnceKey, value func() interface{}) interface{} {
// Fast path: check if the key is already in the map
if v, ok := once.values.Load(key); ok {
@ -54,10 +55,15 @@ func (once *OncePer) Once(key OnceKey, value func() interface{}) interface{} {
return once.maybeWaitFor(key, v)
}
// The waiter is inserted, call the value constructor, store it, and signal the waiter
v := value()
once.values.Store(key, v)
close(waiter)
// The waiter is inserted, call the value constructor, store it, and signal the waiter. Use defer in case
// the function panics.
var v interface{}
defer func() {
once.values.Store(key, v)
close(waiter)
}()
v = value()
return v
}

View File

@ -175,3 +175,43 @@ func TestOncePerReentrant(t *testing.T) {
t.Errorf(`reentrant Once should return "a": %q`, a)
}
}
// Test that a recovered panic in a Once function doesn't deadlock
func TestOncePerPanic(t *testing.T) {
once := OncePer{}
key := NewOnceKey("key")
ch := make(chan interface{})
var a interface{}
go func() {
defer func() {
ch <- recover()
}()
a = once.Once(key, func() interface{} {
panic("foo")
})
}()
p := <-ch
if p.(string) != "foo" {
t.Errorf(`expected panic with "foo", got %#v`, p)
}
if a != nil {
t.Errorf(`expected a to be nil, got %#v`, a)
}
// If the call to Once that panicked leaves the key in a bad state this will deadlock
b := once.Once(key, func() interface{} {
return "bar"
})
// The second call to Once should return nil inserted by the first call that panicked.
if b != nil {
t.Errorf(`expected b to be nil, got %#v`, b)
}
}