Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ void run(List<String> args) async {
}
```

Endpoints need no registration: the generated `Endpoints` object passed to the constructor carries them all. Web routes, by contrast, are registered imperatively on `pod.webServer`, which is why the scaffolded `run()` contains route setup. When `pod.start()` runs, the server connects to the database, applies pending migrations when started with `--apply-migrations`, connects to Redis if enabled, brings up its servers, and starts background work such as [future calls](../scheduling/setup) and [health checks](../operations/health-checks).
Endpoints need no registration: the generated `Endpoints` object passed to the constructor carries them all. Web routes, by contrast, are registered imperatively on `pod.webServer`, which is why the scaffolded `run()` contains route setup. When `pod.start()` runs, the server connects to the database, applies pending migrations when started with `--apply-migrations`, connects to Redis if enabled, brings up its servers, and starts background work such as [future calls](../scheduling/overview) and [health checks](../operations/health-checks).

## The three servers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ The server accepts arguments that control how it starts: `--mode` selects the ru

In production, the `--role` argument controls which parts of the server run:

- **`monolith`** (default) runs everything: the API, Insights, and web servers, plus [future calls](../scheduling/setup) and [health checks](../operations/health-checks).
- **`monolith`** (default) runs everything: the API, Insights, and web servers, plus [future calls](../scheduling/overview) and [health checks](../operations/health-checks).
- **`serverless`** serves requests only. Future calls and health checks are disabled, which fits platforms that start and stop instances on demand.
- **`maintenance`** starts no servers. It performs one-shot work, applying migrations when passed `--apply-migrations` and running any due future calls, then exits. The exit code reports success or failure, which makes it fit for CI jobs and scheduled maintenance tasks.

Expand Down
8 changes: 4 additions & 4 deletions docs/06-concepts/02-endpoints-and-apis/02-sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Serverpod creates a session for every unit of work it runs, and the type reflect
| **WebCallSession** | [Web server](../web-server/overview) routes | Single request | Web pages, form submissions |
| **MethodStreamSession** | [Streaming methods](./streaming) | Stream duration | Real-time updates, chat |
| **StreamingSession** | WebSocket connections of the [deprecated streaming endpoints API](./streaming#streaming-endpoints-deprecated) | Connection duration | Legacy real-time code |
| **FutureCallSession** | [Scheduled tasks](../scheduling/setup) | Task execution | Email sending, batch jobs |
| **FutureCallSession** | [Scheduled tasks](../scheduling/overview) | Task execution | Email sending, batch jobs |
| **InternalSession** | [Manual creation](#create-a-session-for-background-work) | Until closed | Background work, migrations |

You rarely choose a type yourself: your endpoint methods receive the right one, and manual creation always produces an `InternalSession`.
Expand All @@ -49,7 +49,7 @@ class ExampleEndpoint extends Endpoint {
| `authenticated` | The current user's authentication info. See [authentication](../authentication/basics). |
| `log(...)` | Write a log entry tied to this session. See [logging](#logging). |
| `addWillCloseListener(...)` | Register a cleanup callback that runs before the session closes. |
| `serverpod` | The running Serverpod instance, for example for [future calls](../scheduling/setup). |
| `serverpod` | The running Serverpod instance, for example for [future calls](../scheduling/overview). |

## Session lifecycle

Expand Down Expand Up @@ -117,7 +117,7 @@ Future<void> processUser(Session session, int userId) async {
}
```

Schedule the work as a [future call](../scheduling/setup) instead. Future calls survive server restarts and receive their own session when they run:
Schedule the work as a [future call](../scheduling/future-calls) instead. Future calls survive server restarts and receive their own session when they run:

```dart
Future<void> processUser(Session session, int userId) async {
Expand Down Expand Up @@ -146,6 +146,6 @@ The test tools provide a `sessionBuilder` for calling endpoints in tests and sim
## Related

- [Working with endpoints](../endpoints-and-apis): the methods that receive sessions.
- [Scheduling](../scheduling/setup): future calls, the managed way to run delayed work.
- [Scheduling](../scheduling/overview): future calls, the managed way to run delayed work.
- [Logging](../operations/logging): where session log entries go.
- [Authentication](../authentication/basics): what `session.authenticated` holds.
41 changes: 41 additions & 0 deletions docs/06-concepts/06-scheduling/01-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
description: Future calls schedule work to run later, on a delay, at a set time, or on a repeating schedule, persisted in the database so they survive restarts.
---

# Overview

A future call is a piece of server work you schedule to run later instead of right now. You pick a method, say when it should run, and Serverpod invokes it at that time, even if the request that scheduled it has long since finished. Common uses are a welcome email an hour after sign-up, a reminder a day before an appointment, or a cleanup job that runs every night.

Future calls are stored in the database, so they survive a server restart and are shared across every server instance you run. This makes them different from an in-memory `Timer`: a scheduled call is not lost if the process that created it stops.

## What you can schedule

You write a future call as a method on a class, generate the type-safe code, and then schedule it in one of three ways:

- **After a delay**, for example one hour from now.
- **At a specific time**, for example a fixed date and time in the future.
- **On a repeating schedule**, either a fixed interval (every 20 minutes) or a [cron](https://en.wikipedia.org/wiki/Cron) expression (every day at 02:00 UTC). Cron is a standard text format for describing repeating schedules.

## Execution guarantees

A scheduled call runs **at least once**. Across all your running server instances, one instance claims each call and normally runs it a single time. If that instance crashes partway through, the call is picked up and run again by another instance, so a call can occasionally run more than once. Write work that is safe to repeat (for example, check whether the email was already sent before sending it).

If the method itself throws an exception, Serverpod logs the error and does not retry the call. You are responsible for retrying failed work: schedule a new call if the work needs to happen. Recurring calls are the exception, since the next run is always scheduled regardless of whether the current one succeeds.

## Where future calls run

Which servers run future calls depends on their role, the mode a server is started in. Servers in the default `monolith` role run them, and future calls need a database. A server started in the `serverless` role does not run them. If you host in a serverless environment, run a separate scheduled process in the `maintenance` role to execute due calls. See [server roles](../server-fundamentals/running-your-server#choose-a-server-role) and [hosting elsewhere](../../deployments/custom-hosting/hosting-elsewhere).

## What each page covers

- **[Future calls](future-calls)**: define a future call, schedule it after a delay or at a time, and cancel scheduled calls.
- **[Recurring tasks](recurring-tasks)**: run a call on a repeating cron or interval schedule.
- **[Inheritance](inheritance)**: extend future calls from other classes and modules.
- **[Configuration](configuration)**: set concurrency, the scan interval, and broken-call handling.
- **[Legacy](legacy)**: the older string-based API, kept for existing code.

## Related

- [Sessions](../endpoints-and-apis/sessions): the session a future call receives when it runs.
- [Server roles](../server-fundamentals/running-your-server#choose-a-server-role): which roles run scheduled work.
- [Configuration reference](../lookups/configuration-reference): every future-call key with its environment variable.
140 changes: 0 additions & 140 deletions docs/06-concepts/06-scheduling/01-setup.md

This file was deleted.

138 changes: 138 additions & 0 deletions docs/06-concepts/06-scheduling/02-future-calls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
description: A future call is a method Serverpod runs later, scheduled after a delay or at a specific time, and cancellable by identifier.
---

# Future calls

A future call is a method that Serverpod runs later. You define it on a class, generate the type-safe code, and schedule it from anywhere you have access to the server. This page covers the one-off cases: run once after a delay, or once at a specific time. For repeating schedules, see [Recurring tasks](recurring-tasks).

## Define a future call

Extend the `FutureCall` class and add the methods you want to invoke later.

```dart
import 'package:serverpod/serverpod.dart';

class ExampleFutureCall extends FutureCall {
Future<void> doWork(Session session) async {
// Do something interesting in the future here.
}

Future<void> doOtherWork(Session session, String data) async {
// Do something interesting in the future here.
}
}
```

Each method must:

- Return `Future<void>`.
- Take a [`Session`](../endpoints-and-apis/sessions) as its first parameter.
- Use only [serializable types](../data-and-the-database/models) for any other parameters. Typed `List`, `Map`, `Set`, and Dart records are allowed, but streams are not.

Generate the code for your future calls. With `serverpod start` running, saving the file regenerates the code. Outside a session, run `serverpod generate`.

This creates a type-safe interface in your server's `lib/src/generated/future_calls.dart` file, reachable from the `Serverpod` object as `pod.futureCalls`. You do not register these calls yourself. Serverpod does it for you when the server starts, so your `server.dart` needs no future-call setup:

```dart
import 'package:serverpod/serverpod.dart';

import 'src/generated/protocol.dart';
import 'src/generated/endpoints.dart';

void run(List<String> args) async {
final pod = Serverpod(
args,
Protocol(),
Endpoints(),
);

await pod.start();
}
```

:::note
Comment thread
developerjamiu marked this conversation as resolved.
The `pod.futureCalls` accessor exists only once you have defined at least one future call and generated the code.
:::

:::warning
Scheduling a call before the server has started throws an exception, so schedule from an endpoint or after `pod.start()` rather than while building the server.
:::

## Schedule a call

Schedule a call by chaining the timing method, the accessor for your class, and the method to invoke. The accessor is your class name with the first letter lowercased and a trailing `FutureCall` removed, so `ExampleFutureCall` becomes `example`.

Run a call one hour from now with `callWithDelay`:

```dart
await pod.futureCalls
.callWithDelay(const Duration(hours: 1))
.example
.doWork();
```

Run a call at a specific time with `callAtTime`:

```dart
await pod.futureCalls
.callAtTime(DateTime(2030, 1, 1))
.example
.doOtherWork('1');
```

The call runs at the exact instant you pass. A `DateTime` denotes an absolute moment, so `DateTime(2030, 1, 1)` schedules the call for midnight in the server's local time. Pass a UTC value, such as `DateTime.utc(2030, 1, 1)`, if you want the numbers to be read as UTC.

### Schedule from an endpoint

Inside an endpoint you reach the server through `session.serverpod`, so you can schedule a call in response to a request:

```dart
class OrderEndpoint extends Endpoint {
Future<void> placeOrder(Session session, Order order) async {
// ... store the order ...

// Send a follow-up an hour from now.
await session.serverpod.futureCalls
.callWithDelay(const Duration(hours: 1))
.example
.doWork();
}
}
```

## Cancel scheduled calls

Give a call an `identifier` when you schedule it so you can cancel it later. The same identifier can be applied to several calls.

```dart
await pod.futureCalls
.callWithDelay(
const Duration(hours: 1),
identifier: 'an-identifying-string',
)
.example
.doWork();
```

Cancel every not-yet-run call that shares an identifier:

```dart
await pod.futureCalls.cancel('an-identifying-string');
```

Calls that share an identifier run independently. The identifier only groups them so you can cancel them together, and cancelling affects only calls that have not run yet.

## Inspect scheduled calls

Scheduled calls are stored in the `serverpod_future_call` database table. You can query it to see what is pending, which helps when debugging why a call did or did not run.

## Execution timeout

Future calls have no execution timeout: a call runs until its method returns. If a call needs a time limit, enforce it inside the method yourself, for example with `Future.timeout` around the work.

## Related

- [Recurring tasks](recurring-tasks): run a call on a repeating schedule.
- [Configuration](configuration): concurrency, scan interval, and broken-call handling.
- [Sessions](../endpoints-and-apis/sessions): the session a future call receives when it runs.
Loading
Loading