@@ -2,6 +2,8 @@ use crate::prelude::*;
22
33use anyhow:: { Context , Ok } ;
44use futures:: future:: try_join_all;
5+ use log:: warn;
6+ use tokio:: time:: Duration ;
57
68use crate :: base:: value:: EstimatedByteSize ;
79use crate :: base:: { schema, value} ;
@@ -11,6 +13,9 @@ use utils::immutable::RefList;
1113
1214use super :: memoization:: { EvaluationMemory , EvaluationMemoryOptions , evaluate_with_cell} ;
1315
16+ const TIMEOUT_THRESHOLD : Duration = Duration :: from_secs ( 1800 ) ;
17+ const WARNING_THRESHOLD : Duration = Duration :: from_secs ( 30 ) ;
18+
1419#[ derive( Debug ) ]
1520pub struct ScopeValueBuilder {
1621 // TODO: Share the same lock for values produced in the same execution scope, for stricter atomicity.
@@ -356,6 +361,43 @@ async fn evaluate_child_op_scope(
356361 } )
357362}
358363
364+ async fn evaluate_with_timeout_and_warning < F , T > (
365+ eval_future : F ,
366+ timeout_duration : Duration ,
367+ warn_duration : Duration ,
368+ op_kind : String ,
369+ op_name : String ,
370+ ) -> Result < T >
371+ where
372+ F : std:: future:: Future < Output = Result < T > > ,
373+ {
374+ let mut eval_future = Box :: pin ( eval_future) ;
375+ let mut warned = false ;
376+ let timeout_future = tokio:: time:: sleep ( timeout_duration) ;
377+ tokio:: pin!( timeout_future) ;
378+
379+ loop {
380+ tokio:: select! {
381+ res = & mut eval_future => {
382+ return res;
383+ }
384+ _ = & mut timeout_future => {
385+ return Err ( anyhow!(
386+ "Function '{}' ({}) timed out after {} seconds" ,
387+ op_kind, op_name, timeout_duration. as_secs( )
388+ ) ) ;
389+ }
390+ _ = tokio:: time:: sleep( warn_duration) , if !warned => {
391+ warn!(
392+ "Function '{}' ({}) is taking longer than {}s" ,
393+ op_kind, op_name, WARNING_THRESHOLD . as_secs( )
394+ ) ;
395+ warned = true ;
396+ }
397+ }
398+ }
399+ }
400+
359401async fn evaluate_op_scope (
360402 op_scope : & AnalyzedOpScope ,
361403 scoped_entries : RefList < ' _ , & ScopeEntry < ' _ > > ,
@@ -378,6 +420,12 @@ async fn evaluate_op_scope(
378420 input_values. push ( value?) ;
379421 }
380422
423+ let timeout_duration = op. function_exec_info . timeout . unwrap_or ( TIMEOUT_THRESHOLD ) ;
424+ let warn_duration = WARNING_THRESHOLD ;
425+
426+ let op_name_for_warning = op. name . clone ( ) ;
427+ let op_kind_for_warning = op. op_kind . clone ( ) ;
428+
381429 let result = if op. function_exec_info . enable_cache {
382430 let output_value_cell = memory. get_cache_entry (
383431 || {
@@ -391,18 +439,33 @@ async fn evaluate_op_scope(
391439 & op. function_exec_info . output_type ,
392440 /*ttl=*/ None ,
393441 ) ?;
394- evaluate_with_cell ( output_value_cell. as_ref ( ) , move || {
442+
443+ let eval_future = evaluate_with_cell ( output_value_cell. as_ref ( ) , move || {
395444 op. executor . evaluate ( input_values)
396- } )
397- . await
398- . and_then ( |v| head_scope. define_field ( & op. output , & v) )
445+ } ) ;
446+ let v = evaluate_with_timeout_and_warning (
447+ eval_future,
448+ timeout_duration,
449+ warn_duration,
450+ op_kind_for_warning,
451+ op_name_for_warning,
452+ )
453+ . await ?;
454+
455+ head_scope. define_field ( & op. output , & v)
399456 } else {
400- op. executor
401- . evaluate ( input_values)
402- . await
403- . and_then ( |v| head_scope. define_field ( & op. output , & v) )
404- }
405- . with_context ( || format ! ( "Evaluating Transform op `{}`" , op. name, ) ) ;
457+ let eval_future = op. executor . evaluate ( input_values) ;
458+ let v = evaluate_with_timeout_and_warning (
459+ eval_future,
460+ timeout_duration,
461+ warn_duration,
462+ op_kind_for_warning,
463+ op_name_for_warning,
464+ )
465+ . await ?;
466+
467+ head_scope. define_field ( & op. output , & v)
468+ } ;
406469
407470 // Track transform operation completion
408471 if let Some ( ref op_stats) = operation_in_process_stats {
@@ -411,7 +474,7 @@ async fn evaluate_op_scope(
411474 op_stats. finish_processing ( & transform_key, 1 ) ;
412475 }
413476
414- result?
477+ result. with_context ( || format ! ( "Evaluating Transform op `{}`" , op . name ) ) ?
415478 }
416479
417480 AnalyzedReactiveOp :: ForEach ( op) => {
0 commit comments