Java Actor Public Interface¶
Interface table of contents · Common Actor Contract
Session–Actor binding §8.2 owns relocation route updates for a bound Session.
This document fixes the public interface that expresses Actor factory,
context, messaging, manager, and relocation adapter in Java. A regular
message targets with ActorId, and an operation that changes a specific
incarnation uses an ActorRef.
public interface ZLinkActorFactory {
CompletionStage<ZLinkActor> create(ZLinkActorContext context);
}
public interface ZLinkActorHandlerRegistry {
void addHandler(Class<?> handlerType);
}
public interface ZLinkRelocationCancellation {
boolean isCancellationRequested();
}
public interface ZLinkActorRelocationAdapter<TActor extends ZLinkActor> {
CompletionStage<byte[]> capture(
TActor actor, ZLinkRelocationCancellation cancellation);
CompletionStage<Void> restore(
TActor actor, byte[] state, ZLinkRelocationCancellation cancellation);
}
The builder member of
factory registration is owned
by Configuration And Host. Cross-node
relocation behavior is wired directly to the Actor factory configure
callback. The runtime checks the Actor the factory returned against the
specified actorClass, returning a type mismatch as a startup error. A
relocation registry separate from the factory isn't provided. The
adapterClass of preserveStateWith(...) must implement
ZLinkActorRelocationAdapter<TActor> for that Actor type. Adapter type
validation for User/Instance Spot policy is owned by the
Spot Interface. Taking Class<?> is a representation to
keep the policy value common due to Java type erasure, and the framework
checks whether the factory type and adapter generic target match before
socket bind. A mismatch is a startup configuration error.
preserveStateWith(null) is rejected as a configuration error during
callback execution.
The Actor adapter captures/restores application state as an opaque
byte[] with no relocation-adapter-specific size cap. The framework
splits the payload into chunks no larger than
relocationPayloadChunkLimitBytes and transfers them directly over the
source–target ordered mesh connection. Source memory is the restore
origin, and the handoff payload isn't stored in the Relocation Store. A
public state DTO, TState,
stateContractId, state class, and ZLinkMessage aren't put on the
relocation surface. The framework immediately copies the array once
capture completes normally. The array capture returns is still owned by
the adapter — reusing or changing it after completion doesn't change the
preserved payload. Restore is passed a fresh defensive copy of the preserved
payload per call, and the adapter doesn't keep that array after the
stage finishes. A zero-length array is also a valid preserved state — it
isn't interpreted as choosing recreateOnRelocation() or omitting
restore. The adapter doesn't receive owner claim, relocation envelope,
generation, or recovery phase.
If an Actor factory uses preserveStateWith(...) in cross-node
materialization, the same Actor adapter is used for maintenance Actor
relocation, remote User/Entry Spot
join, and each Actor participant of a whole User Spot relocation. The
adapter isn't called on a same-node join or on a factory that selected
disableRelocation() or recreateOnRelocation().
recreateOnRelocation() doesn't capture application state, so it has no
adapter.
The target commits owner after finishing restore and accepted journal
staging. After the lifecycle callback, the saved existing work is put on
the actual Actor queue first, and the relocation temporary queue's work
is moved after that. Once temporary queue registration is removed and
dispatch is switched atomically, the target opens as READY. Source
cleanup, the COMPLETED record don't block the target's message processing. If the target
process terminates after READY, it's handled as ordinary
owner loss, and the previous
relocation isn't automatically replayed. A public phase API for
manipulating this barrier isn't provided.
On a retry within the same source and target process, factory and
restore(...) can be called more than once. capture(...) can also be
repeated before the authority
commit. Only the current owner and attempt fence can commit completion
and open admission. Since the callback doesn't add a relocation ID,
application restore and capture must be retry-safe, and exactly-once
external side effect isn't guaranteed. The factory creates a fresh Actor
instance per target attempt, and the framework only calls that attempt's
restore(...) on that instance. The source instance or a previous target
attempt's instance isn't reused for a new attempt, and restore can be
repeated within the same attempt.
If the capture stage ends with an exception, the attempt is aborted
before authority publication and source authority and admission are
kept. If the restore stage ends with an exception, target admission is
kept sealed and can be retried with the same payload on the same target
process. A different target isn't automatically selected. An exception
isn't turned into an empty payload or normal completion. A null stage
and null byte[] from capture, and a null stage from restore, are
adapter contract violations. A precommit adapter exception and contract
violation where a deadline hasn't been fixed yet in host relocation are
classified as Blocked/StateIncompatible. Once a
deadline is fixed,
Blocked/DeadlineExceeded is used, and cancellation of a stale target
attempt can't commit a terminal result. The adapter must be retry-safe to
allow repeated calls and stale-attempt overlap, and an external side
effect inside the callback can't be assumed exactly-once.
Relocated terminal reply accounting uses internal command ID 46
replyRelayAck. This command only has a stable relocation ID, operation
ID, matching request-source fence (owner ID, lease generation, node RID,
node generation), and status — it doesn't carry payload or metadata. A
physical connection close isn't terminal evidence. Only the
request-source lease expiry stored in an ACK or accepted record completes
terminal accounting — there's no public ACK API.
The source only commits CAPTURED after every admitted connection-bound
work, including connection-bound one-way, reaches terminal accounting.
A durable accepted journal is only used on a source with an owner
lease. If pre-CAPTURED drain doesn't finish within the deadline,
relocation is aborted and host relocation ends with
BLOCKED/DEADLINE_EXCEEDED. Source admission isn't opened before durable
abort and source normalization finish. There's no exception that
captures a connection-bound one-way in an incomplete state.
An Actor on Entry Spot and PerActor User Spot is an independent
relocation unit. Only a SpotWide User Spot's member Actor moves the
Spot and the entire current membership together as one aggregate. User
Spot membership itself isn't a relocation blocker — that Actor unit or
the SpotWide aggregate is only blocked when even one participant
selected disableRelocation() or a compatible target can't be secured.
A participant with relocation disabled gets BLOCKED/RELOCATION_DISABLED;
absence of target/capacity/reservation gets
BLOCKED/TARGET_UNAVAILABLE. The same result applies when no target satisfies the requested
application version and registered factory/type/state-preservation-adapter eligibility. After
target selection, an incompatible transferred state schema/type adapter gets
BLOCKED/STATE_INCOMPATIBLE. The Actor unit finishes target factory and
restore, prepares the accepted journal as a staging queue the
application handler hasn't run, and then performs the NEW_OWNER CAS.
This CAS atomically changes owner, authority owner generation, and the
current Spot to the target
execution shell. Infrastructure relocation doesn't call an application
membership callback. Dispatch opens after finishing journal/queue/Actor
timer replay, source relay, and durable cleanup. There's no public phase
API that controls this order.
When creating a new distributed Actor, the framework selects one target
to become owner, and secures CREATING authority and pending capacity
together as one reservation on that target. Only the target that secured
the reservation performs factory, initial Entry membership, and
initialize. On success, the same reservation is committed with READY
and active capacity; on failure, it's aborted. A target that loses the
CAS race doesn't run a separate factory.
The Actor Join call only provides a synchronous defer(), and doesn't
provide submit(...)/yield(...). defer() only registers an
immutable Join intent and an inactive barrier on the current handler, and
doesn't start a target lookup or Store I/O. If the handler finishes
normally, the Join runs; if it fails, the barrier is discarded. The
result is delivered via the onJoinCompleted(...) Actor callback with
the same 128-bit operation ID.
Operation ID is a completion idempotency ID, not a RelocationId,
reservation ID, or aggregate commit ID. Same-node and cross-node
completion retry are limited to the current source and target process
lifetime. After the process ends, a different runtime doesn't
automatically replay completion.
The overload with no request fixes an empty ZLinkMessage. The default
timeout is 5 seconds, and an explicit value is a finite 1..Integer.MAX_VALUE
ms rounded up to milliseconds. defer() fixes a monotonic absolute
deadline.
Public Member Inventory¶
The declarations below fix this category's Java public types and members.
public interface systems.zlink.framework.actors.ZLinkActorFactory {
public abstract java.util.concurrent.CompletionStage<systems.zlink.framework.actors.ZLinkActor> create(systems.zlink.framework.actors.ZLinkActorContext);
}
public interface systems.zlink.framework.actors.ZLinkRelocationCancellation {
public abstract boolean isCancellationRequested();
}
public interface systems.zlink.framework.actors.ZLinkActorRelocationAdapter<TActor extends systems.zlink.framework.actors.ZLinkActor> {
public abstract java.util.concurrent.CompletionStage<byte[]> capture(TActor, systems.zlink.framework.actors.ZLinkRelocationCancellation);
public abstract java.util.concurrent.CompletionStage<java.lang.Void> restore(TActor, byte[], systems.zlink.framework.actors.ZLinkRelocationCancellation);
}
public interface systems.zlink.framework.actors.ZLinkActorHandlerRegistry {
public abstract void addHandler(java.lang.Class<?>);
}
public final class systems.zlink.framework.actors.ActorRef extends java.lang.Record {
public systems.zlink.framework.actors.ActorRef(java.lang.String, long, java.lang.String, systems.zlink.contracts.core.RoutingId);
public final java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public java.lang.String actorId();
public long objectGeneration();
public java.lang.String meshName();
public systems.zlink.contracts.core.RoutingId nodeRid();
}
public interface systems.zlink.framework.actors.ZLinkActor {
public abstract systems.zlink.framework.actors.ZLinkActorContext context();
public default void configure();
public default java.util.concurrent.CompletionStage<java.lang.Void> onJoinCompleted(systems.zlink.framework.actors.ZLinkActorJoinCompletion);
}
public interface systems.zlink.framework.actors.ZLinkActorClient {
public abstract systems.zlink.framework.actors.ZLinkActorSendCall sendToActor(java.lang.String, java.lang.Object);
public abstract systems.zlink.framework.actors.ZLinkActorRequestCall requestToActor(java.lang.String, java.lang.Object);
}
public interface systems.zlink.framework.actors.ZLinkActorContext {
public abstract java.lang.String actorId();
public abstract long objectGeneration();
public abstract java.lang.String meshName();
public abstract java.util.Optional<java.lang.String> spotId();
public abstract systems.zlink.framework.actors.ZLinkBoundSession boundSession();
public abstract systems.zlink.framework.actors.ZLinkActorJoinCall joinSpot(java.lang.String);
public abstract systems.zlink.framework.actors.ZLinkActorJoinCall joinSpot(java.lang.String, java.lang.Object);
public abstract systems.zlink.framework.actors.ZLinkActorJoinCall joinEntrySpot();
public abstract systems.zlink.framework.actors.ZLinkActorJoinCall joinEntrySpot(java.lang.Object);
}
public interface systems.zlink.framework.actors.ZLinkActorJoinCall {
public abstract systems.zlink.framework.actors.ZLinkActorJoinCall timeout(java.time.Duration);
public abstract void defer();
}
public final class systems.zlink.framework.actors.ZLinkActorJoinOperationId extends java.lang.Record {
public systems.zlink.framework.actors.ZLinkActorJoinOperationId(long, long);
public long high();
public long low();
}
public final class systems.zlink.framework.actors.ZLinkActorJoinCompletion$Accepted extends java.lang.Record implements systems.zlink.framework.actors.ZLinkActorJoinCompletion {
public systems.zlink.framework.actors.ZLinkActorJoinCompletion$Accepted(systems.zlink.framework.actors.ZLinkActorJoinOperationId, systems.zlink.framework.actors.ActorRef, systems.zlink.framework.messaging.ZLinkMessage);
public final java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public systems.zlink.framework.actors.ZLinkActorJoinOperationId operationId();
public systems.zlink.framework.actors.ActorRef actor();
public systems.zlink.framework.messaging.ZLinkMessage reply();
}
public final class systems.zlink.framework.actors.ZLinkActorJoinCompletion$Rejected extends java.lang.Record implements systems.zlink.framework.actors.ZLinkActorJoinCompletion {
public systems.zlink.framework.actors.ZLinkActorJoinCompletion$Rejected(systems.zlink.framework.actors.ZLinkActorJoinOperationId, systems.zlink.framework.messaging.ZLinkMessage);
public final java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public systems.zlink.framework.actors.ZLinkActorJoinOperationId operationId();
public systems.zlink.framework.messaging.ZLinkMessage reply();
}
public final class systems.zlink.framework.actors.ZLinkActorJoinCompletion$Failed extends java.lang.Record implements systems.zlink.framework.actors.ZLinkActorJoinCompletion {
public systems.zlink.framework.actors.ZLinkActorJoinCompletion$Failed(systems.zlink.framework.actors.ZLinkActorJoinOperationId, systems.zlink.framework.errors.ZLinkFrameworkErrorKind);
public systems.zlink.framework.actors.ZLinkActorJoinOperationId operationId();
public systems.zlink.framework.errors.ZLinkFrameworkErrorKind kind();
}
public sealed interface systems.zlink.framework.actors.ZLinkActorJoinCompletion
permits systems.zlink.framework.actors.ZLinkActorJoinCompletion.Accepted,
systems.zlink.framework.actors.ZLinkActorJoinCompletion.Rejected,
systems.zlink.framework.actors.ZLinkActorJoinCompletion.Failed {
}
public interface systems.zlink.framework.actors.ZLinkActorManager {
public abstract systems.zlink.framework.actors.ZLinkActorCreateCall create(java.lang.String, java.lang.String);
public abstract systems.zlink.framework.actors.ZLinkActorGetOrCreateCall getOrCreate(java.lang.String, java.lang.String);
public abstract java.util.concurrent.CompletionStage<java.util.Optional<systems.zlink.framework.actors.ActorRef>> find(java.lang.String);
public abstract java.util.concurrent.CompletionStage<java.util.Optional<systems.zlink.framework.spots.SpotRef>> findSpot(java.lang.String);
public abstract java.util.concurrent.CompletionStage<java.lang.Boolean> destroy(systems.zlink.framework.actors.ActorRef);
}
public interface systems.zlink.framework.actors.ZLinkActorCreateCall {
public abstract systems.zlink.framework.actors.ZLinkActorCreateCall inMesh(java.lang.String);
public abstract systems.zlink.framework.actors.ZLinkActorCreateCall request(java.lang.Object);
public abstract systems.zlink.framework.actors.ZLinkActorCreateCall request(systems.zlink.framework.messaging.ZLinkMessage);
public abstract systems.zlink.framework.actors.ZLinkActorCreateCall timeout(java.time.Duration);
public abstract java.util.concurrent.CompletionStage<systems.zlink.framework.actors.ZLinkActorCreateResult> submit();
public abstract java.util.concurrent.CompletionStage<systems.zlink.framework.actors.ZLinkActorCreateResult> yield();
}
public interface systems.zlink.framework.actors.ZLinkActorGetOrCreateCall {
public abstract systems.zlink.framework.actors.ZLinkActorGetOrCreateCall inMesh(java.lang.String);
public abstract systems.zlink.framework.actors.ZLinkActorGetOrCreateCall request(java.lang.Object);
public abstract systems.zlink.framework.actors.ZLinkActorGetOrCreateCall request(systems.zlink.framework.messaging.ZLinkMessage);
public abstract systems.zlink.framework.actors.ZLinkActorGetOrCreateCall timeout(java.time.Duration);
public abstract java.util.concurrent.CompletionStage<systems.zlink.framework.actors.ZLinkActorCreateResult> submit();
public abstract java.util.concurrent.CompletionStage<systems.zlink.framework.actors.ZLinkActorCreateResult> yield();
}
public sealed interface systems.zlink.framework.actors.ZLinkActorCreateResult
permits systems.zlink.framework.actors.ZLinkActorCreateResult.Existing,
systems.zlink.framework.actors.ZLinkActorCreateResult.Created,
systems.zlink.framework.actors.ZLinkActorCreateResult.Rejected {
}
public interface systems.zlink.framework.actors.ZLinkActorRequestCall {
public abstract systems.zlink.framework.actors.ZLinkActorRequestCall metadata(java.lang.String, java.lang.String);
public abstract systems.zlink.framework.actors.ZLinkActorRequestCall timeout(java.time.Duration);
public abstract <TReply> java.util.concurrent.CompletionStage<TReply> submit(java.lang.Class<TReply>);
public abstract <TReply> java.util.concurrent.CompletionStage<TReply> yield(java.lang.Class<TReply>);
}
public interface systems.zlink.framework.actors.ZLinkActorSendCall {
public abstract systems.zlink.framework.actors.ZLinkActorSendCall metadata(java.lang.String, java.lang.String);
public abstract java.util.concurrent.CompletionStage<java.lang.Void> submit();
}
public interface systems.zlink.framework.actors.ZLinkBoundSession {
public abstract systems.zlink.framework.actors.ZLinkBoundSessionSendCall send(java.lang.Object);
public abstract java.util.concurrent.CompletionStage<java.lang.Void> disconnect();
}
public interface systems.zlink.framework.actors.ZLinkBoundSessionSendCall {
public abstract systems.zlink.framework.actors.ZLinkBoundSessionSendCall metadata(java.lang.String, java.lang.String);
public abstract java.util.concurrent.CompletionStage<java.lang.Void> submit();
}
ActorId is a global logical ID of UTF-8 1..255 bytes. ActorRef
preserves ActorId, a positive signed-63-bit ObjectGeneration, and the
MeshName/NodeRid at query time. A regular message only takes ActorId and
resolves current authority. Only destroy and session bind take the
ref.
Actor Model §6.2 owns the single-use rules, duplicate-option handling, and terminal
re-invocation errors of create and getOrCreate calls.
If inMesh is
omitted and there's one object-role Mesh, it's auto-selected; with 0,
NOT_CONFIGURED; with two or more, INVALID_OPERATION. If the
specified Mesh doesn't exist, NOT_FOUND. The caller doesn't specify a
target RID or placement callback. find and findSpot only return the
current Ready ref, and don't provide a directory or resolver.
create returns ALREADY_EXISTS if a Ready Actor exists, and a new
attempt returns Created or Rejected. getOrCreate returns a Ready
Actor of the same type as Existing, without a callback. If Creating, it
waits for the authority change, and a CAS loser doesn't start a separate
factory or callback. A different operation receives Existing after
Ready, competes for a new reservation after cleanup, and doesn't share an
earlier application reply. Only a resend of the same source Node
RID/lifecycle generation/OperationId reads the correlation-free
creation-operation-terminal-v1 envelope and re-encodes the reply with
the current correlation/reply route. The terminal is kept for 5 minutes
after the original deadline. A callback exception isn't Rejected — it's
a typed creation failure.
ActorRef.objectGeneration() is 1..Long.MAX_VALUE. Typed JSON uses
the required properties actorId, objectGeneration, meshName,
nodeRid, and generation is encoded as a decimal string with no leading
zero. An unknown property, duplicate property, missing required
property, a non-numeric token, and an out-of-range value are rejected.
yield(...) declared on an Actor request is only valid while the
current Actor handler is running on a SpotWide User Spot's shared
execution gate. If called by an Entry Spot Actor or a PerActor User
Spot's Actor, it completes with INVALID_OPERATION, without submitting
the operation or returning the turn. Actor Join is only registered with
defer() inside the current handler, and doesn't provide submit(...)
and yield(...).