// simultaneously.
func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func()) {
var (
- v V
- ok bool
- done bool
- yieldNext bool
- racer int
+ v V
+ ok bool
+ done bool
+ yieldNext bool
+ racer int
+ panicValue any
)
c := newcoro(func(c *coro) {
race.Acquire(unsafe.Pointer(&racer))
race.Acquire(unsafe.Pointer(&racer))
return !done
}
+ // Recover and propagate panics from seq.
+ defer func() {
+ if p := recover(); p != nil {
+ done = true // Invalidate iterator.
+ panicValue = p
+ }
+ race.Release(unsafe.Pointer(&racer))
+ }()
seq(yield)
var v0 V
v, ok = v0, false
done = true
- race.Release(unsafe.Pointer(&racer))
})
next = func() (v1 V, ok1 bool) {
race.Write(unsafe.Pointer(&racer)) // detect races
+
if done {
return
}
race.Release(unsafe.Pointer(&racer))
coroswitch(c)
race.Acquire(unsafe.Pointer(&racer))
+
+ // Propagate panics from seq.
+ if panicValue != nil {
+ panic(panicValue)
+ }
return v, ok
}
stop = func() {
race.Write(unsafe.Pointer(&racer)) // detect races
+
if !done {
done = true
race.Release(unsafe.Pointer(&racer))
coroswitch(c)
race.Acquire(unsafe.Pointer(&racer))
+
+ // Propagate panics from seq.
+ if panicValue != nil {
+ panic(panicValue)
+ }
}
}
return next, stop
// simultaneously.
func Pull2[K, V any](seq Seq2[K, V]) (next func() (K, V, bool), stop func()) {
var (
- k K
- v V
- ok bool
- done bool
- yieldNext bool
- racer int
+ k K
+ v V
+ ok bool
+ done bool
+ yieldNext bool
+ racer int
+ panicValue any
)
c := newcoro(func(c *coro) {
race.Acquire(unsafe.Pointer(&racer))
race.Acquire(unsafe.Pointer(&racer))
return !done
}
+ // Recover and propagate panics from seq.
+ defer func() {
+ if p := recover(); p != nil {
+ done = true // Invalidate iterator.
+ panicValue = p
+ }
+ race.Release(unsafe.Pointer(&racer))
+ }()
seq(yield)
var k0 K
var v0 V
k, v, ok = k0, v0, false
done = true
- race.Release(unsafe.Pointer(&racer))
})
next = func() (k1 K, v1 V, ok1 bool) {
race.Write(unsafe.Pointer(&racer)) // detect races
+
if done {
return
}
race.Release(unsafe.Pointer(&racer))
coroswitch(c)
race.Acquire(unsafe.Pointer(&racer))
+
+ // Propagate panics from seq.
+ if panicValue != nil {
+ panic(panicValue)
+ }
return k, v, ok
}
stop = func() {
race.Write(unsafe.Pointer(&racer)) // detect races
+
if !done {
done = true
race.Release(unsafe.Pointer(&racer))
coroswitch(c)
race.Acquire(unsafe.Pointer(&racer))
+
+ // Propagate panics from seq.
+ if panicValue != nil {
+ panic(panicValue)
+ }
}
}
return next, stop
}
var yieldSlot2 func(int, int) bool
+
+func TestPullPanic(t *testing.T) {
+ t.Run("next", func(t *testing.T) {
+ next, stop := Pull(panicSeq())
+ if !panicsWith("boom", func() { next() }) {
+ t.Fatal("failed to propagate panic on first next")
+ }
+ // Make sure we don't panic again if we try to call next or stop.
+ if _, ok := next(); ok {
+ t.Fatal("next returned true after iterator panicked")
+ }
+ // Calling stop again should be a no-op.
+ stop()
+ })
+ t.Run("stop", func(t *testing.T) {
+ next, stop := Pull(panicSeq())
+ if !panicsWith("boom", func() { stop() }) {
+ t.Fatal("failed to propagate panic on first stop")
+ }
+ // Make sure we don't panic again if we try to call next or stop.
+ if _, ok := next(); ok {
+ t.Fatal("next returned true after iterator panicked")
+ }
+ // Calling stop again should be a no-op.
+ stop()
+ })
+}
+
+func panicSeq() Seq[int] {
+ return func(yield func(int) bool) {
+ panic("boom")
+ }
+}
+
+func TestPull2Panic(t *testing.T) {
+ t.Run("next", func(t *testing.T) {
+ next, stop := Pull2(panicSeq2())
+ if !panicsWith("boom", func() { next() }) {
+ t.Fatal("failed to propagate panic on first next")
+ }
+ // Make sure we don't panic again if we try to call next or stop.
+ if _, _, ok := next(); ok {
+ t.Fatal("next returned true after iterator panicked")
+ }
+ // Calling stop again should be a no-op.
+ stop()
+ })
+ t.Run("stop", func(t *testing.T) {
+ next, stop := Pull2(panicSeq2())
+ if !panicsWith("boom", func() { stop() }) {
+ t.Fatal("failed to propagate panic on first stop")
+ }
+ // Make sure we don't panic again if we try to call next or stop.
+ if _, _, ok := next(); ok {
+ t.Fatal("next returned true after iterator panicked")
+ }
+ // Calling stop again should be a no-op.
+ stop()
+ })
+}
+
+func panicSeq2() Seq2[int, int] {
+ return func(yield func(int, int) bool) {
+ panic("boom")
+ }
+}
+
+func panicsWith(v any, f func()) (panicked bool) {
+ defer func() {
+ if r := recover(); r != nil {
+ if r != v {
+ panic(r)
+ }
+ panicked = true
+ }
+ }()
+ f()
+ return
+}