service.controller¶
The controller: follows the workflows of the runs and records their end.
The state of a run is the service's, not the one of Argo (Ruling 17b): a split task is
done when its <task>-merge node succeeded (when its coordinator <task>-w0 merges,
TaskPlan.coordinator_merges, there is no such node and it is done when <task>-w0
succeeded and wrote out/.simrun-merged; without the marker it waits for it, running,
up to MERGE_MARKER_GRACE_S from the first poll which saw the coordinator succeeded
(RunRecord.merge_pending), and fails after it: the harness of its image is one which
does not merge, from simrun 0.1.0) and failed when
its <task>-w0 or its merge failed (after the retries of Argo), an extra worker
never fails a task; an
unsplit task is its node <task>. When every task is done the run succeeded, when a
task failed the run failed; in both cases the controller stops the workflow, so extra
workers still waiting for admission go away, and releases the reserved node. A run
whose workflow ended is not failed for its phase while a task waits for the marker of
its merge.
A pod is retried once with the retry memory after a kill for memory: a worker, a
merge or an unsplit task killed for memory again with the retry memory fails the run
(Ruling 35), whose message names the retry memory. A kill for memory needs the
evidence of the out of memory killer (Ruling 39): the reason OOMKilled of the
backend, or the report of an attempt which exited 137 counting kills of the out of
memory killer in its cgroup; a pod killed after its grace period was evicted.
The states of the tasks and the out of memory kills are stored in the record at every
poll. When the workflow is gone (deleted by its time to live before the controller saw
its end) the tasks are resolved from these states and from the store: a split task
whose merge wrote out/.simrun-merged is done (Ruling 23), one which waits for the
marker of its merge keeps the run open until the marker is there or its wait is over;
only the tasks still undetermined fail.
The reports of the workers of a task (of simrun-exec for an unsplit plugin task,
by the task id) are recorded as resource observations once the state of the task is
final, each marked in tasks/<task>/recorded/ right after its
observation; a kill for memory of an attempt without a report is an observation of
the pod shape at its limit (Ruling 24).
The logs of the pods of a task are kept in the run store once the task is final and
its pods terminated, the ones which ended before the workflow is stopped or
cancelled (simrun.service.logs); an ended run stays followed (pending_logs) until
its logs are kept or its workflow is gone.
A terminal record is written first; a release of the node or a cancellation of the
workflow which fails stays in the record (pending_release, pending_cancel) and is
retried at the next polls, which do nothing else for a terminal run (Ruling 25). No
call of the backend is made while the record lock is held.
The controller keeps no state in memory: every reconcile reads the records of the
live runs of the index of the store (simrun.service.index), which the service
rebuilds from the records at its start, so a restarted service continues where the
one before stopped; a record which cannot be read is logged and skipped.
Controller
¶
Maps the status of the backend onto the records of the runs.
Create the controller.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
IndexedRunStore
|
The run store with its index. |
required |
resources
|
ResourceStore
|
The store of the resource observations. |
required |
backend
|
Backend
|
The backend the workflows run on. |
required |
types
|
dict[str, TaskType]
|
The task types by name. |
required |
wait_for_wake
¶
Block until woken or timeout_s passed; clears the wake.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout_s
|
float
|
The longest time to wait, in seconds. |
required |
start_submission
¶
Run the submission of a run in the background, see in_flight.
A job which raises fails a run which is still submitting; its workflow may exist, so it is cancelled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run_id
|
str
|
The run, written |
required |
job
|
Callable[[str], None]
|
Resolves, builds and submits the run; called with its id. |
required |
close
¶
Stop taking submissions, without waiting for the running ones.
A build may take an hour; the pod of the service is killed long before.
A run left submitting is resumed at the next start of the service
(recover), from its record.
recover
¶
Settle what a stopped service left: called at the start of the service.
The service is one process, so a run submitting since before the controller
started has lost its submission: it is resumed from its record
(resume, idempotent: an image which was built is found, a node reserved
for the run is kept, a workflow which was created is taken as submitted).
A submission created later may be in flight (the API accepts runs while this
runs) and is left to reconcile, which never fails a submission running in
this process. A record which is not terminal and has no workflow name was
written before names were stored, and fails. The pending logs, releases and
cancellations of terminal runs are retried.
reconcile
¶
Bring the records of the live runs up to date with the backend.
The live runs are the ones of the index which have not ended or have a
release or a cancellation pending; an ended run is not read again. Runs
missing from the index are indexed first (IndexedRunStore.reindex) and
followed also while their index rows cannot be written.
write_if_unchanged
¶
Write after when the record on disk is still before.
Returns:
| Type | Description |
|---|---|
bool
|
Whether |
cancel
¶
Cancel a run: write it cancelled, then cancel its workflow, release its node.
Raises:
| Type | Description |
|---|---|
KeyError
|
The run does not exist. |
ValueError
|
The run has ended. |
settle
¶
Retry the pending logs, release and cancellation of a terminal run.
The logs are kept before the workflow is cancelled; a workflow which does not exist (any more) has no logs to keep. An unknown node counts as released and an unknown workflow as cancelled; a node is released only from the reservation of this run. A run which was deleted meanwhile is ignored.
has_merge_marker
¶
Whether the output of a task has the marker of a merge, out/.simrun-merged.
The directories are listed instead of looking up the marker: on NFS a listing
revalidates the directory, while a lookup may answer from a cached negative entry
of a lookup before the pod wrote the file. The task directory is listed first, so
a cached negative entry of out/ itself does not hide it either.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_dir
|
Path
|
The directory of the task in the run store. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
Whether the marker exists; not when |
task_plans
¶
The plans of the tasks of a run from its record.
A plan written before coordinator_merges existed reads it as False.
task_states
¶
The state of every task of a workflow from the phases of its DAG nodes.
A task is running when one of its pods runs (or its coordinator is done and the merge waits), preempted when none runs and a pod evicted waits for its retry, retrying when none runs and a pod which failed otherwise waits for its retry, queued when it has pods of which none runs, waiting without pods.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workflow
|
Workflow
|
The workflow of the run. |
required |
types
|
dict[str, TaskType]
|
The task types by name. |
required |
status
|
BackendStatus
|
The status of the workflow in the backend. |
required |
plans
|
dict[str, TaskPlan]
|
The plans of the tasks, by id; the state of a split task whose plan
merges in the coordinator is |
required |
merge_state
|
Callable[[str], TaskState]
|
The state of a task by id whose coordinator which merges succeeded: done with the marker of its merge, running while the marker is awaited, failed when the coordinator ran a harness which does not merge. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, TaskState]
|
The state of every task by id. |
is_oom
¶
Whether a report is of a worker which was killed for memory.
Only the evidence of the out of memory killer counts (Rulings 34, 39): a peak near the limit is no kill, the kernel reclaims the page cache there, and an exit 137 may be a kill after the grace period.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
WorkerReport
|
The report. |
required |
attempt
|
int
|
Its attempt. |
required |
kills
|
dict[str, list[int]]
|
The attempts killed for memory per worker ( |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True when the attempt is one killed for memory, or the report counts kills |
bool
|
of the out of memory killer in its cgroup. |
closed
¶
The terminal record: tasks not final are skipped, the node is to release.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
RunRecord
|
The record. |
required |
state
|
RunState
|
The terminal state. |
required |
tasks
|
dict[str, TaskState]
|
The states of the tasks. |
required |
message
|
str | None
|
The message, e.g. why the run failed. |
required |
**update
|
object
|
Further fields of the record. |
{}
|
Returns:
| Type | Description |
|---|---|
RunRecord
|
The record, ended now. |