Skip to main content

futures_rate/
pass.rs

1use crate::inner::{InnerPool, TokenFetcher};
2use crate::InterruptedReason;
3use std::cell::RefCell;
4use std::collections::HashSet;
5use std::future::Future;
6use std::pin::Pin;
7use std::sync::Arc;
8use std::task::{Context, Poll};
9
10thread_local!(
11    static PERMIT_SET: RefCell<HashSet<usize>> = RefCell::new(HashSet::new());
12);
13
14pub(crate) trait TokenHolder {
15    fn render_token(&mut self);
16}
17
18#[deprecated(
19    since = "0.1.4",
20    note = "\
21        The `Permit` is going to be deprecated in favor of calling GateKeeper's `issue()` method \
22        to guard the gate. This struct, along with the associated API (i.e. `register()`), will be
23        removed in the 0.1.5 release.
24    "
25)]
26pub struct Permit<R, F>
27where
28    R: Send + 'static,
29    F: Future<Output = R> + 'static,
30{
31    fut: F,
32    pool: Arc<InnerPool>,
33}
34
35impl<R, F> Permit<R, F>
36where
37    R: Send + 'static,
38    F: Future<Output = R> + 'static,
39{
40    pub(crate) fn new(fut: F, pool: Arc<InnerPool>) -> Self {
41        Permit { fut, pool }
42    }
43}
44
45impl<R, F> Future for Permit<R, F>
46where
47    R: Send + 'static,
48    F: std::future::Future<Output = R> + 'static,
49{
50    type Output = R;
51
52    fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
53        // dissolve the pin to get inner contents
54        let (fut, pool) = unsafe {
55            let ptr = Pin::get_unchecked_mut(self);
56
57            (Pin::new_unchecked(&mut ptr.fut), &ptr.pool)
58        };
59
60        // the current pool's id, to identify the gatekeeper
61        let pool_id = pool.get_id();
62
63        // check if the parent future has already obtained the permit
64        let need_token = PERMIT_SET.with(|set| !set.borrow().contains(&pool_id));
65
66        // if we're the first future to try the gatekeeper, wait for a permit to be available
67        if need_token {
68            pool.request_token(false);
69
70            PERMIT_SET.with(|set| {
71                (*set.borrow_mut()).insert(pool_id);
72            });
73        }
74
75        // poll the future, do the actual work
76        let res = fut.poll(ctx);
77
78        // now we're ready to return the TicketStub, if we're the one requested it
79        if need_token {
80            PERMIT_SET.with(|set| {
81                (*set.borrow_mut()).remove(&pool_id);
82            });
83
84            pool.return_token();
85        }
86
87        res
88    }
89}
90
91pub(crate) struct Ticket<R, F>
92where
93    R: Send + 'static,
94    F: Future<Output = R> + 'static,
95{
96    pool_id: usize,
97    token_obtained: bool,
98    pool: Option<Arc<InnerPool>>,
99    fut: Option<Pin<Box<F>>>,
100}
101
102impl<R, F> Ticket<R, F>
103where
104    R: Send + 'static,
105    F: Future<Output = R> + 'static,
106{
107    pub(crate) fn new(pool: Arc<InnerPool>, fut: Option<F>) -> Self {
108        Ticket {
109            pool_id: 0,
110            token_obtained: false,
111            pool: Some(pool),
112            fut: fut.map(Box::pin),
113        }
114    }
115
116    fn request_token(&mut self) -> bool {
117        assert!(
118            !self.token_obtained,
119            "failed to return previously obtained token ... "
120        );
121
122        if let Some(pool) = self.pool.as_mut() {
123            if pool.request_token(true) {
124                let pool_id = pool.get_id();
125
126                PERMIT_SET.with(|set| {
127                    set.borrow_mut().insert(pool_id);
128                });
129
130                self.pool_id = pool_id;
131                self.token_obtained = true;
132
133                return true;
134            }
135        }
136
137        false
138    }
139
140    fn make_stub(&mut self) -> TicketStub {
141        // now the token has been transferred to the `stub`, we no longer own the token, and we
142        // won't need to render the token from the drop function.
143        self.token_obtained = false;
144
145        // generate the stub from the ticket
146        TicketStub {
147            pool: self.pool.take().unwrap(),
148        }
149    }
150}
151
152impl<R, F> TokenHolder for Ticket<R, F>
153where
154    R: Send + 'static,
155    F: Future<Output = R> + 'static,
156{
157    fn render_token(&mut self) {
158        if let Some(pool) = self.pool.as_ref() {
159            // if we own the reference to the pool, meaning we also own the token that we took, so
160            // we can return it now.
161            assert!(
162                self.token_obtained,
163                "failed at double-returning a previously obtained token ... "
164            );
165
166            pool.return_token();
167
168            PERMIT_SET.with(|set| {
169                set.borrow_mut().remove(&self.pool_id);
170            });
171
172            self.token_obtained = false;
173        }
174    }
175}
176
177impl<R, F> Drop for Ticket<R, F>
178where
179    R: Send + 'static,
180    F: Future<Output = R> + 'static,
181{
182    fn drop(&mut self) {
183        self.render_token();
184    }
185}
186
187impl<R, F> Future for Ticket<R, F>
188where
189    R: Send + 'static,
190    F: Future<Output = R> + 'static,
191{
192    type Output = Result<(Option<TicketStub>, Option<R>), InterruptedReason>;
193
194    fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
195        assert!(
196            self.pool.is_some(),
197            "The pass has already been issued, yet the ticket is polled for access again ... "
198        );
199
200        //TODO: check remote_control, if interrupted, return
201        //       Poll::Ready(Err(InterruptedReason::Cancelled));
202
203        let ref_this = self.get_mut();
204
205        // check if the parent future has already obtained the permit
206        let need_token = if ref_this.pool_id == 0 {
207            true
208        } else {
209            PERMIT_SET.with(|set|
210                !set.borrow().contains(&ref_this.pool_id)
211            )
212        };
213
214        if !need_token || ref_this.request_token() {
215            // if we own the future (i.e. we're in the `TicketStubPolicy::Cooperative` mode), poll
216            // the future and return from what we got. Either way, the TicketStub will be returned
217            // afterwards -- either explicitly when results are pending, or implicitly when the
218            // `Ticket` goes out of the scope and return the TicketStub while being dropped.
219            if let Some(fut) = ref_this.fut.as_mut() {
220                return match fut.as_mut().poll(ctx) {
221                    Poll::Pending => {
222                        // this is the key move for the cooperative mode: we return the token
223                        // immediately after a pending result, meaning we won't wait for the future
224                        // to complete before returning the token.
225                        if need_token {
226                            ref_this.render_token();
227                        }
228
229                        Poll::Pending
230                    }
231                    Poll::Ready(val) => {
232                        // the future is done, we will return the result. the token will be returned
233                        Poll::Ready(Ok((
234                            None,
235                            Some(val)
236                        )))
237                    }
238                };
239            }
240
241            // if running in `TicketStubPolicy::Preemptive` mode, we get the TicketStub, and we
242            // create the TicketStubPass with the reference to the pool, and we will run the future
243            // to the completion. The TicketStub will be returned when the `Ticket` goes out of the
244            // scope after the future is completed. This is the key move, since we generated a stub
245            // from the token, which will live until the future (owned by the intermediate future
246            // generator between gatekeeper and the ticket) is moved towards completion.
247            return Poll::Ready(Ok((
248                Some(ref_this.make_stub()),
249                None
250            )));
251        }
252
253        // we can't get a token yet, make sure correct context are set, then we will go back to wait.
254        if let Some(pool) = ref_this.pool.as_ref() {
255            // only enqueue to wake up if we're in the preemptive mode; otherwise the owning future
256            // will wake us up
257            if ref_this.fut.is_none() {
258                pool.enqueue(ctx.waker().clone());
259            }
260        }
261
262        Poll::Pending
263    }
264}
265
266pub(crate) struct TicketStub {
267    pool: Arc<InnerPool>,
268}
269
270impl TokenHolder for TicketStub {
271    fn render_token(&mut self) {
272        self.pool.return_token();
273    }
274}
275
276impl Drop for TicketStub {
277    fn drop(&mut self) {
278        // if we still own the reference to the poll, it means we need to return the TicketStub to the
279        // pool. A Lannister never forgets his or her debts!
280        self.render_token();
281    }
282}