7.2.261-stable

Tasks

Tasks

GreyCat comes with a concept of asynchronous tasks built-in. The mental model for the tasks is close to what other languages have:

  • Java’s Future
  • JavaScript’s Promise

They essentially are a handle to a potentially not-yet-done computation. Allowing you to poll for readiness, or cancel it mid-way. Tasks are well-suited for long calculations using the graph or involving network requests.

From a language perspective, there are no differences between defining a task or a function.

Executing a Task

To execute a task remotely, add task as a key to the Header when making a request, this will return an instance of a runtime::Task containing all the relevant meta information about the spawned task.

curl -H "task:''" -X POST -d '[]' http://localhost:8080/project::long_computation
# Task{user_id:1,task_id:1,mod:"tasks",type:null,fun:"long_computation",creation:'2024-06-24T10:23:47.965517+00:00',start:null,duration:null,status:TaskStatus::waiting}

Task Object

Field Type Description
user_id int The id of the user that spawned the task
task_id int The unique id of the spawned task
mod String? The Task Mode
fun String? The name of the function spawned
creation time Time when the task was spawned
start time? Time when the task started execution
duration duration? How long the task took
status TaskStatus The status of the task

To follow the task status call runtime::Task::info with the tasks user and task id

curl -X POST -d '[1,1]' http://localhost:8080/runtime::Task::info
# TaskInfo{user_id:1,task_id:1,mod:"tasks",type:null,fun:"long_computation",creation:'2024-06-24T10:15:18.814828+00:00',start:'2024-06-24T10:15:18.815025+00:00',duration:8ms278us,status:TaskStatus::ended,progress:1.0,remaining:null,sub_waiting:0,sub_tasks_all:0}

TaskInfo

In addition to the Task fields the TaskInfo also contains additional information

Field Type Description
progress float? The current task progress, can be updated by the user manually
remaining duration? Based on the progress will estimate how much time is remaining
sub_waiting int? How many subtasks are waiting to be executed
sub_tasks_all int? How many subtasks where spawned in total

Task Status

To track the state of your Task the response contains an TaskStatus enum that explains the different stages it can be in

Enum Value Description
empty Task is empty
waiting Task is waiting
running Task is running
await Task is awaiting
cancelled Task is cancelled
error Task encountered error
ended Task ended successfully
ended_with_errors Task ended with errors

Retrieve Task result

In the above example after fetching the task info, the TaskStatus is set to ended which means we can retrieve the result which is stored in an result.gcb file

The numbers after files and tasks are the respective user and task id that you would replace with the ones from the TaskInfo

curl -X GET 'http://localhost:8080/files/0/tasks/1/result.gcb?json'

Periodic tasks

How it works

Periodic tasks are like Tasks but as the name implies GreyCat will run them periodically once registered.

To register periodic tasks we need to use the runtime module:

fn my_task() {
  println("The current time is ${time::now()}");
}

fn main() {
  var my_task_every_day = PeriodicTask {
    user_id: 0,                 // the user associated with the execution
    arguments: null,            // the arguments to use for the execution
    every: 1_day,               // the periodicity as a duration
    function: project::my_task, // the function pointer of the task
    start: time::now(),         // the time of the first execution
  };

  // register the task in the scheduler
  PeriodicTask::set(Array<PeriodicTask>{my_task_every_day});
}

Note that as of today, the scheduler API is only providing PeriodicTask::set(...) which means you have to always define the whole list of periodic task when calling PeriodicTask::set(...) otherwise the previously registered ones will be unregistered.

Manipulating periodic tasks

Because the API is minimalist, manipulating the current list of registered PeriodicTasks is a bit tedious so here is an example of abstraction above what is currently available in the standard library:

type PeriodicTaskHelper {
  /// Schedules the given task
  static fn schedule(pTask: PeriodicTask) {
    var tasks = PeriodicTask::all();
    tasks.add(pTask);
    PeriodicTask::set(tasks);
  }

  /// Removes the first PeriodicTask from the scheduler and returns it
  ///
  /// If there is no tasks scheduled, `null` is returned.
  static fn shift(): PeriodicTask? {
    var tasks = PeriodicTask::all();
    var pTask: PeriodicTask?;
    if (tasks.size() > 0) {
      pTask = tasks[0];
      tasks.remove(0);
      PeriodicTask::set(tasks);
    }
    return pTask;
  }

  /// Removes the last PeriodicTask from the scheduler and returns it
  ///
  /// If there is no tasks scheduled, `null` is returned.
  static fn pop(): PeriodicTask? {
    var tasks = PeriodicTask::all();
    var len = tasks.size();
    var pTask: PeriodicTask?;
    if (len > 0) {
      pTask = tasks[len];
      tasks.remove(len);
      PeriodicTask::set(tasks);
    }
    return pTask;
  }

  /// Removes the task at the given index.
  ///
  /// Returns `true` on success, otherwise `false`
  static fn remove_at(index: int): bool {
    var tasks = PeriodicTask::all();
    var len = tasks.size();
    if (index >= len) {
      return false;
    }
    tasks.remove(index);
    PeriodicTask::set(tasks);
    return true;
  }

  /// Removes the first task that matches `task.function == ptr`
  ///
  /// Returns `true` on success, otherwise `false`
  static fn remove_first(ptr: function): bool {
    var tasks = PeriodicTask::all();
    for (i, pTask in tasks) {
      if (pTask.function == ptr) {
        tasks.remove(i);
        PeriodicTask::set(tasks);
        return true;
      }
    }
    return false;
  }

  /// Removes the last task that matches `task.function == ptr`
  ///
  /// Returns `true` on success, otherwise `false`
  static fn remove_last(ptr: function): bool {
    var tasks = PeriodicTask::all();
    var len = tasks.size();
    for (var i = len - 1; i >= 0; i--) {
      if (tasks[i].function == ptr) {
        tasks.remove(i);
        PeriodicTask::set(tasks);
        return true;
      }
    }
    return false;
  }
}