Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ execution = client.durable.start(
)
print(execution.id, execution.status)

execution = client.durable.get_from_application(
"order-pipeline", execution.id, client_token=execution.client_token
)
print(execution.is_terminal, execution.result)

owner_client = VolcanoClient(
api_url="https://api.volcano.dev",
anon_key="your-anon-key",
Expand Down Expand Up @@ -245,26 +250,31 @@ Anonymous and service keys do not refresh.

`durable.start()` begins an execution of a deployed durable function and returns
a handle rather than a result: an execution can run for up to 366 days, so its
result is read back with `durable.get()`. It takes the credential `functions.invoke()`
takes, and is, with `durable.messages`, the only durable operation an
application credential may perform.
result is read back later. It takes the credential `functions.invoke()` takes.
An `execution_name` makes the start idempotent — starting again under the same
name returns the execution that already exists rather than beginning a second
one, and is charged once.

`durable.get()`, `durable.list()` and `durable.stop()` are owner-scoped and need
the project owner's platform user token or a project access token, because an
execution is addressed by its id alone and an anonymous key is held by everyone
who loads the page. A `read_only` project access token can read and list but not
stop. Auth-user sessions from `sign_in()`, anonymous keys, and service keys are
not accepted. Poll them from a trusted backend.
`get()` carries `result` once the execution has succeeded and `error` when it
one, and is charged once. Only the start that creates the execution returns its
`client_token`.

`durable.get_from_application()` and `durable.stop_from_application()` read and
stop an execution with the credential `durable.start()` takes. A service key
holding `*`, the default, reaches any execution of the project and a signed-in
user the executions they started; an anonymous key, a service key scoped without
`*`, or a user who did not start the execution, passes the `client_token`. An
execution the caller cannot reach raises `NotFoundError`, as a missing one does.

`durable.get()`, `durable.list()` and `durable.stop()` are owner-scoped, reach any
execution of the project, and need the project owner's platform user token or a
project access token. A `read_only` project access token can read and list but
not stop. Auth-user sessions from `sign_in()`, anonymous keys, and service keys
are not accepted. Call them from a trusted backend.
Every read carries `result` once the execution has succeeded and `error` when it
failed; `result_expired` separates a result the platform has discarded from a
function that returned nothing. `is_terminal` reports whether the execution has
stopped changing, and counts `unknown` — the status the platform writes for an
outcome it could not determine — as finished. `stop()` is accepted rather than
outcome it could not determine — as finished. Either stop is accepted rather than
awaited: what it returns is the execution read back after asking, often still
`running`, so poll `get()` to see it reach `stopped`. Repeating a stop is safe.
`running`, so poll a read to see it reach `stopped`. Repeating a stop is safe.

`durable.approvals` reads and decides the approvals a durable function waits on:

Expand Down
12 changes: 10 additions & 2 deletions docs/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ handle = client.durable.start(
{"order_id": "order-9"},
execution_name="order-9",
)
execution = client.durable.get_from_application(
"charge-order", handle.id, client_token=handle.client_token
)
client.durable.stop_from_application(
"charge-order", handle.id, client_token=handle.client_token
)

owner_client = VolcanoClient(
anon_key=os.environ["VOLCANO_ANON_KEY"],
Expand All @@ -85,9 +91,11 @@ page = owner_client.durable.list(project_id, "charge-order", status="running")
owner_client.durable.stop(project_id, "charge-order", handle.id)
```

`start()` accepts the same active session, service key, or anonymous key as `functions.invoke()`. Besides [sending and following messages](#send-and-follow-messages), it is the only durable operation available to application credentials. An execution name makes a start idempotent.
`start()` accepts the same active session, service key, or anonymous key as `functions.invoke()`. An execution name makes a start idempotent. Only the start that creates the execution returns its `client_token`; a repeat under the same name and a read never do, so keep it with the execution id.

`get_from_application()` and `stop_from_application()` read and stop an execution with the credential `start()` takes, as [sending and following messages](#send-and-follow-messages) does. A service key holding `*`, the default, reaches any execution of the project, and a signed-in user reaches the executions they started. An anonymous key, a service key scoped without `*`, or a user who did not start the execution, passes the `client_token`. The function's visibility must also admit the caller. An execution the caller cannot reach raises `NotFoundError`, as a missing one does, and until an execution leaves `pending` only a service key holding `*` reaches it; that key stopping one then raises `ConflictError`. Reads and stops share the function's rate limit with starts, so poll every few seconds. A `ServerError` from a stop can follow a stop that was already sent, so read the execution before stopping it again.

`get()`, `list()`, and `stop()` are owner-scoped. Call them from a trusted backend with the project owner's platform user token or a project access token; a `read_only` token can read and list but not stop. Auth-user sessions, anonymous keys, and service keys are not accepted. `stop()` returns after the stop request is accepted, so poll `get()` until `is_terminal` is true.
`get()`, `list()`, and `stop()` are owner-scoped and reach any execution of the project. Call them from a trusted backend with the project owner's platform user token or a project access token; a `read_only` token can read and list but not stop. Auth-user sessions, anonymous keys, and service keys are not accepted. Listing has no application equivalent. Either stop returns once the request is accepted, so poll a read until `is_terminal` is true.

## Write a durable function

Expand Down
219 changes: 210 additions & 9 deletions openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4383,10 +4383,13 @@ paths:
A `private` durable function answers every credential but a service key
exactly as a missing one, with 404.

Starting is all this endpoint does. Reading a result or stopping an
execution requires the project owner's token, because an anon key is
shared by everyone who loads the page and an execution is addressed by
id alone.
The start returns a `client_token`. An anon key, or a user who did not
start the execution, sends it as `X-Volcano-Client-Token` to read the
execution, stop it, and reach its messages. A service key scoped to a
list without `*` sends it to read and stop the execution. An anon key is shared by
everyone who loads the page, so the id alone never reaches an
execution. Listing executions takes the project owner's token or a
project access token.

Send `X-Volcano-Execution-Name` to make the start idempotent: repeating
a start with the same name returns the existing execution instead of
Expand Down Expand Up @@ -4505,6 +4508,200 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/durable-functions/{functionId}/executions/{executionId}:
get:
tags:
- Durable Functions
summary: Get a durable execution from an application
description: |
Returns the execution's current state with the credential that started
it, including its `result` once it has succeeded. Poll this to wait for
an execution to finish.

**With a service key:** any execution in the project when the key holds
`*`, the default. A key scoped to a list without `*` reaches an
execution whose `X-Volcano-Client-Token` the request carries, as an
anon key does; the start returns that token.

**With an auth user token:** an execution the user started, or one
whose `X-Volcano-Client-Token` the request carries, of a durable
function whose `visibility` is `authenticated` or `public`.

**With an anon key:** an execution whose `X-Volcano-Client-Token` the
request carries, of a function with `visibility: public`.

Only a service key holding `*` reaches an execution that is still
`pending`. Any other execution answers `404`, as a missing one does.

The response is the one `GET
/projects/{id}/durable-functions/{functionId}/executions/{executionId}`
returns. It never carries `client_token`.
operationId: getDurableExecutionFromApplication
security:
- AnonKey: []
- ServiceRoleKey: []
- AuthUserAccessToken: []
parameters:
- $ref: '#/components/parameters/DurableFunctionId'
- $ref: '#/components/parameters/DurableExecutionId'
- $ref: '#/components/parameters/DurableClientToken'
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/DurableExecution'
'401':
description: Missing or invalid credential
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: |
The anon key lacks `functions.invoke`, the service key is scoped
without it, or the function is `authenticated` and the request
carries an anon key. Also returned when the request's origin is
refused by the project's CORS policy.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: |
Durable function or execution not found, or the credential cannot
reach the execution. A `private` function answers every credential
but a service key this way.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: |
The function's or the project's function rate limit, which reads
share with invocations and starts, or the project is over its
bandwidth cap.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
description: |
Durable execution is not available in this environment, the plan
terms for the request could not be read, or reads of the
execution's state in its region are throttled. The first is not one
to retry in a loop; the others are transient, and a throttled read
carries `Retry-After`.
headers:
Retry-After:
description: |
Seconds to wait before reading again. Sent only when reads in
the execution's region are throttled.
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/durable-functions/{functionId}/executions/{executionId}/stop:
post:
tags:
- Durable Functions
summary: Stop a durable execution from an application
description: |
Cancels a running execution with the credential that started it. Its
completed steps are not undone.

Access follows the same rules as reading the execution.

The call is accepted rather than awaited: the response reports the
execution as it was read back and may still say `running`. Poll
`GET /durable-functions/{functionId}/executions/{executionId}` to see
it settle into `stopped`.

Stopping an execution that already finished is not an error: the
response carries the state it settled in.
operationId: stopDurableExecutionFromApplication
security:
- AnonKey: []
- ServiceRoleKey: []
- AuthUserAccessToken: []
parameters:
- $ref: '#/components/parameters/DurableFunctionId'
- $ref: '#/components/parameters/DurableExecutionId'
- $ref: '#/components/parameters/DurableClientToken'
responses:
'200':
description: |
Stop accepted. The body is the execution as it was read back, which
may still report `running`.
content:
application/json:
schema:
$ref: '#/components/schemas/DurableExecution'
'401':
description: Missing or invalid credential
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: |
The anon key lacks `functions.invoke`, the service key is scoped
without it, or the function is `authenticated` and the request
carries an anon key. Also returned when the request's origin is
refused by the project's CORS policy.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: |
Durable function or execution not found, or the credential cannot
reach the execution. A `private` function answers every credential
but a service key this way.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: |
The execution has not started yet. Only a service key holding `*`
can reach an execution in that state.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: |
The function's or the project's function rate limit, which stops
share with invocations and starts, or the project is over its
bandwidth cap.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
description: |
Durable execution is not available in this environment, the plan
terms for the request could not be read, or the execution's state
could not be read back because reads in its region are throttled.
The first is not one to retry in a loop; the others are transient,
and a throttled read carries `Retry-After`. A stop answered with a
throttle may already have been sent, so read the execution before
stopping it again.
headers:
Retry-After:
description: |
Seconds to wait before reading again. Sent only when reads in
the execution's region are throttled.
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/durable-functions/{functionId}/executions/{executionId}/messages:
post:
tags:
Expand Down Expand Up @@ -16926,7 +17123,8 @@ components:
required: false
description: |
The `client_token` the execution's start returned. Lets an anon key,
or a user who did not start the execution, reach it.
or a user who did not start the execution, reach it, and a service key
scoped to a list without `*` read and stop it.
schema:
type: string
maxLength: 128
Expand Down Expand Up @@ -21591,7 +21789,8 @@ components:
Whatever the function returned, verbatim. Absent while the execution
is still running, absent when the result was too large to return and
was checkpointed instead, and absent once the retention period has
lapsed.
lapsed. A result Volcano has just read can still be returned for up
to five minutes after that.
result_expired:
type: boolean
description: |
Expand All @@ -21614,9 +21813,11 @@ components:
client_token:
type: string
description: |
Lets a browser read and send the execution's messages. Returned only
by the start whose input the execution runs, never by a repeat under
the same name or by a read. Send it as `X-Volcano-Client-Token`.
Lets a browser read the execution, stop it, and read and send its
messages, and a service key scoped to a list without `*` read and
stop it. Returned only by the start whose input the execution runs,
never by a repeat under the same name or by a read. Send it as
`X-Volcano-Client-Token`.
required:
- id
- function_id
Expand Down
2 changes: 2 additions & 0 deletions scripts/generate_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"get_durable_approval.py",
"get_durable_approval_stats.py",
"get_durable_execution.py",
"get_durable_execution_from_application.py",
"force_release_project_lock.py",
"get_project_log_activity.py",
"get_project_lock.py",
Expand All @@ -45,6 +46,7 @@
"send_durable_message_from_application.py",
"start_durable_execution_from_application.py",
"stop_durable_execution.py",
"stop_durable_execution_from_application.py",
"update_storage_object_visibility.py",
"upload_storage_object.py",
}
Expand Down
Loading
Loading