Contact Us 1-800-596-4880

Configuring Delayed, Periodic, and Synchronous Functions

Use the Clock injectable to build a timer to enable delayed, periodic, and synchronous functions. Each policy supports only one timer.

Each timer provides the following two functions:

pub async fn next_tick(&self) -> bool;
pub async fn sleep(&self, interval: Duration) -> bool;
  • next_tick: Sleeps for the minimum amount of time the timer is capable of distinguishing.

  • sleep: Sleeps for an amount of ticks where the elapsed time is greater or equal than the provided Duration. For example, if you configure the period of the clock to 10 seconds, calling timer.sleep(Duration::from_secs(1)) sleeps for 10 seconds.

Both functions return true when the specified time has passed or false if the policy is unapplied or edited during execution.

Configure the Timer

To inject a timer in your policy, insert a Clock into your policy configuration function and define a timer with a time period:

#[entrypoint]
async fn configure(
   launcher: Launcher,
   Configuration(bytes): Configuration,
   clock: Clock, // Inject the clock to be able to launch async tasks.
   client: HttpClient,
) -> Result<()> {


   // set the period between ticks of the timer.
   let timer = clock.period(Duration::from_secs(10));

After you inject the timer, share a its reference with the on_request and on_response functions to begin creating delayed functions:

    launcher
        .launch(on_request(|rs| request_filter(rs, &timer, &config)))
        .await?;

You can now await timer functions in on_request and on_response functions, for example:

    let slept = timer.sleep(duration).await;
    let tick = timer.next_tick().await;
To view an example policy project that launches delayed functions, see Spike Policy Example.

Launch Asynchronous Tasks

Use the timer to execute tasks independent of the request flow. To execute asynchronous tasks:

  1. Define your task inside an async function.

  2. In the policy configuration function, call your function but don’t await the returned future.

  3. In the policy configuration function, call the launch function with the on_request and on_response filter functions but don’t await the returned future.

  4. Use the join! macro to await both futures of your task function and launch function at the same time.

    To use the join macro, add the ‘futures’ crate as dependency in your cargo.toml file.

Each worker executes a copy of the task. Configure a loop inside the function to execute periodic tasks. For example, the following code example awaits both functions:

let task = my_async_task(&timer, &config);
let launched = launcher.launch(filter);
let joined = join!(launched, task);

Adding a loop to the my_async_task function creates periodic tasks, for example:

async fn my_async_task(timer: &Timer, config: &Config) {
    // While the policy is still running.
    // Wait for the next cycle.
    while timer.next_tick().await {
        // Execute periodic task
    }
}

To launch multiple asynchronous tasks, send a reference of the timer to your async functions and join them with the launch task, for example:

let task1 = my_async_task1(&timer, &config);
let task2 = my_async_task2(&timer, &config);
let launched = launcher.launch(filter);
let joined = join!(launched, task1, task2);
To view an example policy project that launches asynchronous periodic tasks, see Metrics Policy Example.

Sync Asynchronous Tasks between Workers

Use the PDK LockBuilder to sync workers. The lock ensures that only one worker is completing a task in a time period. Each policy supports multiple locks.

Locks are useful when a policy consumes an HTTP service that has usage limits. A single worker can fetch data and then share the data with other workers to reduce service requests.

To configure a lock:

  1. Inject the LockBuilder into your policy configuration function:

    #[entrypoint]
    async fn configure(
        launcher: Launcher,
        Configuration(bytes): Configuration,
        clock: Clock,        // Inject the clock to be able to launch async tasks.
        lock: LockBuilder,   // Inject the lock to be able to synchronize the workers.
    ) -> Result<()> {
  2. Configure the lock and share a reference of it with your asynchronous function:

        let lock = lock
            .new(ID.to_string())
            .expiration(Duration::from_secs(20))
            .build();
        let task = my_async_task(&timer, &lock);

    If the asynchronous task executes additional async function calls inside the task, configure the expiration of the lock to a duration longer than the total time needed to complete the nested async function calls.

  3. Inside the asynchronous function, acquire the lock before executing the desired task. If no other worker has the lock, the current worker acquires the lock. Otherwise, you must omit the execution of the task and wait for the next task loop, for example:

    async fn my_async_task(
        timer: &Timer,
        lock: &TryLock,
    ) {
       while timer.next_tick().await {
         if let Some(acquired) = lock.try_lock() {
           // execute task.
         }
       }
    }

    After obtaining the lock, if you execute code that requires you await a result, you must call the refresh_lock function to ensure the lock hasn’t expired. If the lock has expired, the worker is considered unresponsive. Abort the execution of your task because another worker is running the same task execution.

To view an example policy project that synchronizes asynchronous periodic tasks and shares information between workers, see Block Policy Example.