From 6c22adc3f8de6335ce4d7a1751b12a4d38b7bc03 Mon Sep 17 00:00:00 2001 From: Maxime Van Hees Date: Thu, 3 Oct 2024 16:59:50 +0200 Subject: [PATCH] cron jobs in zinit --- docs/readme.md | 4 +- src/zinit/mod.rs | 132 ++++++++++++----------------------------------- 2 files changed, 35 insertions(+), 101 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 5896849..4e4c5a2 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -22,6 +22,7 @@ When running zinit in a container, supply the `--container` argument to the init exec: "command line to start service" test: "command line to test service is running" # optional oneshot: true or false (false by default) +cron: amount of seconds # creates a cronjob, oneshot MUST be set to true after: # list of services that we depend on (optional) - service1_name - service2_name @@ -32,7 +33,8 @@ env: KEY: VALUE ``` -- `oneshot` service is not going to re-spawn when it exits. +- `oneshot` service is not going to re-spawn when it exits, except when `cron` has a value > 0. +- `cron` is used to create a cronjob. This only works when `oneshot` is set to `true`. - if a service depends on a `oneshot` services, it will not get started, unless the oneshot service exits with success. - if a service depends on another service (that is not `oneshot`), it will not get started, unless the service is marked as `running` - a service with no test command is marked running if it successfully executed, regardless if it exits immediately after or not, hence a test command is useful. diff --git a/src/zinit/mod.rs b/src/zinit/mod.rs index b6df13b..75db7fa 100644 --- a/src/zinit/mod.rs +++ b/src/zinit/mod.rs @@ -546,13 +546,7 @@ impl ZInit { let name = name.clone(); let mut service = input.write().await; - if service.target == Target::Down { - debug!("service '{}' target is down", name); - return; - } - - if service.scheduled { - debug!("service '{}' already scheduled", name); + if service.target == Target::Down || service.scheduled { return; } @@ -567,9 +561,7 @@ impl ZInit { break; } let config = service.service.clone(); - // Release the lock; enables other services to aquire it and schedule - // themselves - drop(service); + drop(service); // Release the lock // Wait for dependencies while !self.can_schedule(&config).await { @@ -585,10 +577,7 @@ impl ZInit { }; let mut service = input.write().await; - // We check again in case target has changed. Since we had to release the lock - // earlier to not block locking on this service (for example if a stop was called) - // while the service was waiting for dependencies. - // The lock is kept until the spawning and the update of the pid. + if service.target == Target::Down { // Service target is down; exit the loop break; @@ -615,99 +604,42 @@ impl ZInit { } }; - if config.one_shot { - service.state.set(State::Running); - } - - // We don't lock the here here because this can take forever - // to finish. So, we allow other operation on the service (for example) - // status and stop operations. + // Since only oneshot services can have cron, we are in oneshot mode + service.state.set(State::Running); drop(service); // Release the lock - let mut handler = None; - if !config.one_shot { - let m = self.clone(); - handler = Some(tokio::spawn(m.test(name.clone(), config.clone()))); - } + // Wait for the child process to finish + let wait_result = child.wait().await; - // Prepare futures for selection - let cron_future = config - .cron - .map(|cron_duration| tokio::time::sleep(Duration::from_secs(cron_duration))); - - let child_pid = child.pid; - let wait_future = child.wait(); - - // Use select to wait on the child process or the cron future - tokio::select! { - result = wait_future => { - // The child process has exited - if let Some(handler) = handler { - handler.abort(); - } - - let mut service = input.write().await; - service.pid = Pid::from_raw(0); - - match result { - Err(err) => { - error!("failed to read service '{}' status: {}", name, err); - service.state.set(State::Unknown); - } - Ok(status) => service.state.set(match status.success() { - true => State::Success, - false => State::Error(status), - }), - } - drop(service); - - if config.one_shot { - // For oneshot services, we don't need to restart - self.notify.notify_waiters(); - break; - } - } - _ = async { - if let Some(cron_fut) = cron_future { - cron_fut.await; - } else { - futures::future::pending::<()>().await; - } - } => { - // Cron duration elapsed - if *self.shutdown.read().await { - // If shutting down, exit the loop - break; - } - - let service = input.read().await; - if service.target == Target::Down { - break; - } - let signal_name = service.service.signal.stop.to_uppercase(); - drop(service); - - let signal = match signal::Signal::from_str(&signal_name) { - Ok(signal) => signal, - Err(err) => { - error!("unknown stop signal configured in service '{}': {}", name, err); - break; - } - }; - - // Send stop signal to the process group - let _ = self.pm.signal(child_pid, signal); - - // Optionally wait for the service to stop - time::sleep(Duration::from_secs(1)).await; + let mut service = input.write().await; + service.pid = Pid::from_raw(0); + + match wait_result { + Err(err) => { + error!("failed to read service '{}' status: {}", name, err); + service.state.set(State::Unknown); } + Ok(status) => service.state.set(match status.success() { + true => State::Success, + false => State::Error(status), + }), } + drop(service); // Release the lock - // Allow some time before potentially restarting the service - time::sleep(Duration::from_secs(1)).await; + // Check if we should schedule the service again based on cron + if let Some(cron_duration) = config.cron { + if *self.shutdown.read().await { + // If shutting down, exit the loop + break; + } + // Wait for the specified cron duration + tokio::time::sleep(std::time::Duration::from_secs(cron_duration)).await; + // Loop will restart and the oneshot service will be executed again + } else { + // No cron specified, exit the loop after the oneshot execution + break; + } } - - // The loop will repeat unless we break out due to shutdown or service being down } let mut service = input.write().await;