1 | use crate::loom::sync::atomic::AtomicUsize; |
2 | |
3 | use std::fmt; |
4 | use std::sync::atomic::Ordering::{AcqRel, Acquire, Release}; |
5 | |
6 | pub(super) struct State { |
7 | val: AtomicUsize, |
8 | } |
9 | |
10 | /// Current state value. |
11 | #[derive (Copy, Clone)] |
12 | pub(super) struct Snapshot(usize); |
13 | |
14 | type UpdateResult = Result<Snapshot, Snapshot>; |
15 | |
16 | /// The task is currently being run. |
17 | const RUNNING: usize = 0b0001; |
18 | |
19 | /// The task is complete. |
20 | /// |
21 | /// Once this bit is set, it is never unset. |
22 | const COMPLETE: usize = 0b0010; |
23 | |
24 | /// Extracts the task's lifecycle value from the state. |
25 | const LIFECYCLE_MASK: usize = 0b11; |
26 | |
27 | /// Flag tracking if the task has been pushed into a run queue. |
28 | const NOTIFIED: usize = 0b100; |
29 | |
30 | /// The join handle is still around. |
31 | const JOIN_INTEREST: usize = 0b1_000; |
32 | |
33 | /// A join handle waker has been set. |
34 | const JOIN_WAKER: usize = 0b10_000; |
35 | |
36 | /// The task has been forcibly cancelled. |
37 | const CANCELLED: usize = 0b100_000; |
38 | |
39 | /// All bits. |
40 | const STATE_MASK: usize = LIFECYCLE_MASK | NOTIFIED | JOIN_INTEREST | JOIN_WAKER | CANCELLED; |
41 | |
42 | /// Bits used by the ref count portion of the state. |
43 | const REF_COUNT_MASK: usize = !STATE_MASK; |
44 | |
45 | /// Number of positions to shift the ref count. |
46 | const REF_COUNT_SHIFT: usize = REF_COUNT_MASK.count_zeros() as usize; |
47 | |
48 | /// One ref count. |
49 | const REF_ONE: usize = 1 << REF_COUNT_SHIFT; |
50 | |
51 | /// State a task is initialized with. |
52 | /// |
53 | /// A task is initialized with three references: |
54 | /// |
55 | /// * A reference that will be stored in an `OwnedTasks` or `LocalOwnedTasks`. |
56 | /// * A reference that will be sent to the scheduler as an ordinary notification. |
57 | /// * A reference for the `JoinHandle`. |
58 | /// |
59 | /// As the task starts with a `JoinHandle`, `JOIN_INTEREST` is set. |
60 | /// As the task starts with a `Notified`, `NOTIFIED` is set. |
61 | const INITIAL_STATE: usize = (REF_ONE * 3) | JOIN_INTEREST | NOTIFIED; |
62 | |
63 | #[must_use ] |
64 | pub(super) enum TransitionToRunning { |
65 | Success, |
66 | Cancelled, |
67 | Failed, |
68 | Dealloc, |
69 | } |
70 | |
71 | #[must_use ] |
72 | pub(super) enum TransitionToIdle { |
73 | Ok, |
74 | OkNotified, |
75 | OkDealloc, |
76 | Cancelled, |
77 | } |
78 | |
79 | #[must_use ] |
80 | pub(super) enum TransitionToNotifiedByVal { |
81 | DoNothing, |
82 | Submit, |
83 | Dealloc, |
84 | } |
85 | |
86 | #[must_use ] |
87 | pub(crate) enum TransitionToNotifiedByRef { |
88 | DoNothing, |
89 | Submit, |
90 | } |
91 | |
92 | #[must_use ] |
93 | pub(super) struct TransitionToJoinHandleDrop { |
94 | pub(super) drop_waker: bool, |
95 | pub(super) drop_output: bool, |
96 | } |
97 | |
98 | /// All transitions are performed via RMW operations. This establishes an |
99 | /// unambiguous modification order. |
100 | impl State { |
101 | /// Returns a task's initial state. |
102 | pub(super) fn new() -> State { |
103 | // The raw task returned by this method has a ref-count of three. See |
104 | // the comment on INITIAL_STATE for more. |
105 | State { |
106 | val: AtomicUsize::new(INITIAL_STATE), |
107 | } |
108 | } |
109 | |
110 | /// Loads the current state, establishes `Acquire` ordering. |
111 | pub(super) fn load(&self) -> Snapshot { |
112 | Snapshot(self.val.load(Acquire)) |
113 | } |
114 | |
115 | /// Attempts to transition the lifecycle to `Running`. This sets the |
116 | /// notified bit to false so notifications during the poll can be detected. |
117 | pub(super) fn transition_to_running(&self) -> TransitionToRunning { |
118 | self.fetch_update_action(|mut next| { |
119 | let action; |
120 | assert!(next.is_notified()); |
121 | |
122 | if !next.is_idle() { |
123 | // This happens if the task is either currently running or if it |
124 | // has already completed, e.g. if it was cancelled during |
125 | // shutdown. Consume the ref-count and return. |
126 | next.ref_dec(); |
127 | if next.ref_count() == 0 { |
128 | action = TransitionToRunning::Dealloc; |
129 | } else { |
130 | action = TransitionToRunning::Failed; |
131 | } |
132 | } else { |
133 | // We are able to lock the RUNNING bit. |
134 | next.set_running(); |
135 | next.unset_notified(); |
136 | |
137 | if next.is_cancelled() { |
138 | action = TransitionToRunning::Cancelled; |
139 | } else { |
140 | action = TransitionToRunning::Success; |
141 | } |
142 | } |
143 | (action, Some(next)) |
144 | }) |
145 | } |
146 | |
147 | /// Transitions the task from `Running` -> `Idle`. |
148 | /// |
149 | /// The transition to `Idle` fails if the task has been flagged to be |
150 | /// cancelled. |
151 | pub(super) fn transition_to_idle(&self) -> TransitionToIdle { |
152 | self.fetch_update_action(|curr| { |
153 | assert!(curr.is_running()); |
154 | |
155 | if curr.is_cancelled() { |
156 | return (TransitionToIdle::Cancelled, None); |
157 | } |
158 | |
159 | let mut next = curr; |
160 | let action; |
161 | next.unset_running(); |
162 | |
163 | if !next.is_notified() { |
164 | // Polling the future consumes the ref-count of the Notified. |
165 | next.ref_dec(); |
166 | if next.ref_count() == 0 { |
167 | action = TransitionToIdle::OkDealloc; |
168 | } else { |
169 | action = TransitionToIdle::Ok; |
170 | } |
171 | } else { |
172 | // The caller will schedule a new notification, so we create a |
173 | // new ref-count for the notification. Our own ref-count is kept |
174 | // for now, and the caller will drop it shortly. |
175 | next.ref_inc(); |
176 | action = TransitionToIdle::OkNotified; |
177 | } |
178 | |
179 | (action, Some(next)) |
180 | }) |
181 | } |
182 | |
183 | /// Transitions the task from `Running` -> `Complete`. |
184 | pub(super) fn transition_to_complete(&self) -> Snapshot { |
185 | const DELTA: usize = RUNNING | COMPLETE; |
186 | |
187 | let prev = Snapshot(self.val.fetch_xor(DELTA, AcqRel)); |
188 | assert!(prev.is_running()); |
189 | assert!(!prev.is_complete()); |
190 | |
191 | Snapshot(prev.0 ^ DELTA) |
192 | } |
193 | |
194 | /// Transitions from `Complete` -> `Terminal`, decrementing the reference |
195 | /// count the specified number of times. |
196 | /// |
197 | /// Returns true if the task should be deallocated. |
198 | pub(super) fn transition_to_terminal(&self, count: usize) -> bool { |
199 | let prev = Snapshot(self.val.fetch_sub(count * REF_ONE, AcqRel)); |
200 | assert!( |
201 | prev.ref_count() >= count, |
202 | "current: {}, sub: {}" , |
203 | prev.ref_count(), |
204 | count |
205 | ); |
206 | prev.ref_count() == count |
207 | } |
208 | |
209 | /// Transitions the state to `NOTIFIED`. |
210 | /// |
211 | /// If no task needs to be submitted, a ref-count is consumed. |
212 | /// |
213 | /// If a task needs to be submitted, the ref-count is incremented for the |
214 | /// new Notified. |
215 | pub(super) fn transition_to_notified_by_val(&self) -> TransitionToNotifiedByVal { |
216 | self.fetch_update_action(|mut snapshot| { |
217 | let action; |
218 | |
219 | if snapshot.is_running() { |
220 | // If the task is running, we mark it as notified, but we should |
221 | // not submit anything as the thread currently running the |
222 | // future is responsible for that. |
223 | snapshot.set_notified(); |
224 | snapshot.ref_dec(); |
225 | |
226 | // The thread that set the running bit also holds a ref-count. |
227 | assert!(snapshot.ref_count() > 0); |
228 | |
229 | action = TransitionToNotifiedByVal::DoNothing; |
230 | } else if snapshot.is_complete() || snapshot.is_notified() { |
231 | // We do not need to submit any notifications, but we have to |
232 | // decrement the ref-count. |
233 | snapshot.ref_dec(); |
234 | |
235 | if snapshot.ref_count() == 0 { |
236 | action = TransitionToNotifiedByVal::Dealloc; |
237 | } else { |
238 | action = TransitionToNotifiedByVal::DoNothing; |
239 | } |
240 | } else { |
241 | // We create a new notified that we can submit. The caller |
242 | // retains ownership of the ref-count they passed in. |
243 | snapshot.set_notified(); |
244 | snapshot.ref_inc(); |
245 | action = TransitionToNotifiedByVal::Submit; |
246 | } |
247 | |
248 | (action, Some(snapshot)) |
249 | }) |
250 | } |
251 | |
252 | /// Transitions the state to `NOTIFIED`. |
253 | pub(super) fn transition_to_notified_by_ref(&self) -> TransitionToNotifiedByRef { |
254 | self.fetch_update_action(|mut snapshot| { |
255 | if snapshot.is_complete() || snapshot.is_notified() { |
256 | // There is nothing to do in this case. |
257 | (TransitionToNotifiedByRef::DoNothing, None) |
258 | } else if snapshot.is_running() { |
259 | // If the task is running, we mark it as notified, but we should |
260 | // not submit as the thread currently running the future is |
261 | // responsible for that. |
262 | snapshot.set_notified(); |
263 | (TransitionToNotifiedByRef::DoNothing, Some(snapshot)) |
264 | } else { |
265 | // The task is idle and not notified. We should submit a |
266 | // notification. |
267 | snapshot.set_notified(); |
268 | snapshot.ref_inc(); |
269 | (TransitionToNotifiedByRef::Submit, Some(snapshot)) |
270 | } |
271 | }) |
272 | } |
273 | |
274 | /// Transitions the state to `NOTIFIED`, unconditionally increasing the ref |
275 | /// count. |
276 | /// |
277 | /// Returns `true` if the notified bit was transitioned from `0` to `1`; |
278 | /// otherwise `false.` |
279 | #[cfg (all( |
280 | tokio_unstable, |
281 | tokio_taskdump, |
282 | feature = "rt" , |
283 | target_os = "linux" , |
284 | any(target_arch = "aarch64" , target_arch = "x86" , target_arch = "x86_64" ) |
285 | ))] |
286 | pub(super) fn transition_to_notified_for_tracing(&self) -> bool { |
287 | self.fetch_update_action(|mut snapshot| { |
288 | if snapshot.is_notified() { |
289 | (false, None) |
290 | } else { |
291 | snapshot.set_notified(); |
292 | snapshot.ref_inc(); |
293 | (true, Some(snapshot)) |
294 | } |
295 | }) |
296 | } |
297 | |
298 | /// Sets the cancelled bit and transitions the state to `NOTIFIED` if idle. |
299 | /// |
300 | /// Returns `true` if the task needs to be submitted to the pool for |
301 | /// execution. |
302 | pub(super) fn transition_to_notified_and_cancel(&self) -> bool { |
303 | self.fetch_update_action(|mut snapshot| { |
304 | if snapshot.is_cancelled() || snapshot.is_complete() { |
305 | // Aborts to completed or cancelled tasks are no-ops. |
306 | (false, None) |
307 | } else if snapshot.is_running() { |
308 | // If the task is running, we mark it as cancelled. The thread |
309 | // running the task will notice the cancelled bit when it |
310 | // stops polling and it will kill the task. |
311 | // |
312 | // The set_notified() call is not strictly necessary but it will |
313 | // in some cases let a wake_by_ref call return without having |
314 | // to perform a compare_exchange. |
315 | snapshot.set_notified(); |
316 | snapshot.set_cancelled(); |
317 | (false, Some(snapshot)) |
318 | } else { |
319 | // The task is idle. We set the cancelled and notified bits and |
320 | // submit a notification if the notified bit was not already |
321 | // set. |
322 | snapshot.set_cancelled(); |
323 | if !snapshot.is_notified() { |
324 | snapshot.set_notified(); |
325 | snapshot.ref_inc(); |
326 | (true, Some(snapshot)) |
327 | } else { |
328 | (false, Some(snapshot)) |
329 | } |
330 | } |
331 | }) |
332 | } |
333 | |
334 | /// Sets the `CANCELLED` bit and attempts to transition to `Running`. |
335 | /// |
336 | /// Returns `true` if the transition to `Running` succeeded. |
337 | pub(super) fn transition_to_shutdown(&self) -> bool { |
338 | let mut prev = Snapshot(0); |
339 | |
340 | let _ = self.fetch_update(|mut snapshot| { |
341 | prev = snapshot; |
342 | |
343 | if snapshot.is_idle() { |
344 | snapshot.set_running(); |
345 | } |
346 | |
347 | // If the task was not idle, the thread currently running the task |
348 | // will notice the cancelled bit and cancel it once the poll |
349 | // completes. |
350 | snapshot.set_cancelled(); |
351 | Some(snapshot) |
352 | }); |
353 | |
354 | prev.is_idle() |
355 | } |
356 | |
357 | /// Optimistically tries to swap the state assuming the join handle is |
358 | /// __immediately__ dropped on spawn. |
359 | pub(super) fn drop_join_handle_fast(&self) -> Result<(), ()> { |
360 | use std::sync::atomic::Ordering::Relaxed; |
361 | |
362 | // Relaxed is acceptable as if this function is called and succeeds, |
363 | // then nothing has been done w/ the join handle. |
364 | // |
365 | // The moment the join handle is used (polled), the `JOIN_WAKER` flag is |
366 | // set, at which point the CAS will fail. |
367 | // |
368 | // Given this, there is no risk if this operation is reordered. |
369 | self.val |
370 | .compare_exchange_weak( |
371 | INITIAL_STATE, |
372 | (INITIAL_STATE - REF_ONE) & !JOIN_INTEREST, |
373 | Release, |
374 | Relaxed, |
375 | ) |
376 | .map(|_| ()) |
377 | .map_err(|_| ()) |
378 | } |
379 | |
380 | /// Unsets the `JOIN_INTEREST` flag. If `COMPLETE` is not set, the `JOIN_WAKER` |
381 | /// flag is also unset. |
382 | /// The returned `TransitionToJoinHandleDrop` indicates whether the `JoinHandle` should drop |
383 | /// the output of the future or the join waker after the transition. |
384 | pub(super) fn transition_to_join_handle_dropped(&self) -> TransitionToJoinHandleDrop { |
385 | self.fetch_update_action(|mut snapshot| { |
386 | assert!(snapshot.is_join_interested()); |
387 | |
388 | let mut transition = TransitionToJoinHandleDrop { |
389 | drop_waker: false, |
390 | drop_output: false, |
391 | }; |
392 | |
393 | snapshot.unset_join_interested(); |
394 | |
395 | if !snapshot.is_complete() { |
396 | // If `COMPLETE` is unset we also unset `JOIN_WAKER` to give the |
397 | // `JoinHandle` exclusive access to the waker following rule 6 in task/mod.rs. |
398 | // The `JoinHandle` will drop the waker if it has exclusive access |
399 | // to drop it. |
400 | snapshot.unset_join_waker(); |
401 | } else { |
402 | // If `COMPLETE` is set the task is completed so the `JoinHandle` is responsible |
403 | // for dropping the output. |
404 | transition.drop_output = true; |
405 | } |
406 | |
407 | if !snapshot.is_join_waker_set() { |
408 | // If the `JOIN_WAKER` bit is unset and the `JOIN_HANDLE` has exclusive access to |
409 | // the join waker and should drop it following this transition. |
410 | // This might happen in two situations: |
411 | // 1. The task is not completed and we just unset the `JOIN_WAKer` above in this |
412 | // function. |
413 | // 2. The task is completed. In that case the `JOIN_WAKER` bit was already unset |
414 | // by the runtime during completion. |
415 | transition.drop_waker = true; |
416 | } |
417 | |
418 | (transition, Some(snapshot)) |
419 | }) |
420 | } |
421 | |
422 | /// Sets the `JOIN_WAKER` bit. |
423 | /// |
424 | /// Returns `Ok` if the bit is set, `Err` otherwise. This operation fails if |
425 | /// the task has completed. |
426 | pub(super) fn set_join_waker(&self) -> UpdateResult { |
427 | self.fetch_update(|curr| { |
428 | assert!(curr.is_join_interested()); |
429 | assert!(!curr.is_join_waker_set()); |
430 | |
431 | if curr.is_complete() { |
432 | return None; |
433 | } |
434 | |
435 | let mut next = curr; |
436 | next.set_join_waker(); |
437 | |
438 | Some(next) |
439 | }) |
440 | } |
441 | |
442 | /// Unsets the `JOIN_WAKER` bit. |
443 | /// |
444 | /// Returns `Ok` has been unset, `Err` otherwise. This operation fails if |
445 | /// the task has completed. |
446 | pub(super) fn unset_waker(&self) -> UpdateResult { |
447 | self.fetch_update(|curr| { |
448 | assert!(curr.is_join_interested()); |
449 | |
450 | if curr.is_complete() { |
451 | return None; |
452 | } |
453 | |
454 | // If the task is completed, this bit may have been unset by |
455 | // `unset_waker_after_complete`. |
456 | assert!(curr.is_join_waker_set()); |
457 | |
458 | let mut next = curr; |
459 | next.unset_join_waker(); |
460 | |
461 | Some(next) |
462 | }) |
463 | } |
464 | |
465 | /// Unsets the `JOIN_WAKER` bit unconditionally after task completion. |
466 | /// |
467 | /// This operation requires the task to be completed. |
468 | pub(super) fn unset_waker_after_complete(&self) -> Snapshot { |
469 | let prev = Snapshot(self.val.fetch_and(!JOIN_WAKER, AcqRel)); |
470 | assert!(prev.is_complete()); |
471 | assert!(prev.is_join_waker_set()); |
472 | Snapshot(prev.0 & !JOIN_WAKER) |
473 | } |
474 | |
475 | pub(super) fn ref_inc(&self) { |
476 | use std::process; |
477 | use std::sync::atomic::Ordering::Relaxed; |
478 | |
479 | // Using a relaxed ordering is alright here, as knowledge of the |
480 | // original reference prevents other threads from erroneously deleting |
481 | // the object. |
482 | // |
483 | // As explained in the [Boost documentation][1], Increasing the |
484 | // reference counter can always be done with memory_order_relaxed: New |
485 | // references to an object can only be formed from an existing |
486 | // reference, and passing an existing reference from one thread to |
487 | // another must already provide any required synchronization. |
488 | // |
489 | // [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html) |
490 | let prev = self.val.fetch_add(REF_ONE, Relaxed); |
491 | |
492 | // If the reference count overflowed, abort. |
493 | if prev > isize::MAX as usize { |
494 | process::abort(); |
495 | } |
496 | } |
497 | |
498 | /// Returns `true` if the task should be released. |
499 | pub(super) fn ref_dec(&self) -> bool { |
500 | let prev = Snapshot(self.val.fetch_sub(REF_ONE, AcqRel)); |
501 | assert!(prev.ref_count() >= 1); |
502 | prev.ref_count() == 1 |
503 | } |
504 | |
505 | /// Returns `true` if the task should be released. |
506 | pub(super) fn ref_dec_twice(&self) -> bool { |
507 | let prev = Snapshot(self.val.fetch_sub(2 * REF_ONE, AcqRel)); |
508 | assert!(prev.ref_count() >= 2); |
509 | prev.ref_count() == 2 |
510 | } |
511 | |
512 | fn fetch_update_action<F, T>(&self, mut f: F) -> T |
513 | where |
514 | F: FnMut(Snapshot) -> (T, Option<Snapshot>), |
515 | { |
516 | let mut curr = self.load(); |
517 | |
518 | loop { |
519 | let (output, next) = f(curr); |
520 | let next = match next { |
521 | Some(next) => next, |
522 | None => return output, |
523 | }; |
524 | |
525 | let res = self.val.compare_exchange(curr.0, next.0, AcqRel, Acquire); |
526 | |
527 | match res { |
528 | Ok(_) => return output, |
529 | Err(actual) => curr = Snapshot(actual), |
530 | } |
531 | } |
532 | } |
533 | |
534 | fn fetch_update<F>(&self, mut f: F) -> Result<Snapshot, Snapshot> |
535 | where |
536 | F: FnMut(Snapshot) -> Option<Snapshot>, |
537 | { |
538 | let mut curr = self.load(); |
539 | |
540 | loop { |
541 | let next = match f(curr) { |
542 | Some(next) => next, |
543 | None => return Err(curr), |
544 | }; |
545 | |
546 | let res = self.val.compare_exchange(curr.0, next.0, AcqRel, Acquire); |
547 | |
548 | match res { |
549 | Ok(_) => return Ok(next), |
550 | Err(actual) => curr = Snapshot(actual), |
551 | } |
552 | } |
553 | } |
554 | } |
555 | |
556 | // ===== impl Snapshot ===== |
557 | |
558 | impl Snapshot { |
559 | /// Returns `true` if the task is in an idle state. |
560 | pub(super) fn is_idle(self) -> bool { |
561 | self.0 & (RUNNING | COMPLETE) == 0 |
562 | } |
563 | |
564 | /// Returns `true` if the task has been flagged as notified. |
565 | pub(super) fn is_notified(self) -> bool { |
566 | self.0 & NOTIFIED == NOTIFIED |
567 | } |
568 | |
569 | fn unset_notified(&mut self) { |
570 | self.0 &= !NOTIFIED; |
571 | } |
572 | |
573 | fn set_notified(&mut self) { |
574 | self.0 |= NOTIFIED; |
575 | } |
576 | |
577 | pub(super) fn is_running(self) -> bool { |
578 | self.0 & RUNNING == RUNNING |
579 | } |
580 | |
581 | fn set_running(&mut self) { |
582 | self.0 |= RUNNING; |
583 | } |
584 | |
585 | fn unset_running(&mut self) { |
586 | self.0 &= !RUNNING; |
587 | } |
588 | |
589 | pub(super) fn is_cancelled(self) -> bool { |
590 | self.0 & CANCELLED == CANCELLED |
591 | } |
592 | |
593 | fn set_cancelled(&mut self) { |
594 | self.0 |= CANCELLED; |
595 | } |
596 | |
597 | /// Returns `true` if the task's future has completed execution. |
598 | pub(super) fn is_complete(self) -> bool { |
599 | self.0 & COMPLETE == COMPLETE |
600 | } |
601 | |
602 | pub(super) fn is_join_interested(self) -> bool { |
603 | self.0 & JOIN_INTEREST == JOIN_INTEREST |
604 | } |
605 | |
606 | fn unset_join_interested(&mut self) { |
607 | self.0 &= !JOIN_INTEREST; |
608 | } |
609 | |
610 | pub(super) fn is_join_waker_set(self) -> bool { |
611 | self.0 & JOIN_WAKER == JOIN_WAKER |
612 | } |
613 | |
614 | fn set_join_waker(&mut self) { |
615 | self.0 |= JOIN_WAKER; |
616 | } |
617 | |
618 | fn unset_join_waker(&mut self) { |
619 | self.0 &= !JOIN_WAKER; |
620 | } |
621 | |
622 | pub(super) fn ref_count(self) -> usize { |
623 | (self.0 & REF_COUNT_MASK) >> REF_COUNT_SHIFT |
624 | } |
625 | |
626 | fn ref_inc(&mut self) { |
627 | assert!(self.0 <= isize::MAX as usize); |
628 | self.0 += REF_ONE; |
629 | } |
630 | |
631 | pub(super) fn ref_dec(&mut self) { |
632 | assert!(self.ref_count() > 0); |
633 | self.0 -= REF_ONE; |
634 | } |
635 | } |
636 | |
637 | impl fmt::Debug for State { |
638 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
639 | let snapshot: Snapshot = self.load(); |
640 | snapshot.fmt(fmt) |
641 | } |
642 | } |
643 | |
644 | impl fmt::Debug for Snapshot { |
645 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
646 | fmt&mut DebugStruct<'_, '_>.debug_struct("Snapshot" ) |
647 | .field("is_running" , &self.is_running()) |
648 | .field("is_complete" , &self.is_complete()) |
649 | .field("is_notified" , &self.is_notified()) |
650 | .field("is_cancelled" , &self.is_cancelled()) |
651 | .field("is_join_interested" , &self.is_join_interested()) |
652 | .field("is_join_waker_set" , &self.is_join_waker_set()) |
653 | .field(name:"ref_count" , &self.ref_count()) |
654 | .finish() |
655 | } |
656 | } |
657 | |