:sig
function task_execute(target : as string, timeout_seconds : as f64, props : as dval = {}) dval

:params
target : a unit path followed by `:NAME`, naming the `TASK:NAME` handler to run
timeout_seconds : how long the task may run; when it is up the process is ended, finished or not
props : the task input, received as the handler's `props`

:returns
`{pid}` when the task process started; `{error}` with a code when it could not

:content
`task_execute()` runs a task handler outside the request, right away, in a process of its own. It returns as soon as that process is started and never waits for the handler. This is the call for work that must not happen on the request's time and that nobody needs to wait for: sending the mail, writing the audit row, poking an external system.

The timeout is mandatory. The handler runs under it as its invocation deadline, and the process is ended when the time is up whether the handler finished or not. Choose it for the work, not generously; a task that needs an hour is a service.

The task runs in a fresh workspace with a copy of the props. It does not see the caller's request, session, connections, or unit variables. Its printed output and its failures go to the operator log.

:errors
An unknown unit yields `invalid_target`; a timeout of zero yields `invalid_timeout`; a process that could not be started yields `spawn_failed` or `runner_failed`. A handler the unit does not define, a trap, and the timeout are logged by the task process; the caller is not told, because it has already moved on.

:example capy render
var started := task_execute("/doc/examples/task_worker.capy:STORE", 30, key: "report-42")
if started.error?
{
	print("could not start: ", string(started.error))
}

:see-group
tasks

:see
handler/service
capy-11-tasks-and-jobs
