146 lines
7.1 KiB
Markdown
146 lines
7.1 KiB
Markdown
# Phase 1 actor prototype
|
|
|
|
Start the interactive, in-memory runtime session with:
|
|
|
|
```sh
|
|
cargo run -p runa
|
|
```
|
|
|
|
The executable loads [counter.rn](../runa-std/actors/counter.rn) as the default
|
|
behavior. Commands include `spawn`, `send`, `tick`, `inspect`, `actors`,
|
|
`events`, `stop`, and `restart`; enter `help` to see usage. All commands operate
|
|
on the same process-local runtime. This CLI is for local development, not a
|
|
versioned external API.
|
|
|
|
Example:
|
|
|
|
```text
|
|
runa> spawn 0
|
|
spawned ActorId(1)
|
|
runa> spawn 10
|
|
spawned ActorId(2)
|
|
runa> send 1 1 2
|
|
message queued
|
|
runa> tick 100
|
|
runa> inspect 1
|
|
runa> inspect 2
|
|
```
|
|
|
|
The sender commits state 1 and sends a structured message; the receiver commits
|
|
state 15. `tick <count> [instruction-budget]` explicitly advances the scheduler.
|
|
The default per-actor slice budget is 32 instructions.
|
|
|
|
## Current contracts
|
|
|
|
- `runa-actor` owns actor, mailbox and implementation IDs, structured messages,
|
|
snapshots, statuses and lifecycle events.
|
|
- `runa-core` owns stable source and implementation identity types.
|
|
- `runa-code` preserves original source, derives identities, and caches
|
|
compiled artifacts for the process lifetime.
|
|
- `runa-rune` owns compilation and typed bounded invocation execution.
|
|
- `runa-runtime` owns implementation registration, queues, private committed
|
|
state, scheduling, message effects and lifecycle coordination.
|
|
- `runa` composes the experiment and demonstration.
|
|
|
|
An implementation is published once under an ID. Actors reference that exact
|
|
implementation. Implementation IDs use source content and the compile
|
|
fingerprint; actor and mailbox IDs remain process-local counters. Actor IDs and
|
|
incarnation counters use checked increments.
|
|
|
|
The source identity hashes the exact original UTF-8 bytes with the versioned
|
|
`runa-source:v1` domain. Whitespace/comment canonicalization is intentionally
|
|
not attempted; textually different source has a different identity. The
|
|
implementation identity includes the source ID, pinned Rune version, compiler
|
|
settings/features, registered native-module manifest, runtime ABI version, and
|
|
Cargo target triple. The native-module manifest and runtime ABI identifier must
|
|
be updated when those compile inputs change.
|
|
|
|
The cache is in-memory only. Invalid source is retained in the current
|
|
`CodeStore` for diagnostics, but does not produce a compiled artifact. Cache
|
|
identity is not a claim that Rune bytecode is portable or safe to persist.
|
|
`CodeStore::compile_with_module` requires an explicit stable module identity;
|
|
callers must change it when registered native functions or their contracts
|
|
change. The CLI currently creates a fresh `CodeStore` when it starts.
|
|
|
|
Prototype state is an `i64`; messages contain integer `kind` and `value`
|
|
fields. Rune handlers have this deliberately restricted interface:
|
|
|
|
```text
|
|
handle(state, kind, value) -> (new_state, [(target_actor_id, kind, value), ...])
|
|
```
|
|
|
|
The returned effect vector describes sends; handlers cannot access the runtime
|
|
or directly mutate committed state. General schema/versioned values and
|
|
capability-bearing contexts are future work.
|
|
|
|
## Scheduling and lifecycle
|
|
|
|
`tick(nonzero_budget)` gives each runnable actor one bounded slice in ID order.
|
|
FIFO queues are bounded, including the reserved message in capacity accounting.
|
|
A queued message is reserved when its invocation starts. Subsequent messages
|
|
stay queued, including while the actor awaits. A wake marks an awaiting actor
|
|
runnable; the caller still needs to drive ticks.
|
|
|
|
Successful handlers validate every send target and its aggregate queue
|
|
capacity before updating state, acknowledging the reserved message, and
|
|
enqueueing effects. Invalid effects fail the actor without partial sends or a
|
|
state commit. This is a **logical, single-worker in-memory atomicity guarantee**:
|
|
observers using the runtime see either the transition before commit or after
|
|
commit, not an intermediate state/effect set. It is not crash-safe or durable.
|
|
External side effects performed by native callbacks are outside this boundary
|
|
and cannot be rolled back.
|
|
|
|
Runtime errors retain the last committed state and reserved message, set an
|
|
observable failed status, and emit a failure event. The failed message is not
|
|
silently acknowledged or automatically retried. External sends return errors
|
|
for unknown, unavailable, or full actors.
|
|
|
|
Stopping drops the invocation, retaining committed state, mailbox and reserved
|
|
message. Restarting a stopped or failed actor preserves its actor ID, mailbox
|
|
ID, implementation ID, committed state, and queued-message order; it increments
|
|
its incarnation and puts the reserved message at the front of the queue.
|
|
Restart is explicit. Repeated failure of the same reserved message can therefore
|
|
block later messages until an operator changes the implementation or otherwise
|
|
resolves it; there is no automatic poison-message policy or supervision.
|
|
|
|
### Transition and retry guarantees
|
|
|
|
| Situation | State | Reserved input | Effects | Status |
|
|
| --- | --- | --- | --- | --- |
|
|
| Handler returns a valid transition | New state commits | Acknowledged | All validated sends enqueue | `Idle` |
|
|
| Rune execution or transition decoding fails | Last committed state retained | Retained | None from the returned transition | `Failed` |
|
|
| Any target is unknown, stopped, failed, or over capacity | Last committed state retained | Retained | No sends from the transition enqueue | `Failed` |
|
|
| Stop during an invocation | Last committed state retained | Retained | None from the incomplete transition | `Stopped` |
|
|
| Restart after stop or failure | Retained until retried | Moved to queue front | None until retry completes | `Idle` |
|
|
|
|
Mailbox capacity counts queued plus reserved messages. Before applying a
|
|
transition, the runtime validates all effects, including aggregate sends to a
|
|
single target. For a self-send, the completing input is acknowledged as part
|
|
of the same transition, freeing its reserved slot before the new effects are
|
|
enqueued. An invalid target or over-capacity effect rejects the entire returned
|
|
transition.
|
|
|
|
These are process-local guarantees only. A host crash loses state, mailboxes,
|
|
reservations and events. External/native callback effects may already have
|
|
occurred even when the Rune invocation later fails or is stopped.
|
|
|
|
Lifecycle tests verify that a failed reservation retries from the last
|
|
committed state, that stop/restart preserves reserved-first FIFO order, and
|
|
that a running actor's queued messages are not processed until the reservation
|
|
finishes. Transition tests cover malformed results, runtime failures, invalid
|
|
targets, aggregate capacity, and successful message effects.
|
|
|
|
## Limits
|
|
|
|
This is not yet a full image: no persistent mailboxes, transactional outbox,
|
|
supervisors, hot upgrades, API server, GUI, or automatic image bootstrap.
|
|
The example default script is bundled through `include_str!`.
|
|
Messages are not accepted by stopped/failed actors.
|
|
|
|
Native functions must be nonblocking. Dropping a future proves cleanup only
|
|
for the controlled test operation, not rollback of arbitrary external effects.
|
|
Instruction slices cannot interrupt a native callback.
|
|
|
|
The [execution decision](decisions/0001-worker-local-execution.md) records the
|
|
scheduling trade-offs. Heap profiling and source-level debugging remain open.
|