1
1
package sync
2
2
3
3
import (
4
+ "errors"
4
5
"fmt"
5
- "io"
6
- "log"
7
6
"runtime"
8
7
"sync/atomic"
9
8
"time"
10
9
)
11
10
12
11
const DeadlockDetection = 40 * time .Second
13
12
13
+ var ErrCoroutineAlreadyFinished = errors .New ("coroutine already finished" )
14
+
14
15
type CoroutineCreator interface {
15
16
NewCoroutine (ctx Context , fn func (Context ) error )
16
17
}
@@ -53,7 +54,8 @@ type coState struct {
53
54
54
55
err error
55
56
56
- logger logger
57
+ // logger logger
58
+ // idx int
57
59
58
60
deadlockDetection time.Duration
59
61
@@ -68,6 +70,11 @@ func NewCoroutine(ctx Context, fn func(ctx Context) error) Coroutine {
68
70
defer s .finish () // Ensure we always mark the coroutine as finished
69
71
defer func () {
70
72
if r := recover (); r != nil {
73
+ if err , ok := r .(error ); ok && errors .Is (err , ErrCoroutineAlreadyFinished ) {
74
+ // Ignore this specific error
75
+ return
76
+ }
77
+
71
78
s .err = fmt .Errorf ("panic: %v" , r )
72
79
}
73
80
}()
@@ -86,21 +93,26 @@ func NewCoroutine(ctx Context, fn func(ctx Context) error) Coroutine {
86
93
func newState () * coState {
87
94
// i++
88
95
89
- return & coState {
96
+ c := & coState {
90
97
blocking : make (chan bool , 1 ),
91
98
unblock : make (chan bool ),
92
99
// Only used while debugging issues, default to discarding log messages
93
- logger : log .New (io .Discard , "[co]" , log .LstdFlags ),
94
100
// logger: log.New(os.Stderr, fmt.Sprintf("[co %v]", i), log.Lmsgprefix|log.Ltime),
101
+ // idx: i,
95
102
deadlockDetection : DeadlockDetection ,
96
103
}
104
+
105
+ // Start out as blocked
106
+ c .blocked .Store (true )
107
+
108
+ return c
97
109
}
98
110
99
111
func (s * coState ) finish () {
100
112
s .finished .Store (true )
101
113
s .blocking <- true
102
114
103
- s .logger .Println ("finish" )
115
+ // s.logger.Println("finish")
104
116
}
105
117
106
118
func (s * coState ) SetCoroutineCreator (creator CoroutineCreator ) {
@@ -136,23 +148,28 @@ func (s *coState) Yield() {
136
148
}
137
149
138
150
func (s * coState ) yield (markBlocking bool ) {
139
- s .logger .Println ("yielding" )
140
-
141
- s .blocked .Store (true )
151
+ // s.logger.Println("yielding")
142
152
143
153
if markBlocking {
154
+ if s .shouldExit .Load () != nil {
155
+ // s.logger.Println("yielding, but should exit")
156
+ panic (ErrCoroutineAlreadyFinished )
157
+ }
158
+
159
+ s .blocked .Store (true )
160
+
144
161
s .blocking <- true
145
162
}
146
163
147
- s .logger .Println ("yielded" )
164
+ // s.logger.Println("yielded")
148
165
149
166
// Wait for the next Execute() call
150
167
<- s .unblock
151
168
152
169
// Once we're here, another Execute() call has been made. s.blocking is empty
153
170
154
171
if s .shouldExit .Load () != nil {
155
- s .logger .Println ("exiting" )
172
+ // s.logger.Println("exiting")
156
173
157
174
// Goexit runs all deferred functions, which includes calling finish() in the main
158
175
// execution function. That marks the coroutine as finished and blocking.
@@ -161,37 +178,37 @@ func (s *coState) yield(markBlocking bool) {
161
178
162
179
s .blocked .Store (false )
163
180
164
- s .logger .Println ("done yielding, continuing" )
181
+ // s.logger.Println("done yielding, continuing")
165
182
}
166
183
167
184
func (s * coState ) Execute () {
168
185
s .ResetProgress ()
169
186
170
187
if s .Finished () {
171
- s .logger .Println ("execute: already finished" )
188
+ // s.logger.Println("execute: already finished")
172
189
return
173
190
}
174
191
175
192
t := time .NewTimer (s .deadlockDetection )
176
193
defer t .Stop ()
177
194
178
- s .logger .Println ("execute: unblocking" )
195
+ // s.logger.Println("execute: unblocking")
179
196
s .unblock <- true
180
- s .logger .Println ("execute: unblocked" )
197
+ // s.logger.Println("execute: unblocked")
181
198
182
199
runtime .Gosched ()
183
200
184
201
// Run until blocked (which is also true when finished)
185
202
select {
186
203
case <- s .blocking :
187
- s .logger .Println ("execute: blocked" )
204
+ // s.logger.Println("execute: blocked")
188
205
case <- t .C :
189
206
panic ("coroutine timed out" )
190
207
}
191
208
}
192
209
193
210
func (s * coState ) Exit () {
194
- s .logger .Println ("exit" )
211
+ // s.logger.Println("exit")
195
212
196
213
if s .Finished () {
197
214
return
0 commit comments