@@ -44,6 +44,7 @@ pub struct JobConfig {
4444 pub concurrency : Concurrency ,
4545 pub retry : Option < RetryConfigRaw > ,
4646 pub working_dir : Option < String > ,
47+ pub jitter : Option < String > ,
4748}
4849
4950#[ derive( Debug , Deserialize ) ]
@@ -52,6 +53,7 @@ pub struct RetryConfigRaw {
5253 pub max : u32 ,
5354 #[ serde( default = "default_retry_delay" ) ]
5455 pub delay : String ,
56+ pub jitter : Option < String > ,
5557}
5658
5759fn default_retry_delay ( ) -> String {
@@ -77,12 +79,14 @@ pub struct Job {
7779 pub concurrency : Concurrency ,
7880 pub retry : Option < RetryConfig > ,
7981 pub working_dir : Option < String > ,
82+ pub jitter : Option < Duration > ,
8083}
8184
8285#[ derive( Debug , Clone ) ]
8386pub struct RetryConfig {
8487 pub max : u32 ,
8588 pub delay : Duration ,
89+ pub jitter : Option < Duration > ,
8690}
8791
8892pub fn parse_config ( content : & str ) -> Result < ( RunnerConfig , Vec < Job > ) > {
@@ -116,10 +120,20 @@ pub fn parse_config(content: &str) -> Result<(RunnerConfig, Vec<Job>)> {
116120 . map ( |r| {
117121 let delay = parse_duration ( & r. delay )
118122 . map_err ( |e| anyhow ! ( "Invalid retry delay '{}' in job '{}': {}" , r. delay, id, e) ) ?;
119- Ok :: < _ , anyhow:: Error > ( RetryConfig { max : r. max , delay } )
123+ let jitter = r. jitter
124+ . map ( |j| parse_duration ( & j)
125+ . map_err ( |e| anyhow ! ( "Invalid retry jitter '{}' in job '{}': {}" , j, id, e) ) )
126+ . transpose ( ) ?;
127+ Ok :: < _ , anyhow:: Error > ( RetryConfig { max : r. max , delay, jitter } )
120128 } )
121129 . transpose ( ) ?;
122130
131+ let jitter = job
132+ . jitter
133+ . map ( |j| parse_duration ( & j)
134+ . map_err ( |e| anyhow ! ( "Invalid jitter '{}' in job '{}': {}" , j, id, e) ) )
135+ . transpose ( ) ?;
136+
123137 Ok ( Job {
124138 id,
125139 name,
@@ -129,6 +143,7 @@ pub fn parse_config(content: &str) -> Result<(RunnerConfig, Vec<Job>)> {
129143 concurrency : job. concurrency ,
130144 retry,
131145 working_dir : job. working_dir ,
146+ jitter,
132147 } )
133148 } )
134149 . collect :: < Result < Vec < _ > > > ( ) ?;
@@ -138,7 +153,9 @@ pub fn parse_config(content: &str) -> Result<(RunnerConfig, Vec<Job>)> {
138153
139154fn parse_duration ( s : & str ) -> Result < Duration > {
140155 let s = s. trim ( ) ;
141- if let Some ( secs) = s. strip_suffix ( 's' ) {
156+ if let Some ( millis) = s. strip_suffix ( "ms" ) {
157+ Ok ( Duration :: from_millis ( millis. parse ( ) ?) )
158+ } else if let Some ( secs) = s. strip_suffix ( 's' ) {
142159 Ok ( Duration :: from_secs ( secs. parse ( ) ?) )
143160 } else if let Some ( mins) = s. strip_suffix ( 'm' ) {
144161 Ok ( Duration :: from_secs ( mins. parse :: < u64 > ( ) ? * 60 ) )
@@ -370,4 +387,61 @@ jobs:
370387"# ;
371388 assert ! ( parse_config( yaml) . is_err( ) ) ;
372389 }
390+
391+ #[ test]
392+ fn parse_jitter ( ) {
393+ let yaml = r#"
394+ jobs:
395+ with_jitter:
396+ schedule:
397+ cron: "* * * * *"
398+ run: echo test
399+ jitter: 30s
400+ no_jitter:
401+ schedule:
402+ cron: "* * * * *"
403+ run: echo test
404+ "# ;
405+ let ( _, jobs) = parse_config ( yaml) . unwrap ( ) ;
406+ let find = |id : & str | jobs. iter ( ) . find ( |j| j. id == id) . unwrap ( ) ;
407+
408+ assert_eq ! ( find( "with_jitter" ) . jitter, Some ( Duration :: from_secs( 30 ) ) ) ;
409+ assert ! ( find( "no_jitter" ) . jitter. is_none( ) ) ;
410+ }
411+
412+ #[ test]
413+ fn parse_retry_jitter ( ) {
414+ let yaml = r#"
415+ jobs:
416+ with_retry_jitter:
417+ schedule:
418+ cron: "* * * * *"
419+ run: echo test
420+ retry:
421+ max: 3
422+ delay: 1s
423+ jitter: 500ms
424+ retry_no_jitter:
425+ schedule:
426+ cron: "* * * * *"
427+ run: echo test
428+ retry:
429+ max: 2
430+ delay: 1s
431+ "# ;
432+ let ( _, jobs) = parse_config ( yaml) . unwrap ( ) ;
433+ let find = |id : & str | jobs. iter ( ) . find ( |j| j. id == id) . unwrap ( ) ;
434+
435+ let retry1 = find ( "with_retry_jitter" ) . retry . as_ref ( ) . unwrap ( ) ;
436+ assert_eq ! ( retry1. jitter, Some ( Duration :: from_millis( 500 ) ) ) ;
437+
438+ let retry2 = find ( "retry_no_jitter" ) . retry . as_ref ( ) . unwrap ( ) ;
439+ assert ! ( retry2. jitter. is_none( ) ) ;
440+ }
441+
442+ #[ test]
443+ fn parse_duration_milliseconds ( ) {
444+ assert_eq ! ( parse_duration( "500ms" ) . unwrap( ) , Duration :: from_millis( 500 ) ) ;
445+ assert_eq ! ( parse_duration( "1000ms" ) . unwrap( ) , Duration :: from_millis( 1000 ) ) ;
446+ }
373447}
0 commit comments