@@ -100,12 +100,11 @@ func WithAbortEarlyOnErrorOption() WaitForStateOption {
100
100
// the given duration before checking the state. This is useful if the
101
101
// function is called immediately after sending an event to the state machine
102
102
// and the state machine needs some time to process the event.
103
- func (s * CachedObserver ) WaitForState (ctx context.Context ,
103
+ func (c * CachedObserver ) WaitForState (ctx context.Context ,
104
104
timeout time.Duration , state StateType ,
105
105
opts ... WaitForStateOption ) error {
106
106
107
107
var options fsmOptions
108
-
109
108
for _ , opt := range opts {
110
109
opt .apply (& options )
111
110
}
@@ -120,61 +119,77 @@ func (s *CachedObserver) WaitForState(ctx context.Context,
120
119
}
121
120
}
122
121
122
+ // Create a new context with a timeout.
123
123
timeoutCtx , cancel := context .WithTimeout (ctx , timeout )
124
124
defer cancel ()
125
125
126
- // Channel to notify when the desired state is reached
127
- // or an error occurred.
128
- ch := make (chan error )
126
+ ch := c .WaitForStateAsync (timeoutCtx , state , options .abortEarlyOnError )
129
127
130
- // Goroutine to wait on condition variable
131
- go func () {
132
- s . notificationMx . Lock ()
133
- defer s . notificationMx . Unlock ( )
128
+ // Wait for either the condition to be met or for a timeout.
129
+ select {
130
+ case <- timeoutCtx . Done ():
131
+ return NewErrWaitingForStateTimeout ( state )
134
132
135
- for {
136
- // Check if the last state is the desired state
137
- if s .lastNotification .NextState == state {
138
- select {
139
- case <- timeoutCtx .Done ():
140
- return
133
+ case err := <- ch :
134
+ return err
135
+ }
136
+ }
141
137
142
- case ch <- nil :
143
- return
144
- }
138
+ // WaitForStateAsync waits asynchronously until the passed context is canceled
139
+ // or the expected state is reached. The function returns a channel that will
140
+ // receive an error if the expected state is reached or an error occurred. If
141
+ // the context is canceled before the expected state is reached, the channel
142
+ // will receive an ErrWaitingForStateTimeout error.
143
+ func (c * CachedObserver ) WaitForStateAsync (ctx context.Context , state StateType ,
144
+ abortOnEarlyError bool ) chan error {
145
+
146
+ // Channel to notify when the desired state is reached or an error
147
+ // occurred.
148
+ ch := make (chan error , 1 )
149
+
150
+ // Wait on the notification condition variable asynchronously to avoid
151
+ // blocking the caller.
152
+ go func () {
153
+ c .notificationMx .Lock ()
154
+ defer c .notificationMx .Unlock ()
155
+
156
+ // writeResult writes the result to the channel. If the context
157
+ // is canceled, an ErrWaitingForStateTimeout error is written
158
+ // to the channel.
159
+ writeResult := func (err error ) {
160
+ select {
161
+ case <- ctx .Done ():
162
+ ch <- NewErrWaitingForStateTimeout (
163
+ state ,
164
+ )
165
+
166
+ case ch <- err :
145
167
}
168
+ }
146
169
147
- // Check if an error occurred
148
- if s . lastNotification . Event == OnError {
149
- if options . abortEarlyOnError {
150
- select {
151
- case <- timeoutCtx . Done ():
152
- return
170
+ for {
171
+ // Check if the last state is the desired state.
172
+ if c . lastNotification . NextState == state {
173
+ writeResult ( nil )
174
+ return
175
+ }
153
176
154
- case ch <- s .lastNotification .LastActionError :
155
- return
156
- }
177
+ // Check if an error has occurred.
178
+ if c .lastNotification .Event == OnError {
179
+ lastErr := c .lastNotification .LastActionError
180
+ if abortOnEarlyError {
181
+ writeResult (lastErr )
182
+ return
157
183
}
158
184
}
159
185
160
- // Otherwise, wait for the next notification
161
- s .notificationCond .Wait ()
186
+ // Otherwise use the conditional variable to wait for
187
+ // the next notification.
188
+ c .notificationCond .Wait ()
162
189
}
163
190
}()
164
191
165
- // Wait for either the condition to be met or for a timeout
166
- select {
167
- case <- timeoutCtx .Done ():
168
- return NewErrWaitingForStateTimeout (
169
- state , s .lastNotification .NextState ,
170
- )
171
-
172
- case lastActionErr := <- ch :
173
- if lastActionErr != nil {
174
- return lastActionErr
175
- }
176
- return nil
177
- }
192
+ return ch
178
193
}
179
194
180
195
// FixedSizeSlice is a slice with a fixed size.
0 commit comments