Skip to main content

parking_lot/
condvar.rs

1// Copyright 2016 Amanieu d'Antras
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use crate::raw_condvar::RawCondvar;
9
10pub use lock_api::WaitTimeoutResult;
11
12/// A Condition Variable
13///
14/// Condition variables represent the ability to block a thread such that it
15/// consumes no CPU time while waiting for an event to occur. Condition
16/// variables are typically associated with a boolean predicate (a condition)
17/// and a mutex. The predicate is always verified inside of the mutex before
18/// determining that thread must block.
19///
20/// Note that this module places one additional restriction over the system
21/// condition variables: each condvar can be used with only one mutex at a
22/// time. Any attempt to use multiple mutexes on the same condition variable
23/// simultaneously will result in a runtime panic. However it is possible to
24/// switch to a different mutex if there are no threads currently waiting on
25/// the condition variable.
26///
27/// # Differences from the standard library `Condvar`
28///
29/// - No spurious wakeups: A wait will only return a non-timeout result if it
30///   was woken up by `notify_one` or `notify_all`.
31/// - `Condvar::notify_all` will only wake up a single thread, the rest are
32///   requeued to wait for the `Mutex` to be unlocked by the thread that was
33///   woken up.
34/// - Only requires 1 word of space, whereas the standard library boxes the
35///   `Condvar` due to platform limitations.
36/// - Can be statically constructed.
37/// - Does not require any drop glue when dropped.
38/// - Inline fast path for the uncontended case.
39///
40/// # Examples
41///
42/// ```
43/// use parking_lot::{Mutex, Condvar};
44/// use std::sync::Arc;
45/// use std::thread;
46///
47/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
48/// let pair2 = pair.clone();
49///
50/// // Inside of our lock, spawn a new thread, and then wait for it to start
51/// thread::spawn(move|| {
52///     let &(ref lock, ref cvar) = &*pair2;
53///     let mut started = lock.lock();
54///     *started = true;
55///     cvar.notify_one();
56/// });
57///
58/// // wait for the thread to start up
59/// let &(ref lock, ref cvar) = &*pair;
60/// let mut started = lock.lock();
61/// if !*started {
62///     cvar.wait(&mut started);
63/// }
64/// // Note that we used an if instead of a while loop above. This is only
65/// // possible because parking_lot's Condvar will never spuriously wake up.
66/// // This means that wait() will only return after notify_one or notify_all is
67/// // called.
68/// ```
69pub type Condvar = lock_api::Condvar<RawCondvar>;
70
71#[cfg(test)]
72mod tests {
73    use crate::{Condvar, Mutex, MutexGuard};
74    use std::sync::mpsc::channel;
75    use std::sync::Arc;
76    use std::thread;
77    use std::thread::sleep;
78    use std::thread::JoinHandle;
79    use std::time::Duration;
80    use std::time::Instant;
81
82    #[test]
83    fn smoke() {
84        let c = Condvar::new();
85        c.notify_one();
86        c.notify_all();
87    }
88
89    #[test]
90    fn notify_one() {
91        let m = Arc::new(Mutex::new(()));
92        let m2 = m.clone();
93        let c = Arc::new(Condvar::new());
94        let c2 = c.clone();
95
96        let mut g = m.lock();
97        let _t = thread::spawn(move || {
98            let _g = m2.lock();
99            c2.notify_one();
100        });
101        c.wait(&mut g);
102    }
103
104    #[test]
105    fn notify_all() {
106        const N: usize = 10;
107
108        let data = Arc::new((Mutex::new(0), Condvar::new()));
109        let (tx, rx) = channel();
110        for _ in 0..N {
111            let data = data.clone();
112            let tx = tx.clone();
113            thread::spawn(move || {
114                let (lock, cond) = &*data;
115                let mut cnt = lock.lock();
116                *cnt += 1;
117                if *cnt == N {
118                    tx.send(()).unwrap();
119                }
120                while *cnt != 0 {
121                    cond.wait(&mut cnt);
122                }
123                tx.send(()).unwrap();
124            });
125        }
126        drop(tx);
127
128        let (lock, cond) = &*data;
129        rx.recv().unwrap();
130        let mut cnt = lock.lock();
131        *cnt = 0;
132        cond.notify_all();
133        drop(cnt);
134
135        for _ in 0..N {
136            rx.recv().unwrap();
137        }
138    }
139
140    #[test]
141    fn notify_one_return_true() {
142        let m = Arc::new(Mutex::new(()));
143        let m2 = m.clone();
144        let c = Arc::new(Condvar::new());
145        let c2 = c.clone();
146
147        let mut g = m.lock();
148        let _t = thread::spawn(move || {
149            let _g = m2.lock();
150            assert!(c2.notify_one());
151        });
152        c.wait(&mut g);
153    }
154
155    #[test]
156    fn notify_one_return_false() {
157        let m = Arc::new(Mutex::new(()));
158        let c = Arc::new(Condvar::new());
159
160        let _t = thread::spawn(move || {
161            let _g = m.lock();
162            assert!(!c.notify_one());
163        });
164    }
165
166    #[test]
167    fn notify_all_return() {
168        const N: usize = 10;
169
170        let data = Arc::new((Mutex::new(0), Condvar::new()));
171        let (tx, rx) = channel();
172        for _ in 0..N {
173            let data = data.clone();
174            let tx = tx.clone();
175            thread::spawn(move || {
176                let (lock, cond) = &*data;
177                let mut cnt = lock.lock();
178                *cnt += 1;
179                if *cnt == N {
180                    tx.send(()).unwrap();
181                }
182                while *cnt != 0 {
183                    cond.wait(&mut cnt);
184                }
185                tx.send(()).unwrap();
186            });
187        }
188        drop(tx);
189
190        let (lock, cond) = &*data;
191        rx.recv().unwrap();
192        let mut cnt = lock.lock();
193        *cnt = 0;
194        assert_eq!(cond.notify_all(), N);
195        drop(cnt);
196
197        for _ in 0..N {
198            rx.recv().unwrap();
199        }
200
201        assert_eq!(cond.notify_all(), 0);
202    }
203
204    #[test]
205    fn wait_for() {
206        let m = Arc::new(Mutex::new(()));
207        let m2 = m.clone();
208        let c = Arc::new(Condvar::new());
209        let c2 = c.clone();
210
211        let mut g = m.lock();
212        let no_timeout = c.wait_for(&mut g, Duration::from_millis(1));
213        assert!(no_timeout.timed_out());
214
215        let _t = thread::spawn(move || {
216            let _g = m2.lock();
217            c2.notify_one();
218        });
219        let timeout_res = c.wait_for(&mut g, Duration::from_secs(u64::max_value()));
220        assert!(!timeout_res.timed_out());
221
222        drop(g);
223    }
224
225    #[test]
226    fn wait_until() {
227        let m = Arc::new(Mutex::new(()));
228        let m2 = m.clone();
229        let c = Arc::new(Condvar::new());
230        let c2 = c.clone();
231
232        let mut g = m.lock();
233        let no_timeout = c.wait_until(&mut g, Instant::now() + Duration::from_millis(1));
234        assert!(no_timeout.timed_out());
235        let _t = thread::spawn(move || {
236            let _g = m2.lock();
237            c2.notify_one();
238        });
239        let timeout_res = c.wait_until(
240            &mut g,
241            Instant::now() + Duration::from_millis(u32::max_value() as u64),
242        );
243        assert!(!timeout_res.timed_out());
244        drop(g);
245    }
246
247    fn spawn_wait_while_notifier(
248        mutex: Arc<Mutex<u32>>,
249        cv: Arc<Condvar>,
250        num_iters: u32,
251        timeout: Option<Instant>,
252    ) -> JoinHandle<()> {
253        thread::spawn(move || {
254            for epoch in 1..=num_iters {
255                // spin to wait for main test thread to block
256                // before notifying it to wake back up and check
257                // its condition.
258                let mut sleep_backoff = Duration::from_millis(1);
259                let _mutex_guard = loop {
260                    let mutex_guard = mutex.lock();
261
262                    if let Some(timeout) = timeout {
263                        if Instant::now() >= timeout {
264                            return;
265                        }
266                    }
267
268                    if *mutex_guard == epoch {
269                        break mutex_guard;
270                    }
271
272                    drop(mutex_guard);
273
274                    // give main test thread a good chance to
275                    // acquire the lock before this thread does.
276                    sleep(sleep_backoff);
277                    sleep_backoff *= 2;
278                };
279
280                cv.notify_one();
281            }
282        })
283    }
284
285    #[test]
286    fn wait_while_until_internal_does_not_wait_if_initially_false() {
287        let mutex = Arc::new(Mutex::new(0));
288        let cv = Arc::new(Condvar::new());
289
290        let condition = |counter: &mut u32| {
291            *counter += 1;
292            false
293        };
294
295        let mut mutex_guard = mutex.lock();
296        cv.wait_while(&mut mutex_guard, condition);
297
298        assert!(*mutex_guard == 1);
299    }
300
301    #[test]
302    fn wait_while_until_internal_times_out_before_false() {
303        let mutex = Arc::new(Mutex::new(0));
304        let cv = Arc::new(Condvar::new());
305
306        let num_iters = 3;
307        let condition = |counter: &mut u32| {
308            *counter += 1;
309            true
310        };
311
312        let mut mutex_guard = mutex.lock();
313        let timeout = Instant::now() + Duration::from_millis(500);
314        let handle = spawn_wait_while_notifier(mutex.clone(), cv.clone(), num_iters, Some(timeout));
315
316        let timeout_result = cv.wait_while_until(&mut mutex_guard, condition, timeout);
317
318        assert!(timeout_result.timed_out());
319        assert!(*mutex_guard == num_iters + 1);
320
321        // prevent deadlock with notifier
322        drop(mutex_guard);
323        handle.join().unwrap();
324    }
325
326    #[test]
327    fn wait_while_until_internal() {
328        let mutex = Arc::new(Mutex::new(0));
329        let cv = Arc::new(Condvar::new());
330
331        let num_iters = 4;
332
333        let condition = |counter: &mut u32| {
334            *counter += 1;
335            *counter <= num_iters
336        };
337
338        let mut mutex_guard = mutex.lock();
339        let handle = spawn_wait_while_notifier(mutex.clone(), cv.clone(), num_iters, None);
340
341        cv.wait_while(&mut mutex_guard, condition);
342
343        assert!(*mutex_guard == num_iters + 1);
344
345        cv.wait_while(&mut mutex_guard, condition);
346        handle.join().unwrap();
347
348        assert!(*mutex_guard == num_iters + 2);
349    }
350
351    #[test]
352    #[should_panic]
353    fn two_mutexes() {
354        let m = Arc::new(Mutex::new(()));
355        let m2 = m.clone();
356        let m3 = Arc::new(Mutex::new(()));
357        let c = Arc::new(Condvar::new());
358        let c2 = c.clone();
359
360        // Make sure we don't leave the child thread dangling
361        struct PanicGuard<'a>(&'a Condvar);
362        impl<'a> Drop for PanicGuard<'a> {
363            fn drop(&mut self) {
364                self.0.notify_one();
365            }
366        }
367
368        let (tx, rx) = channel();
369        let g = m.lock();
370        let _t = thread::spawn(move || {
371            let mut g = m2.lock();
372            tx.send(()).unwrap();
373            c2.wait(&mut g);
374        });
375        drop(g);
376        rx.recv().unwrap();
377        let _g = m.lock();
378        let _guard = PanicGuard(&c);
379        c.wait(&mut m3.lock());
380    }
381
382    #[test]
383    fn two_mutexes_disjoint() {
384        let m = Arc::new(Mutex::new(()));
385        let m2 = m.clone();
386        let m3 = Arc::new(Mutex::new(()));
387        let c = Arc::new(Condvar::new());
388        let c2 = c.clone();
389
390        let mut g = m.lock();
391        let _t = thread::spawn(move || {
392            let _g = m2.lock();
393            c2.notify_one();
394        });
395        c.wait(&mut g);
396        drop(g);
397
398        let _ = c.wait_for(&mut m3.lock(), Duration::from_millis(1));
399    }
400
401    #[test]
402    fn test_debug_condvar() {
403        let c = Condvar::new();
404        assert_eq!(format!("{:?}", c), "Condvar { .. }");
405    }
406
407    #[test]
408    fn test_condvar_requeue() {
409        let m = Arc::new(Mutex::new(()));
410        let m2 = m.clone();
411        let c = Arc::new(Condvar::new());
412        let c2 = c.clone();
413        let t = thread::spawn(move || {
414            let mut g = m2.lock();
415            c2.wait(&mut g);
416        });
417
418        let mut g = m.lock();
419        while !c.notify_one() {
420            // Wait for the thread to get into wait()
421            MutexGuard::bump(&mut g);
422            // Yield, so the other thread gets a chance to do something.
423            thread::yield_now();
424        }
425        // The thread should have been requeued to the mutex, which we wake up now.
426        drop(g);
427        t.join().unwrap();
428    }
429
430    #[test]
431    fn test_issue_129() {
432        let locks = Arc::new((Mutex::new(0), Condvar::new()));
433
434        let (tx, rx) = channel();
435        for _ in 0..4 {
436            let locks = locks.clone();
437            let tx = tx.clone();
438            thread::spawn(move || {
439                let mut guard = locks.0.lock();
440                *guard += 1;
441                locks.1.wait(&mut guard);
442                locks.1.wait_for(&mut guard, Duration::from_millis(1));
443                locks.1.notify_one();
444                tx.send(()).unwrap();
445            });
446        }
447
448        while *locks.0.lock() != 4 {
449            thread::sleep(Duration::from_millis(100));
450        }
451        locks.1.notify_one();
452
453        for _ in 0..4 {
454            assert_eq!(rx.recv_timeout(Duration::from_millis(500)), Ok(()));
455        }
456    }
457}
458
459/// This module contains an integration test that is heavily inspired from WebKit's own integration
460/// tests for it's own Condvar.
461#[cfg(test)]
462#[cfg(not(miri))] // Miri is too slow
463mod webkit_queue_test {
464    use crate::{Condvar, Mutex, MutexGuard};
465    use std::{collections::VecDeque, sync::Arc, thread, time::Duration};
466
467    #[derive(Clone, Copy)]
468    enum Timeout {
469        Bounded(Duration),
470        Forever,
471    }
472
473    #[derive(Clone, Copy)]
474    enum NotifyStyle {
475        One,
476        All,
477    }
478
479    struct Queue {
480        items: VecDeque<usize>,
481        should_continue: bool,
482    }
483
484    impl Queue {
485        fn new() -> Self {
486            Self {
487                items: VecDeque::new(),
488                should_continue: true,
489            }
490        }
491    }
492
493    fn wait<T: ?Sized>(
494        condition: &Condvar,
495        lock: &mut MutexGuard<'_, T>,
496        predicate: impl Fn(&mut MutexGuard<'_, T>) -> bool,
497        timeout: &Timeout,
498    ) {
499        while !predicate(lock) {
500            match timeout {
501                Timeout::Forever => condition.wait(lock),
502                Timeout::Bounded(bound) => {
503                    condition.wait_for(lock, *bound);
504                }
505            }
506        }
507    }
508
509    fn notify(style: NotifyStyle, condition: &Condvar, should_notify: bool) {
510        match style {
511            NotifyStyle::One => {
512                condition.notify_one();
513            }
514            NotifyStyle::All => {
515                if should_notify {
516                    condition.notify_all();
517                }
518            }
519        }
520    }
521
522    fn run_queue_test(
523        num_producers: usize,
524        num_consumers: usize,
525        max_queue_size: usize,
526        messages_per_producer: usize,
527        notify_style: NotifyStyle,
528        timeout: Timeout,
529        delay: Duration,
530    ) {
531        let input_queue = Arc::new(Mutex::new(Queue::new()));
532        let empty_condition = Arc::new(Condvar::new());
533        let full_condition = Arc::new(Condvar::new());
534
535        let output_vec = Arc::new(Mutex::new(vec![]));
536
537        let consumers = (0..num_consumers)
538            .map(|_| {
539                consumer_thread(
540                    input_queue.clone(),
541                    empty_condition.clone(),
542                    full_condition.clone(),
543                    timeout,
544                    notify_style,
545                    output_vec.clone(),
546                    max_queue_size,
547                )
548            })
549            .collect::<Vec<_>>();
550        let producers = (0..num_producers)
551            .map(|_| {
552                producer_thread(
553                    messages_per_producer,
554                    input_queue.clone(),
555                    empty_condition.clone(),
556                    full_condition.clone(),
557                    timeout,
558                    notify_style,
559                    max_queue_size,
560                )
561            })
562            .collect::<Vec<_>>();
563
564        thread::sleep(delay);
565
566        for producer in producers.into_iter() {
567            producer.join().expect("Producer thread panicked");
568        }
569
570        {
571            let mut input_queue = input_queue.lock();
572            input_queue.should_continue = false;
573        }
574        empty_condition.notify_all();
575
576        for consumer in consumers.into_iter() {
577            consumer.join().expect("Consumer thread panicked");
578        }
579
580        let mut output_vec = output_vec.lock();
581        assert_eq!(output_vec.len(), num_producers * messages_per_producer);
582        output_vec.sort();
583        for msg_idx in 0..messages_per_producer {
584            for producer_idx in 0..num_producers {
585                assert_eq!(msg_idx, output_vec[msg_idx * num_producers + producer_idx]);
586            }
587        }
588    }
589
590    fn consumer_thread(
591        input_queue: Arc<Mutex<Queue>>,
592        empty_condition: Arc<Condvar>,
593        full_condition: Arc<Condvar>,
594        timeout: Timeout,
595        notify_style: NotifyStyle,
596        output_queue: Arc<Mutex<Vec<usize>>>,
597        max_queue_size: usize,
598    ) -> thread::JoinHandle<()> {
599        thread::spawn(move || loop {
600            let (should_notify, result) = {
601                let mut queue = input_queue.lock();
602                wait(
603                    &empty_condition,
604                    &mut queue,
605                    |state| -> bool { !state.items.is_empty() || !state.should_continue },
606                    &timeout,
607                );
608                if queue.items.is_empty() && !queue.should_continue {
609                    return;
610                }
611                let should_notify = queue.items.len() == max_queue_size;
612                let result = queue.items.pop_front();
613                std::mem::drop(queue);
614                (should_notify, result)
615            };
616            notify(notify_style, &full_condition, should_notify);
617
618            if let Some(result) = result {
619                output_queue.lock().push(result);
620            }
621        })
622    }
623
624    fn producer_thread(
625        num_messages: usize,
626        queue: Arc<Mutex<Queue>>,
627        empty_condition: Arc<Condvar>,
628        full_condition: Arc<Condvar>,
629        timeout: Timeout,
630        notify_style: NotifyStyle,
631        max_queue_size: usize,
632    ) -> thread::JoinHandle<()> {
633        thread::spawn(move || {
634            for message in 0..num_messages {
635                let should_notify = {
636                    let mut queue = queue.lock();
637                    wait(
638                        &full_condition,
639                        &mut queue,
640                        |state| state.items.len() < max_queue_size,
641                        &timeout,
642                    );
643                    let should_notify = queue.items.is_empty();
644                    queue.items.push_back(message);
645                    std::mem::drop(queue);
646                    should_notify
647                };
648                notify(notify_style, &empty_condition, should_notify);
649            }
650        })
651    }
652
653    macro_rules! run_queue_tests {
654        ( $( $name:ident(
655            num_producers: $num_producers:expr,
656            num_consumers: $num_consumers:expr,
657            max_queue_size: $max_queue_size:expr,
658            messages_per_producer: $messages_per_producer:expr,
659            notification_style: $notification_style:expr,
660            timeout: $timeout:expr,
661            delay_seconds: $delay_seconds:expr);
662        )* ) => {
663            $(#[test]
664            fn $name() {
665                let delay = Duration::from_secs($delay_seconds);
666                run_queue_test(
667                    $num_producers,
668                    $num_consumers,
669                    $max_queue_size,
670                    $messages_per_producer,
671                    $notification_style,
672                    $timeout,
673                    delay,
674                    );
675            })*
676        };
677    }
678
679    run_queue_tests! {
680        sanity_check_queue(
681            num_producers: 1,
682            num_consumers: 1,
683            max_queue_size: 1,
684            messages_per_producer: 100_000,
685            notification_style: NotifyStyle::All,
686            timeout: Timeout::Bounded(Duration::from_secs(1)),
687            delay_seconds: 0
688        );
689        sanity_check_queue_timeout(
690            num_producers: 1,
691            num_consumers: 1,
692            max_queue_size: 1,
693            messages_per_producer: 100_000,
694            notification_style: NotifyStyle::All,
695            timeout: Timeout::Forever,
696            delay_seconds: 0
697        );
698        new_test_without_timeout_5(
699            num_producers: 1,
700            num_consumers: 5,
701            max_queue_size: 1,
702            messages_per_producer: 100_000,
703            notification_style: NotifyStyle::All,
704            timeout: Timeout::Forever,
705            delay_seconds: 0
706        );
707        one_producer_one_consumer_one_slot(
708            num_producers: 1,
709            num_consumers: 1,
710            max_queue_size: 1,
711            messages_per_producer: 100_000,
712            notification_style: NotifyStyle::All,
713            timeout: Timeout::Forever,
714            delay_seconds: 0
715        );
716        one_producer_one_consumer_one_slot_timeout(
717            num_producers: 1,
718            num_consumers: 1,
719            max_queue_size: 1,
720            messages_per_producer: 100_000,
721            notification_style: NotifyStyle::All,
722            timeout: Timeout::Forever,
723            delay_seconds: 1
724        );
725        one_producer_one_consumer_hundred_slots(
726            num_producers: 1,
727            num_consumers: 1,
728            max_queue_size: 100,
729            messages_per_producer: 1_000_000,
730            notification_style: NotifyStyle::All,
731            timeout: Timeout::Forever,
732            delay_seconds: 0
733        );
734        ten_producers_one_consumer_one_slot(
735            num_producers: 10,
736            num_consumers: 1,
737            max_queue_size: 1,
738            messages_per_producer: 10000,
739            notification_style: NotifyStyle::All,
740            timeout: Timeout::Forever,
741            delay_seconds: 0
742        );
743        ten_producers_one_consumer_hundred_slots_notify_all(
744            num_producers: 10,
745            num_consumers: 1,
746            max_queue_size: 100,
747            messages_per_producer: 10000,
748            notification_style: NotifyStyle::All,
749            timeout: Timeout::Forever,
750            delay_seconds: 0
751        );
752        ten_producers_one_consumer_hundred_slots_notify_one(
753            num_producers: 10,
754            num_consumers: 1,
755            max_queue_size: 100,
756            messages_per_producer: 10000,
757            notification_style: NotifyStyle::One,
758            timeout: Timeout::Forever,
759            delay_seconds: 0
760        );
761        one_producer_ten_consumers_one_slot(
762            num_producers: 1,
763            num_consumers: 10,
764            max_queue_size: 1,
765            messages_per_producer: 10000,
766            notification_style: NotifyStyle::All,
767            timeout: Timeout::Forever,
768            delay_seconds: 0
769        );
770        one_producer_ten_consumers_hundred_slots_notify_all(
771            num_producers: 1,
772            num_consumers: 10,
773            max_queue_size: 100,
774            messages_per_producer: 100_000,
775            notification_style: NotifyStyle::All,
776            timeout: Timeout::Forever,
777            delay_seconds: 0
778        );
779        one_producer_ten_consumers_hundred_slots_notify_one(
780            num_producers: 1,
781            num_consumers: 10,
782            max_queue_size: 100,
783            messages_per_producer: 100_000,
784            notification_style: NotifyStyle::One,
785            timeout: Timeout::Forever,
786            delay_seconds: 0
787        );
788        ten_producers_ten_consumers_one_slot(
789            num_producers: 10,
790            num_consumers: 10,
791            max_queue_size: 1,
792            messages_per_producer: 50000,
793            notification_style: NotifyStyle::All,
794            timeout: Timeout::Forever,
795            delay_seconds: 0
796        );
797        ten_producers_ten_consumers_hundred_slots_notify_all(
798            num_producers: 10,
799            num_consumers: 10,
800            max_queue_size: 100,
801            messages_per_producer: 50000,
802            notification_style: NotifyStyle::All,
803            timeout: Timeout::Forever,
804            delay_seconds: 0
805        );
806        ten_producers_ten_consumers_hundred_slots_notify_one(
807            num_producers: 10,
808            num_consumers: 10,
809            max_queue_size: 100,
810            messages_per_producer: 50000,
811            notification_style: NotifyStyle::One,
812            timeout: Timeout::Forever,
813            delay_seconds: 0
814        );
815    }
816}