Skip to main content

parking_lot/
condvar.rs

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