diff options
author | Igor Zinkovsky <igor@deno.com> | 2023-11-01 11:57:55 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-01 11:57:55 -0700 |
commit | 01d3e0f317ca180bbf0ac8a17c6651869110e02f (patch) | |
tree | b8e3bb91e87cf396bfa782f6377158f8a170b32b /ext/cron/lib.rs | |
parent | 82643857cc77a80f9a819584035ec147a6114553 (diff) |
feat(cron) implement Deno.cron() (#21019)
This PR adds unstable `Deno.cron` API to trigger execution of cron jobs.
* State: All cron state is in memory. Cron jobs are scheduled according
to the cron schedule expression and the current time. No state is
persisted to disk.
* Time zone: Cron expressions specify time in UTC.
* Overlapping executions: not permitted. If the next scheduled execution
time occurs while the same cron job is still executing, the scheduled
execution is skipped.
* Retries: failed jobs are automatically retried until they succeed or
until retry threshold is reached. Retry policy can be optionally
specified using `options.backoffSchedule`.
Diffstat (limited to 'ext/cron/lib.rs')
-rw-r--r-- | ext/cron/lib.rs | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/ext/cron/lib.rs b/ext/cron/lib.rs new file mode 100644 index 000000000..c49659703 --- /dev/null +++ b/ext/cron/lib.rs @@ -0,0 +1,128 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +mod interface; +pub mod local; +mod time; + +use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; + +use deno_core::error::get_custom_error_class; +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::op2; +use deno_core::OpState; +use deno_core::Resource; +use deno_core::ResourceId; + +pub use crate::interface::*; + +pub const UNSTABLE_FEATURE_NAME: &str = "cron"; + +deno_core::extension!(deno_cron, + deps = [ deno_console ], + parameters = [ C: CronHandler ], + ops = [ + op_cron_create<C>, + op_cron_next<C>, + ], + esm = [ "01_cron.ts" ], + options = { + cron_handler: C, + }, + state = |state, options| { + state.put(Rc::new(options.cron_handler)); + } +); + +struct CronResource<EH: CronHandle + 'static> { + handle: Rc<EH>, +} + +impl<EH: CronHandle + 'static> Resource for CronResource<EH> { + fn name(&self) -> Cow<str> { + "cron".into() + } + + fn close(self: Rc<Self>) { + self.handle.close(); + } +} + +#[op2] +#[smi] +fn op_cron_create<C>( + state: Rc<RefCell<OpState>>, + #[string] name: String, + #[string] cron_schedule: String, + #[serde] backoff_schedule: Option<Vec<u32>>, +) -> Result<ResourceId, AnyError> +where + C: CronHandler + 'static, +{ + let cron_handler = { + let state = state.borrow(); + // TODO(bartlomieju): replace with `state.feature_checker.check_or_exit` + // once we phase out `check_or_exit_with_legacy_fallback` + state + .feature_checker + .check_or_exit_with_legacy_fallback(UNSTABLE_FEATURE_NAME, "Deno.cron"); + state.borrow::<Rc<C>>().clone() + }; + + validate_cron_name(&name)?; + + let handle = cron_handler.create(CronSpec { + name, + cron_schedule, + backoff_schedule, + })?; + + let handle_rid = { + let mut state = state.borrow_mut(); + state.resource_table.add(CronResource { + handle: Rc::new(handle), + }) + }; + Ok(handle_rid) +} + +#[op2(async)] +async fn op_cron_next<C>( + state: Rc<RefCell<OpState>>, + #[smi] rid: ResourceId, + prev_success: bool, +) -> Result<bool, AnyError> +where + C: CronHandler + 'static, +{ + let cron_handler = { + let state = state.borrow(); + let resource = match state.resource_table.get::<CronResource<C::EH>>(rid) { + Ok(resource) => resource, + Err(err) => { + if get_custom_error_class(&err) == Some("BadResource") { + return Ok(false); + } else { + return Err(err); + } + } + }; + resource.handle.clone() + }; + + cron_handler.next(prev_success).await +} + +fn validate_cron_name(name: &str) -> Result<(), AnyError> { + if name.len() > 64 { + return Err(type_error("Cron name is too long")); + } + if !name.chars().all(|c| { + c.is_ascii_whitespace() || c.is_ascii_alphanumeric() || c == '_' || c == '-' + }) { + return Err(type_error("Invalid cron name")); + } + Ok(()) +} |