1use std::collections::HashMap;
39use std::fmt::Debug;
40use std::future::Future;
41use std::marker::PhantomData;
42use std::pin::Pin;
43use std::sync::atomic::{AtomicBool, AtomicU16, Ordering};
44use std::sync::{Arc, Mutex, RwLock};
45use std::task::{Context, Poll, ready};
46
47use futures::future::Either;
48use pin_project::{pin_project, pinned_drop};
49use tokio::runtime::Handle;
50use tokio::sync::Notify;
51use tracing::{debug, error};
52
53pub use super::errors::SingleflightError;
54use crate::error_printer::ErrorPrinter;
55
56type SingleflightResult<T, E> = Result<T, SingleflightError<E>>;
57type CallMap<T, E> = HashMap<String, Arc<Call<T, E>>>;
58type CallCreate<'a, T, E> = (Arc<Call<T, E>>, CreateGuard<'a, T, E>);
59
60pub trait ResultType: Send + Clone + Sync + Debug {}
67impl<T: Send + Clone + Sync + Debug> ResultType for T {}
68
69pub trait ResultError: Send + Debug + Sync {}
73impl<E: Send + Debug + Sync> ResultError for E {}
74
75pub trait TaskFuture<T, E>: Future<Output = Result<T, E>> + Send {}
79impl<T, E, F: Future<Output = Result<T, E>> + Send> TaskFuture<T, E> for F {}
80
81#[derive(Debug, Clone)]
90struct Call<T, E>
91where
92 T: ResultType,
93 E: ResultError,
94{
95 nt: Arc<Notify>,
97
98 res: Arc<RwLock<Option<SingleflightResult<T, E>>>>,
111
112 num_waiters: Arc<AtomicU16>,
114}
115
116impl<T, E> Call<T, E>
117where
118 T: ResultType,
119 E: ResultError,
120{
121 fn new() -> Self {
122 Self {
123 nt: Arc::new(Notify::new()),
124 res: Arc::new(RwLock::new(None)),
125 num_waiters: Arc::new(AtomicU16::new(0)),
126 }
127 }
128
129 fn complete(&self, res: SingleflightResult<T, E>) {
132 let mut val = self.res.write().unwrap();
134 *val = Some(res);
135 self.nt.notify_waiters();
136 let num_waiters = self.num_waiters.load(Ordering::SeqCst);
137 debug!("Completed Call with: {} waiters", num_waiters);
138 }
139
140 fn get_future(&self) -> impl Future<Output = SingleflightResult<T, E>> + '_ {
143 let res = self.res.read().unwrap();
145 if let Some(result) = res.clone() {
146 debug!("Call already completed");
148 Either::Left(async move { result })
149 } else {
150 self.num_waiters.fetch_add(1, Ordering::SeqCst);
152 debug!("Adding to Call's Notify");
153
154 let notified = self.nt.notified();
158 Either::Right(async move {
159 notified.await;
160 self.get()
161 })
162 }
163 }
164
165 fn get(&self) -> SingleflightResult<T, E> {
168 let res = self.res.read().unwrap();
169 res.clone().unwrap_or(Err(SingleflightError::NoResult))
170 }
171}
172
173#[derive(Debug)]
176pub struct Group<T, E>
177where
178 T: ResultType + 'static,
179 E: ResultError,
180{
181 call_map: Arc<Mutex<CallMap<T, E>>>,
182 _marker: PhantomData<fn(E)>,
183}
184
185impl<T, E: 'static> Default for Group<T, E>
186where
187 T: ResultType + 'static,
188 E: ResultError,
189{
190 fn default() -> Self {
191 Self {
192 call_map: Arc::new(Default::default()),
193 _marker: Default::default(),
194 }
195 }
196}
197
198impl<T, E: 'static> Group<T, E>
199where
200 T: ResultType + 'static,
201 E: ResultError,
202{
203 pub fn new() -> Group<T, E> {
205 Self::default()
206 }
207
208 pub async fn work(
219 &self,
220 key: &str,
221 fut: impl TaskFuture<T, E> + 'static,
222 ) -> (Result<T, SingleflightError<E>>, bool) {
223 let (call, create_guard) = match self.get_call_or_create(key) {
225 Ok((call, create_guard)) => (call, create_guard),
226 Err(err) => return (Err(err), false),
227 };
228 match &create_guard {
230 CreateGuard::Owned(_, _) => {
231 let owner_task = OwnerTask::new(fut, call.clone());
233 let owner_handle = Handle::current().spawn(owner_task);
234
235 match owner_handle.await {
237 Ok(res) => (res, true),
238 Err(e) => (Err(SingleflightError::JoinError(e.to_string())), true),
239 }
240 },
241 CreateGuard::Waiter => (call.get_future().await, false),
242 }
243 }
244
245 pub async fn work_dump_caller_info(
247 &self,
248 key: &str,
249 fut: impl TaskFuture<T, E> + 'static,
250 ) -> Result<T, SingleflightError<E>> {
251 let (result, _) = self.work(key, fut).await;
252 result
253 }
254
255 fn get_call_or_create<'a>(&'a self, key: &'a str) -> Result<CallCreate<'a, T, E>, SingleflightError<E>> {
263 let mut m = self
264 .call_map
265 .lock()
266 .log_error("Failed to lock call map")
267 .map_err(|_| SingleflightError::GroupLockPoisoned)?;
268 if let Some(c) = m.get(key).cloned() {
269 Ok((c, CreateGuard::Waiter))
270 } else {
271 let c = Arc::new(Call::new());
272 let our_call = c.clone();
273 m.insert(key.to_owned(), c);
274 Ok((our_call, CreateGuard::Owned(self, key)))
275 }
276 }
277
278 fn remove_call(&self, key: &str) -> SingleflightResult<(), E> {
281 let mut m = self
282 .call_map
283 .lock()
284 .log_error("Failed to lock call map")
285 .map_err(|_| SingleflightError::GroupLockPoisoned)?;
286 m.remove(key).ok_or(SingleflightError::CallMissing)?;
287 Ok(())
288 }
289}
290
291enum CreateGuard<'a, T, E>
295where
296 T: ResultType + 'static,
297 E: ResultError + 'static,
298{
299 Owned(&'a Group<T, E>, &'a str),
300 Waiter,
301}
302
303impl<T, E> Drop for CreateGuard<'_, T, E>
304where
305 T: ResultType + 'static,
306 E: ResultError + 'static,
307{
308 fn drop(&mut self) {
309 match self {
310 CreateGuard::Owned(group, key) => group
311 .remove_call(key)
312 .inspect_err(|err| error!(?err, "Couldn't remove call from map"))
313 .unwrap(),
314 CreateGuard::Waiter => {},
315 }
316 }
317}
318
319#[pin_project(PinnedDrop)]
331#[must_use = "futures do nothing unless you `.await` or poll them"]
332struct OwnerTask<T, E, F>
333where
334 T: ResultType,
335 E: ResultError,
336 F: TaskFuture<T, E>,
337{
338 #[pin]
339 fut: F,
340 got_response: AtomicBool,
341 call: Arc<Call<T, E>>,
342}
343
344impl<T, E, F> OwnerTask<T, E, F>
345where
346 T: ResultType,
347 E: ResultError,
348 F: TaskFuture<T, E>,
349{
350 fn new(fut: F, call: Arc<Call<T, E>>) -> Self {
351 Self {
352 fut,
353 got_response: AtomicBool::new(false),
354 call,
355 }
356 }
357}
358
359impl<T, E, F> Future for OwnerTask<T, E, F>
360where
361 T: ResultType,
362 E: ResultError,
363 F: TaskFuture<T, E>,
364{
365 type Output = Result<T, SingleflightError<E>>;
366
367 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
368 let this = self.project();
369 let res: Result<T, E> = ready!(this.fut.poll(cx));
370 let res = res.map_err(|e| SingleflightError::InternalError(e));
371 let call = this.call;
373 this.got_response.store(true, Ordering::SeqCst);
374 call.complete(res.clone());
375 Poll::Ready(res)
376 }
377}
378
379#[pinned_drop]
380impl<T, E, F> PinnedDrop for OwnerTask<T, E, F>
381where
382 T: ResultType,
383 E: ResultError,
384 F: TaskFuture<T, E>,
385{
386 fn drop(self: Pin<&mut Self>) {
387 let this = self.project();
390 if !this.got_response.load(Ordering::SeqCst) {
391 let call = this.call;
392 call.complete(Err(SingleflightError::OwnerPanicked));
393 }
394 }
395}
396
397#[cfg(test)]
398pub(crate) mod tests {
399 use std::sync::Arc;
400 use std::sync::atomic::{AtomicU32, Ordering};
401 use std::time::Duration;
402
403 use futures::future::join_all;
404 use tokio::runtime::Handle;
405 use tokio::task::JoinHandle;
406 use tokio::time::timeout;
407
408 use super::super::errors::SingleflightError;
409 use super::{Call, Group, OwnerTask};
410 use crate::core::XetContext;
411
412 pub(crate) const WAITER_TIMEOUT: Duration = Duration::from_millis(100);
417
418 const RES: usize = 7;
419
420 async fn return_res() -> Result<usize, ()> {
421 Ok(RES)
422 }
423
424 async fn expensive_fn(x: Arc<AtomicU32>, resp: usize) -> Result<usize, ()> {
425 tokio::time::sleep(Duration::new(1, 0)).await;
426 x.fetch_add(1, Ordering::SeqCst);
427 Ok(resp)
428 }
429
430 #[test]
431 fn test_simple_with_xet_runtime() {
432 let ctx = XetContext::default().unwrap();
433 let g = Group::new();
434 let res = ctx
435 .runtime
436 .bridge_sync(async move { g.work("key", return_res()).await })
437 .unwrap()
438 .0;
439 let r = res.unwrap();
440 assert_eq!(r, RES);
441 }
442
443 #[tokio::test]
444 async fn test_simple() {
445 let g = Group::new();
446 let res = g.work("key", return_res()).await.0;
447 let r = res.unwrap();
448 assert_eq!(r, RES);
449 }
450
451 #[test]
452 #[cfg_attr(feature = "smoke-test", ignore)]
453 fn test_multiple_threads_with_xet_runtime() {
454 let times_called = Arc::new(AtomicU32::new(0));
455 let ctx = XetContext::default().unwrap();
456 let g: Arc<Group<usize, ()>> = Arc::new(Group::new());
457 let mut handlers: Vec<JoinHandle<(usize, bool)>> = Vec::new();
458 let runtime = ctx.runtime.clone();
459 let tasks = async move {
460 for _ in 0..10 {
461 let g = g.clone();
462 let counter = times_called.clone();
463 handlers.push(runtime.spawn(async move {
464 let tup = g.work("key", expensive_fn(counter, RES)).await;
465 let res = tup.0;
466 let fn_response = res.unwrap();
467 (fn_response, tup.1)
468 }));
469 }
470
471 let num_callers = join_all(handlers)
472 .await
473 .into_iter()
474 .map(|r| r.unwrap())
475 .filter(|(val, is_caller)| {
476 assert_eq!(*val, RES);
477 *is_caller
478 })
479 .count();
480 assert_eq!(1, num_callers);
481 assert_eq!(1, times_called.load(Ordering::SeqCst));
482 };
483 ctx.runtime.bridge_sync(tasks).unwrap();
484 }
485
486 #[tokio::test]
487 #[cfg_attr(feature = "smoke-test", ignore)]
488 async fn test_multiple_threads() {
489 let times_called = Arc::new(AtomicU32::new(0));
490 let g: Arc<Group<usize, ()>> = Arc::new(Group::new());
491 let mut handlers: Vec<JoinHandle<(usize, bool)>> = Vec::new();
492 for _ in 0..10 {
493 let g = g.clone();
494 let counter = times_called.clone();
495 handlers.push(Handle::current().spawn(async move {
496 let tup = g.work("key", expensive_fn(counter, RES)).await;
497 let res = tup.0;
498 let fn_response = res.unwrap();
499 (fn_response, tup.1)
500 }));
501 }
502
503 let num_callers = join_all(handlers)
504 .await
505 .into_iter()
506 .map(|r| r.unwrap())
507 .filter(|(val, is_caller)| {
508 assert_eq!(*val, RES);
509 *is_caller
510 })
511 .count();
512 assert_eq!(1, num_callers);
513 assert_eq!(1, times_called.load(Ordering::SeqCst));
514 }
515
516 #[tokio::test]
517 #[cfg_attr(feature = "smoke-test", ignore)]
518 async fn test_error() {
519 let times_called = Arc::new(AtomicU32::new(0));
520
521 async fn expensive_error_fn(x: Arc<AtomicU32>) -> Result<usize, &'static str> {
522 tokio::time::sleep(Duration::new(1, 500)).await;
523 x.fetch_add(1, Ordering::SeqCst);
524 Err("Error")
525 }
526
527 let g: Arc<Group<usize, &'static str>> = Arc::new(Group::new());
528 let mut handlers = Vec::new();
529
530 for _ in 0..10 {
531 let g = g.clone();
532 let counter = times_called.clone();
533 handlers.push(Handle::current().spawn(async move {
534 let tup = g.work("key", expensive_error_fn(counter)).await;
535 let res = tup.0;
536 assert!(res.is_err());
537 tup.1
538 }));
539 }
540
541 let num_callers = join_all(handlers).await.into_iter().map(|r| r.unwrap()).filter(|b| *b).count();
542 assert_eq!(1, num_callers);
543 assert_eq!(1, times_called.load(Ordering::SeqCst));
544 }
545
546 #[tokio::test]
547 #[cfg_attr(feature = "smoke-test", ignore)]
548 async fn test_multiple_keys() {
549 let times_called_x = Arc::new(AtomicU32::new(0));
550 let times_called_y = Arc::new(AtomicU32::new(0));
551
552 let mut handlers1 = call_success_n_times(5, "key", times_called_x.clone(), 7);
553 let mut handlers2 = call_success_n_times(5, "key2", times_called_y.clone(), 13);
554 handlers1.append(&mut handlers2);
555 let count_x = AtomicU32::new(0);
556 let count_y = AtomicU32::new(0);
557
558 let num_callers = join_all(handlers1)
559 .await
560 .into_iter()
561 .map(|r| r.unwrap())
562 .filter(|(val, is_caller)| {
563 if *val == 7 {
564 count_x.fetch_add(1, Ordering::SeqCst);
565 } else if *val == 13 {
566 count_y.fetch_add(1, Ordering::SeqCst);
567 } else {
568 panic!("joined a number not expected: {}", *val);
569 }
570 *is_caller
571 })
572 .count();
573 assert_eq!(2, num_callers);
574 assert_eq!(5, count_x.load(Ordering::SeqCst));
575 assert_eq!(5, count_y.load(Ordering::SeqCst));
576 assert_eq!(1, times_called_x.load(Ordering::SeqCst));
577 assert_eq!(1, times_called_y.load(Ordering::SeqCst));
578 }
579
580 fn call_success_n_times(times: usize, key: &str, c: Arc<AtomicU32>, val: usize) -> Vec<JoinHandle<(usize, bool)>> {
582 let g: Arc<Group<usize, ()>> = Arc::new(Group::new());
583 let mut handlers = Vec::new();
584 for _ in 0..times {
585 let g = g.clone();
586 let counter = c.clone();
587 let k = key.to_owned();
588 handlers.push(Handle::current().spawn(async move {
589 let tup = g.work(k.as_str(), expensive_fn(counter, val)).await;
590 let res = tup.0;
591 let fn_response = res.unwrap();
592 (fn_response, tup.1)
593 }));
594 }
595 handlers
596 }
597
598 #[tokio::test]
599 async fn test_owner_task_future_impl() {
600 const VAL: i32 = 10;
601 let future = async { Ok::<i32, String>(VAL) };
602 let call = Arc::new(Call::new());
603 let owner_task = OwnerTask::new(future, call.clone());
604 let result = tokio::spawn(owner_task).await;
605 assert_eq!(VAL, result.unwrap().unwrap());
606 assert_eq!(VAL, call.get().unwrap());
607 }
608
609 #[tokio::test]
610 async fn test_owner_task_future_notify() {
611 const VAL: i32 = 10;
612 let future = async { Ok::<i32, String>(VAL) };
613 let call = Arc::new(Call::new());
614 let call_waiter = call.clone();
615 let waiter_task = async move {
616 let waiter_future = call_waiter.get_future();
617 assert_eq!(VAL, waiter_future.await.unwrap());
618 };
619 let waiter_handle = tokio::spawn(waiter_task);
620 let owner_task = OwnerTask::new(future, call.clone());
621 let result = tokio::spawn(owner_task).await;
622 timeout(WAITER_TIMEOUT, waiter_handle).await.unwrap().unwrap();
623 assert_eq!(VAL, result.unwrap().unwrap());
624 assert_eq!(VAL, call.get().unwrap());
625 assert_eq!(1, call.num_waiters.load(Ordering::SeqCst)) }
627
628 #[tokio::test]
629 async fn test_owner_task_future_panic() {
630 let future = async { panic!("failing task") };
631 let call = Arc::new(Call::<i32, String>::new());
632 let call_waiter = call.clone();
633 let waiter_task = async move {
634 let waiter_future = call_waiter.get_future();
635 let result = waiter_future.await;
636 assert!(matches!(result, Err(SingleflightError::OwnerPanicked)));
637 };
638 let waiter_handle = tokio::spawn(waiter_task);
639
640 let owner_task = OwnerTask::new(future, call.clone());
641 let result = tokio::spawn(owner_task).await;
642 assert!(result.is_err());
643 timeout(WAITER_TIMEOUT, waiter_handle).await.unwrap().unwrap();
644 assert_eq!(1, call.num_waiters.load(Ordering::SeqCst)) }
646}
647
648#[cfg(test)]
649mod test_deadlock {
650 use std::collections::HashMap;
651 use std::sync::Arc;
652
653 use futures::StreamExt;
654 use futures::stream::iter;
655 use tests::WAITER_TIMEOUT;
656 use tokio::runtime::Handle;
657 use tokio::sync::mpsc::error::SendError;
658 use tokio::sync::mpsc::{Sender, channel};
659 use tokio::sync::{Mutex, Notify};
660 use tokio::time::timeout;
661
662 use super::{Group, tests};
663
664 #[tokio::test]
665 async fn test_deadlock() {
666 let group: Arc<Group<usize, ()>> = Arc::new(Group::new());
681 let (send1, mut recv1) = channel::<usize>(1);
683 let (send2, mut recv2) = channel::<usize>(1);
684 let vals1: Vec<usize> = vec![1, 2, 3, 4, SHARED_ITEM];
686 let vals2: Vec<usize> = vec![6, 7, SHARED_ITEM, 8, 9];
687
688 let waiters: Arc<Mutex<HashMap<usize, Arc<Notify>>>> = Arc::new(Mutex::new(HashMap::new()));
693 {
694 let mut guard = waiters.lock().await;
695 guard.insert(vals2[1], Arc::new(Notify::new()));
696 guard.insert(SHARED_ITEM, Arc::new(Notify::new()));
697 }
698
699 let t1 = Handle::current().spawn(run_task(1, group.clone(), waiters.clone(), send1, false, vals1.clone()));
701 let t2 = Handle::current().spawn(run_task(2, group.clone(), waiters.clone(), send2, true, vals2.clone()));
702
703 for (i, expected_val) in vals1.into_iter().enumerate() {
705 if i == 3 {
706 println!("[main] notifying val: {}", vals2[1]);
708 let guard = waiters.lock().await;
709 guard.get(&vals2[1]).unwrap().notify_one();
710 println!("[main] notified val: {}", vals2[1])
711 }
712 if i == 4 {
713 println!("[main] notifying val: {}", SHARED_ITEM);
715 let guard = waiters.lock().await;
716 guard.get(&SHARED_ITEM).unwrap().notify_one();
717 println!("[main] notified val: {}", SHARED_ITEM);
718 }
719 println!("[main] getting t1[{}]", i);
720 let res = timeout(WAITER_TIMEOUT, recv1.recv())
721 .await
722 .map_err(|_| format!("Timed out on task1 waiting for val: {}. Likely deadlock.", expected_val));
723 let val = res.unwrap().unwrap();
724 println!("[main] got val: {} from t1[{}]", val, i);
725 assert_eq!(expected_val, val);
726 }
727
728 for expected_val in vals2 {
730 let res = timeout(WAITER_TIMEOUT, recv2.recv())
731 .await
732 .map_err(|_| format!("Timed out on task2 waiting for val: {}. Likely deadlock.", expected_val));
733 let val = res.unwrap().unwrap();
734 assert_eq!(expected_val, val);
735 }
736
737 t1.await.unwrap().unwrap();
739 t2.await.unwrap().unwrap();
740 }
741
742 const SHARED_ITEM: usize = 5;
743
744 async fn run_task(
745 id: i32,
746 g: Arc<Group<usize, ()>>,
747 waiters: Arc<Mutex<HashMap<usize, Arc<Notify>>>>,
748 send_chan: Sender<usize>,
749 should_own: bool,
750 vals: Vec<usize>,
751 ) -> Result<(), SendError<usize>> {
752 let mut strm = iter(vals.into_iter().map(|v| {
754 let g = g.clone();
755 let waiters = waiters.clone();
756 async move {
758 println!("[task: {}] running task for: {}", id, v);
759 let (res, is_owner) = g.work(format!("{}", v).as_str(), run_fut(v, waiters)).await;
760 println!("[task: {}] completed task for: {}, is_owner: {}", id, v, is_owner);
761 if v == SHARED_ITEM {
762 assert_eq!(should_own, is_owner);
763 }
764 res.unwrap()
765 }
766 }))
767 .buffered(3);
768
769 while let Some(val) = strm.next().await {
770 println!("[task: {}] sending next element: {}", id, val);
771 send_chan.send(val).await?;
772 println!("[task: {}] sent next element: {}", id, val);
773 }
774 println!("[task: {}] done executing", id);
775 Ok(())
776 }
777
778 async fn run_fut(v: usize, waiters: Arc<Mutex<HashMap<usize, Arc<Notify>>>>) -> Result<usize, ()> {
779 let waiter = {
780 let x = waiters.lock().await;
781 x.get(&v).cloned()
782 };
783 if let Some(waiter) = waiter {
785 println!("val: {}, waiting for signal", v);
786 waiter.notified().await;
787 println!("val: {}, woke up from signal", v);
788 }
789 Ok(v)
790 }
791}
792
793#[cfg(test)]
794mod test_futures_unordered {
795 use std::future::Future;
796 use std::pin::Pin;
797 use std::sync::Arc;
798 use std::time::Duration;
799
800 use futures_util::TryStreamExt;
801 use futures_util::stream::FuturesUnordered;
802 use tokio::sync::mpsc;
803 use tokio::time::sleep;
804
805 use super::super::errors::SingleflightError;
806 use super::Group;
807
808 type FutType = Pin<Box<dyn Future<Output = Result<(i32, bool), SingleflightError<String>>> + Send>>;
809
810 #[tokio::test]
811 async fn test_dropped_owner() {
812 let group = Arc::new(Group::new());
829
830 let (ready_tx, mut ready_rx) = mpsc::channel(1);
832 let (done_tx, mut done_rx) = mpsc::channel(1);
835
836 let fut_owner = get_fut(group.clone(), "key1", async move {
838 ready_tx.send(true).await.unwrap();
839 sleep(Duration::from_millis(100)).await;
840 done_tx.send(true).await.unwrap();
841 Ok(1)
842 });
843 let fut_waiter =
845 get_fut(group.clone(), "key1", async { Err("Test BUG: waiter should not be called".to_string()) });
846
847 let fut_err = get_fut(group.clone(), "key2", async { Err("failed".to_string()) });
849
850 let handle = tokio::spawn(async move {
852 assert!(ready_rx.recv().await.unwrap());
853 let (i, is_owner) = fut_waiter.await.unwrap();
854 assert!(!is_owner);
855 assert_eq!(i, 1);
856 });
857
858 let futures: Result<Vec<(i32, bool)>, SingleflightError<String>> =
865 FuturesUnordered::from_iter(vec![fut_owner, fut_err]).try_collect().await;
866
867 assert!(futures.is_err());
868 assert!(!group.call_map.lock().unwrap().contains_key("key1"));
870 assert!(done_rx.recv().await.unwrap());
871 handle.await.unwrap();
872
873 let fut_after = get_fut(group, "key1", async { Ok(5) });
876 let (i, is_owner) = fut_after.await.unwrap();
877 assert!(is_owner);
878 assert_eq!(i, 5);
879 }
880
881 fn get_fut(
882 g: Arc<Group<i32, String>>,
883 key: &str,
884 f: impl Future<Output = Result<i32, String>> + Send + 'static,
885 ) -> FutType {
886 let key = key.to_string();
887 Box::pin(async move {
888 let (res, is_owner) = g.work(&key, f).await;
889 let i = res?;
890 Ok((i, is_owner))
891 })
892 }
893}