diff options
-rw-r--r-- | android/onceper.go | 16 | ||||
-rw-r--r-- | android/onceper_test.go | 40 |
2 files changed, 51 insertions, 5 deletions
diff --git a/android/onceper.go b/android/onceper.go index 5ad17fa9..ff865c2e 100644 --- a/android/onceper.go +++ b/android/onceper.go | |||
@@ -40,7 +40,8 @@ func (once *OncePer) maybeWaitFor(key OnceKey, value interface{}) interface{} { | |||
40 | } | 40 | } |
41 | 41 | ||
42 | // Once computes a value the first time it is called with a given key per OncePer, and returns the | 42 | // Once computes a value the first time it is called with a given key per OncePer, and returns the |
43 | // value without recomputing when called with the same key. key must be hashable. | 43 | // value without recomputing when called with the same key. key must be hashable. If value panics |
44 | // the panic will be propagated but the next call to Once with the same key will return nil. | ||
44 | func (once *OncePer) Once(key OnceKey, value func() interface{}) interface{} { | 45 | func (once *OncePer) Once(key OnceKey, value func() interface{}) interface{} { |
45 | // Fast path: check if the key is already in the map | 46 | // Fast path: check if the key is already in the map |
46 | if v, ok := once.values.Load(key); ok { | 47 | if v, ok := once.values.Load(key); ok { |
@@ -54,10 +55,15 @@ func (once *OncePer) Once(key OnceKey, value func() interface{}) interface{} { | |||
54 | return once.maybeWaitFor(key, v) | 55 | return once.maybeWaitFor(key, v) |
55 | } | 56 | } |
56 | 57 | ||
57 | // The waiter is inserted, call the value constructor, store it, and signal the waiter | 58 | // The waiter is inserted, call the value constructor, store it, and signal the waiter. Use defer in case |
58 | v := value() | 59 | // the function panics. |
59 | once.values.Store(key, v) | 60 | var v interface{} |
60 | close(waiter) | 61 | defer func() { |
62 | once.values.Store(key, v) | ||
63 | close(waiter) | ||
64 | }() | ||
65 | |||
66 | v = value() | ||
61 | 67 | ||
62 | return v | 68 | return v |
63 | } | 69 | } |
diff --git a/android/onceper_test.go b/android/onceper_test.go index 95303baf..1a55ff4b 100644 --- a/android/onceper_test.go +++ b/android/onceper_test.go | |||
@@ -175,3 +175,43 @@ func TestOncePerReentrant(t *testing.T) { | |||
175 | t.Errorf(`reentrant Once should return "a": %q`, a) | 175 | t.Errorf(`reentrant Once should return "a": %q`, a) |
176 | } | 176 | } |
177 | } | 177 | } |
178 | |||
179 | // Test that a recovered panic in a Once function doesn't deadlock | ||
180 | func TestOncePerPanic(t *testing.T) { | ||
181 | once := OncePer{} | ||
182 | key := NewOnceKey("key") | ||
183 | |||
184 | ch := make(chan interface{}) | ||
185 | |||
186 | var a interface{} | ||
187 | |||
188 | go func() { | ||
189 | defer func() { | ||
190 | ch <- recover() | ||
191 | }() | ||
192 | |||
193 | a = once.Once(key, func() interface{} { | ||
194 | panic("foo") | ||
195 | }) | ||
196 | }() | ||
197 | |||
198 | p := <-ch | ||
199 | |||
200 | if p.(string) != "foo" { | ||
201 | t.Errorf(`expected panic with "foo", got %#v`, p) | ||
202 | } | ||
203 | |||
204 | if a != nil { | ||
205 | t.Errorf(`expected a to be nil, got %#v`, a) | ||
206 | } | ||
207 | |||
208 | // If the call to Once that panicked leaves the key in a bad state this will deadlock | ||
209 | b := once.Once(key, func() interface{} { | ||
210 | return "bar" | ||
211 | }) | ||
212 | |||
213 | // The second call to Once should return nil inserted by the first call that panicked. | ||
214 | if b != nil { | ||
215 | t.Errorf(`expected b to be nil, got %#v`, b) | ||
216 | } | ||
217 | } | ||