import (
"internal/race"
+ "runtime"
"unsafe"
)
yieldNext bool
racer int
panicValue any
+ seqDone bool // to detect Goexit
)
c := newcoro(func(c *coro) {
race.Acquire(unsafe.Pointer(&racer))
// Recover and propagate panics from seq.
defer func() {
if p := recover(); p != nil {
- done = true // Invalidate iterator.
panicValue = p
+ } else if !seqDone {
+ panicValue = goexitPanicValue
}
+ done = true // Invalidate iterator
race.Release(unsafe.Pointer(&racer))
}()
seq(yield)
var v0 V
v, ok = v0, false
- done = true
+ seqDone = true
})
next = func() (v1 V, ok1 bool) {
race.Write(unsafe.Pointer(&racer)) // detect races
coroswitch(c)
race.Acquire(unsafe.Pointer(&racer))
- // Propagate panics from seq.
+ // Propagate panics and goexits from seq.
if panicValue != nil {
- panic(panicValue)
+ if panicValue == goexitPanicValue {
+ // Propagate runtime.Goexit from seq.
+ runtime.Goexit()
+ } else {
+ panic(panicValue)
+ }
}
return v, ok
}
coroswitch(c)
race.Acquire(unsafe.Pointer(&racer))
- // Propagate panics from seq.
+ // Propagate panics and goexits from seq.
if panicValue != nil {
- panic(panicValue)
+ if panicValue == goexitPanicValue {
+ // Propagate runtime.Goexit from seq.
+ runtime.Goexit()
+ } else {
+ panic(panicValue)
+ }
}
}
}
yieldNext bool
racer int
panicValue any
+ seqDone bool
)
c := newcoro(func(c *coro) {
race.Acquire(unsafe.Pointer(&racer))
// Recover and propagate panics from seq.
defer func() {
if p := recover(); p != nil {
- done = true // Invalidate iterator.
panicValue = p
+ } else if !seqDone {
+ panicValue = goexitPanicValue
}
+ done = true // Invalidate iterator.
race.Release(unsafe.Pointer(&racer))
}()
seq(yield)
var k0 K
var v0 V
k, v, ok = k0, v0, false
- done = true
+ seqDone = true
})
next = func() (k1 K, v1 V, ok1 bool) {
race.Write(unsafe.Pointer(&racer)) // detect races
coroswitch(c)
race.Acquire(unsafe.Pointer(&racer))
- // Propagate panics from seq.
+ // Propagate panics and goexits from seq.
if panicValue != nil {
- panic(panicValue)
+ if panicValue == goexitPanicValue {
+ // Propagate runtime.Goexit from seq.
+ runtime.Goexit()
+ } else {
+ panic(panicValue)
+ }
}
return k, v, ok
}
coroswitch(c)
race.Acquire(unsafe.Pointer(&racer))
- // Propagate panics from seq.
+ // Propagate panics and goexits from seq.
if panicValue != nil {
- panic(panicValue)
+ if panicValue == goexitPanicValue {
+ // Propagate runtime.Goexit from seq.
+ runtime.Goexit()
+ } else {
+ panic(panicValue)
+ }
}
}
}
return next, stop
}
+
+// goexitPanicValue is a sentinel value indicating that an iterator
+// exited via runtime.Goexit.
+var goexitPanicValue any = new(int)
f()
return
}
+
+func TestPullGoexit(t *testing.T) {
+ t.Run("next", func(t *testing.T) {
+ var next func() (int, bool)
+ var stop func()
+ if !goexits(t, func() {
+ next, stop = Pull(goexitSeq())
+ next()
+ }) {
+ t.Fatal("failed to Goexit from next")
+ }
+ if x, ok := next(); x != 0 || ok {
+ t.Fatal("iterator returned valid value after Goexit")
+ }
+ stop()
+ })
+ t.Run("stop", func(t *testing.T) {
+ var next func() (int, bool)
+ var stop func()
+ if !goexits(t, func() {
+ next, stop = Pull(goexitSeq())
+ stop()
+ }) {
+ t.Fatal("failed to Goexit from stop")
+ }
+ if x, ok := next(); x != 0 || ok {
+ t.Fatal("iterator returned valid value after Goexit")
+ }
+ stop()
+ })
+}
+
+func goexitSeq() Seq[int] {
+ return func(yield func(int) bool) {
+ runtime.Goexit()
+ }
+}
+
+func TestPull2Goexit(t *testing.T) {
+ t.Run("next", func(t *testing.T) {
+ var next func() (int, int, bool)
+ var stop func()
+ if !goexits(t, func() {
+ next, stop = Pull2(goexitSeq2())
+ next()
+ }) {
+ t.Fatal("failed to Goexit from next")
+ }
+ if x, y, ok := next(); x != 0 || y != 0 || ok {
+ t.Fatal("iterator returned valid value after Goexit")
+ }
+ stop()
+ })
+ t.Run("stop", func(t *testing.T) {
+ var next func() (int, int, bool)
+ var stop func()
+ if !goexits(t, func() {
+ next, stop = Pull2(goexitSeq2())
+ stop()
+ }) {
+ t.Fatal("failed to Goexit from stop")
+ }
+ if x, y, ok := next(); x != 0 || y != 0 || ok {
+ t.Fatal("iterator returned valid value after Goexit")
+ }
+ stop()
+ })
+}
+
+func goexitSeq2() Seq2[int, int] {
+ return func(yield func(int, int) bool) {
+ runtime.Goexit()
+ }
+}
+
+func goexits(t *testing.T, f func()) bool {
+ t.Helper()
+
+ exit := make(chan bool)
+ go func() {
+ cleanExit := false
+ defer func() {
+ exit <- recover() == nil && !cleanExit
+ }()
+ f()
+ cleanExit = true
+ }()
+ return <-exit
+}