Skip to content

PROP-239 - Add latent tasks for on-demand FaaS execution - #252

Open
JeffMboya wants to merge 10 commits into
absmach:mainfrom
JeffMboya:PROP-239
Open

PROP-239 - Add latent tasks for on-demand FaaS execution#252
JeffMboya wants to merge 10 commits into
absmach:mainfrom
JeffMboya:PROP-239

Conversation

@JeffMboya

Copy link
Copy Markdown
Contributor

What type of PR is this?

Feature: adds latent FaaS tasks that precompile on deploy and instantiate on demand.

What does this do?

Deploys a task that precompiles its WASM component without running, then instantiates it per request in parallel.

Which issue(s) does this PR fix/relate to?

Resolves #239

Have you included tests for your changes?

Yes. Proplet precompile/invoke and concurrent-invocation tests, plus a manager InvokeTask service test.

Did you document any new/modified features?

No README/API docs changed; usage is the new tasks invoke <id> CLI command.

Notes

Latent flag persists only in memory/badger stores, matching existing Priority and Schedule handling.

JeffMboya added 3 commits July 2, 2026 10:11
…ocation

Latent tasks pull and precompile their WASM component on deploy but do
not execute it. The compiled component is cached and instantiated on
demand when an invoke request arrives; concurrent invocations each get a
fresh instance so they run in parallel.

- Runtime gains precompile() and invoke() (default: unsupported)
- WasmtimeRuntime caches precompiled components and reuses the shared
  component run paths for both start and invoke
- StartRequest gains a latent flag; latent starts precompile and return
- InvokeRequest and a control/manager/invoke handler drive invocations,
  publishing results to control/proplet/invoke_results
Adds a Latent flag on tasks that is forwarded to the proplet in the
start payload, so a task can be deployed without executing. A new
InvokeTask service method and POST /tasks/{taskID}/invoke endpoint
publish invocations to control/manager/invoke; per-invocation results
arrive on control/proplet/invoke_results and are recorded without
marking the deployed task terminal.

- Service.InvokeTask + publishInvoke + updateInvokeResultsHandler
- ActionInvoke plugin authorization and middleware wrappers
- invoke-task HTTP endpoint, decoder and mock
Expose InvokeTask on the SDK and a "tasks invoke <id> [inputs...]"
CLI command so latent tasks can be triggered on demand.
@JeffMboya JeffMboya changed the title feat: add latent tasks for on-demand FaaS execution PROP-239 - Add latent tasks for on-demand FaaS execution Jul 2, 2026
JeffMboya added 7 commits July 2, 2026 10:43
Store the precompiled component's wasm bytes once behind an Arc instead
of deep-copying the whole StartConfig (including the wasm binary) on
every invocation. invoke now clones only an Arc refcount and an empty
config; run_component_export takes the bytes as a parameter, removing a
second per-call copy on both the start and invoke paths.
Invoke now always targets exactly one proplet: a pinned task uses its
deployed proplet, and a broadcast-deployed latent task (present on every
active proplet) is load-balanced via the round-robin scheduler instead
of fanning the request out to all of them. This distributes request load
across proplets and gives each invocation a single responder.
Invoke is now request/response instead of fire-and-forget. The manager
tags each invocation with an id, registers a waiter, and blocks until
the proplet publishes the matching result on control/proplet/invoke_results
(or the request times out). The proplet echoes the invocation id back in
an InvokeResultMessage. InvokeTask, the SDK, the CLI and POST
/tasks/{id}/invoke now surface the function's output to the caller, so an
external HTTP client gets the result directly.
SelectProplet mutates LastProplet without synchronization. The latent
invoke path now calls it concurrently (load-balancing broadcast tasks),
racing with concurrent task starts. Add a mutex around the counter and a
concurrent regression test.
InvokeTask used time.After, which leaks a 30s timer every time the
result or context case wins the select — the common path on the FaaS
hot path. Use time.NewTimer with defer Stop().
updateInvokeResultsHandler only set the task error on failure, so a
successful invocation after a failed one left a stale error on the task.
Set it unconditionally so the persisted result and error always reflect
the latest invocation.
The synchronous invoke path already returns each result to the caller
via the pending-invocation waiter, so writing the last invocation's
result back onto the task was pure overhead: a task-repo write per
invocation plus a last-write-wins race with concurrent invokes. The
invoke-results handler now only routes the result to the waiting caller.

@rodneyosodo rodneyosodo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics observations while reviewing latent tasks

Comment thread proplet/src/service.rs
}

info!(
"Latent task {} precompiled and ready for on-demand invocation",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tasks_running is incremented at L539 for all start commands (including latent), but only decremented on the error path (L691). The success path just returns Ok(()) and leaks the counter.

Comment thread proplet/src/service.rs
let invoke_span = tracing::info_span!("task.invoke.execute", task_id = %task_id);
tokio::spawn(
async move {
metrics.tasks_started.inc();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tasks_started is already incremented during precompile (L538). This second increment on invoke means tasks_started = 1 + N while tasks_completed = N — the metric invariant is off by one.

Comment thread proplet/src/service.rs
metrics.tasks_started.inc();
metrics.tasks_running.inc();

let result = runtime.invoke(task_id.clone(), inputs, env).await;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If runtime.invoke() panics, tasks_running.dec() at L1129 is skipped. Consider wrapping in catch_unwind or noting the gap.

Comment thread manager/service.go
}

return svc.taskPropletRepo.Get(ctx, t.ID)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

publishInvoke uses map[string]any while every other publish method uses a typed struct. Also InvokeRequest.env exists but is never populated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Latent tasks to provide FaaS

2 participants