2
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
4
5
- use std:: mem;
5
+ use std:: { mem, sync :: Mutex } ;
6
6
7
7
use super :: { RustFutureContinuationCallback , RustFuturePoll } ;
8
8
@@ -20,7 +20,12 @@ use super::{RustFutureContinuationCallback, RustFuturePoll};
20
20
/// state, invoking any future callbacks as soon as they're stored.
21
21
22
22
#[ derive( Debug ) ]
23
- pub ( super ) enum Scheduler {
23
+ pub ( super ) struct Scheduler {
24
+ state : Mutex < SchedulerState > ,
25
+ }
26
+
27
+ #[ derive( Debug ) ]
28
+ pub ( super ) enum SchedulerState {
24
29
/// No continuations set, neither wake() nor cancel() called.
25
30
Empty ,
26
31
/// `wake()` was called when there was no continuation set. The next time `store` is called,
@@ -33,58 +38,138 @@ pub(super) enum Scheduler {
33
38
Set ( RustFutureContinuationCallback , * const ( ) ) ,
34
39
}
35
40
36
- impl Scheduler {
37
- pub ( super ) fn new ( ) -> Self {
38
- Self :: Empty
41
+ /// Encapsulates a call to a RustFutureContinuationCallback
42
+ struct CallbackCall {
43
+ callback : RustFutureContinuationCallback ,
44
+ data : * const ( ) ,
45
+ poll_data : RustFuturePoll ,
46
+ }
47
+
48
+ impl CallbackCall {
49
+ fn new (
50
+ callback : RustFutureContinuationCallback ,
51
+ data : * const ( ) ,
52
+ poll_data : RustFuturePoll ,
53
+ ) -> Self {
54
+ Self {
55
+ callback,
56
+ data,
57
+ poll_data,
58
+ }
39
59
}
40
60
41
- /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or
42
- /// `Cancelled` state, call the continuation immediately with the data.
43
- pub ( super ) fn store ( & mut self , callback : RustFutureContinuationCallback , data : * const ( ) ) {
61
+ fn invoke ( self ) {
62
+ ( self . callback ) ( self . data , self . poll_data )
63
+ }
64
+ }
65
+
66
+ /// The SchedulerState impl contains all the ways to mutate the inner state field.
67
+ ///
68
+ /// All methods return an `Option<CallbackCall>` rather than invoking the callback directly.
69
+ /// This is important, since the Mutex is locked while inside these methods. If we called the
70
+ /// callback directly, the foreign code could poll the future again, which would try to lock the
71
+ /// mutex again and lead to a deadlock.
72
+ impl SchedulerState {
73
+ fn store (
74
+ & mut self ,
75
+ callback : RustFutureContinuationCallback ,
76
+ data : * const ( ) ,
77
+ ) -> Option < CallbackCall > {
44
78
match self {
45
- Self :: Empty => * self = Self :: Set ( callback, data) ,
79
+ Self :: Empty => {
80
+ * self = Self :: Set ( callback, data) ;
81
+ None
82
+ }
46
83
Self :: Set ( old_callback, old_data) => {
47
84
log:: error!(
48
- "store: observed `Self ::Set` state. Is poll() being called from multiple threads at once?"
85
+ "store: observed `SchedulerState ::Set` state. Is poll() being called from multiple threads at once?"
49
86
) ;
50
- old_callback ( * old_data, RustFuturePoll :: Ready ) ;
87
+ let call = CallbackCall :: new ( * old_callback , * old_data, RustFuturePoll :: Ready ) ;
51
88
* self = Self :: Set ( callback, data) ;
89
+ Some ( call)
52
90
}
53
91
Self :: Waked => {
54
92
* self = Self :: Empty ;
55
- callback ( data, RustFuturePoll :: MaybeReady ) ;
56
- }
57
- Self :: Cancelled => {
58
- callback ( data, RustFuturePoll :: Ready ) ;
93
+ Some ( CallbackCall :: new (
94
+ callback,
95
+ data,
96
+ RustFuturePoll :: MaybeReady ,
97
+ ) )
59
98
}
99
+ Self :: Cancelled => Some ( CallbackCall :: new ( callback, data, RustFuturePoll :: Ready ) ) ,
60
100
}
61
101
}
62
102
63
- pub ( super ) fn wake ( & mut self ) {
103
+ fn wake ( & mut self ) -> Option < CallbackCall > {
64
104
match self {
65
105
// If we had a continuation set, then call it and transition to the `Empty` state.
66
- Self :: Set ( callback, old_data) => {
106
+ SchedulerState :: Set ( callback, old_data) => {
67
107
let old_data = * old_data;
68
108
let callback = * callback;
69
- * self = Self :: Empty ;
70
- callback ( old_data, RustFuturePoll :: MaybeReady ) ;
109
+ * self = SchedulerState :: Empty ;
110
+ Some ( CallbackCall :: new (
111
+ callback,
112
+ old_data,
113
+ RustFuturePoll :: MaybeReady ,
114
+ ) )
71
115
}
72
116
// If we were in the `Empty` state, then transition to `Waked`. The next time `store`
73
117
// is called, we will immediately call the continuation.
74
- Self :: Empty => * self = Self :: Waked ,
118
+ SchedulerState :: Empty => {
119
+ * self = SchedulerState :: Waked ;
120
+ None
121
+ }
75
122
// This is a no-op if we were in the `Cancelled` or `Waked` state.
76
- _ => ( ) ,
123
+ _ => None ,
124
+ }
125
+ }
126
+
127
+ fn cancel ( & mut self ) -> Option < CallbackCall > {
128
+ if let SchedulerState :: Set ( callback, old_data) =
129
+ mem:: replace ( self , SchedulerState :: Cancelled )
130
+ {
131
+ Some ( CallbackCall :: new ( callback, old_data, RustFuturePoll :: Ready ) )
132
+ } else {
133
+ None
134
+ }
135
+ }
136
+ }
137
+
138
+ impl Scheduler {
139
+ pub ( super ) fn new ( ) -> Self {
140
+ Self {
141
+ state : Mutex :: new ( SchedulerState :: Empty ) ,
77
142
}
78
143
}
79
144
80
- pub ( super ) fn cancel ( & mut self ) {
81
- if let Self :: Set ( callback, old_data) = mem:: replace ( self , Self :: Cancelled ) {
82
- callback ( old_data, RustFuturePoll :: Ready ) ;
145
+ /// Call a method on the inner state field
146
+ ///
147
+ /// If it returns a callback to invoke, then make the call after releasing the mutex.
148
+ fn call_state_method ( & self , f : impl Fn ( & mut SchedulerState ) -> Option < CallbackCall > ) {
149
+ let mut state = self . state . lock ( ) . unwrap ( ) ;
150
+ let callback_call = f ( & mut state) ;
151
+ drop ( state) ;
152
+ if let Some ( callback_call) = callback_call {
153
+ callback_call. invoke ( )
83
154
}
84
155
}
85
156
157
+ /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or
158
+ /// `Cancelled` state, call the continuation immediately with the data.
159
+ pub ( super ) fn store ( & self , callback : RustFutureContinuationCallback , data : * const ( ) ) {
160
+ self . call_state_method ( |state| state. store ( callback, data) )
161
+ }
162
+
163
+ pub ( super ) fn wake ( & self ) {
164
+ self . call_state_method ( SchedulerState :: wake)
165
+ }
166
+
167
+ pub ( super ) fn cancel ( & self ) {
168
+ self . call_state_method ( SchedulerState :: cancel)
169
+ }
170
+
86
171
pub ( super ) fn is_cancelled ( & self ) -> bool {
87
- matches ! ( self , Self :: Cancelled )
172
+ matches ! ( * self . state . lock ( ) . unwrap ( ) , SchedulerState :: Cancelled )
88
173
}
89
174
}
90
175
0 commit comments