콘텐츠로 이동

Java Actor 공개 인터페이스

인터페이스 목차 · Actor 공통 계약

Bound Session의 relocation route 갱신은 Session–Actor binding §8.2가 소유한다.

이 문서는 Java에서 Actor factory, context, messaging, manager와 relocation adapter를 표현하는 공개 인터페이스를 고정한다. 일반 message는 ActorId로 대상을 지정하고, 특정 incarnation을 변경하는 operation은 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);
}

Factory registration의 정확한 builder member는 구성과 host가 소유한다. Cross-node relocation 동작은 Actor factory configure callback에 직접 연결한다. Runtime은 factory가 반환한 Actor를 명시한 actorClass로 검사해 type 불일치를 startup 오류로 반환한다. Factory와 분리된 relocation registry는 제공하지 않는다. preserveStateWith(...)adapterClass는 해당 Actor type의 ZLinkActorRelocationAdapter<TActor>를 구현해야 한다. User·Instance Spot policy의 adapter type 검증은 Spot 인터페이스가 소유한다. Class<?>를 받는 것은 Java type erasure 때문에 policy value를 공통으로 유지하기 위한 표현이며, Framework는 factory type과 adapter generic target이 일치하는지 socket bind 전에 검사한다. Mismatch는 startup configuration error다. preserveStateWith(null)은 callback 실행 중 configuration error로 거부한다.

Actor adapter는 application state를 opaque byte[]로 capture·restore하며 relocation adapter 전용 size 상한을 두지 않는다. Framework는 payload를 relocationPayloadChunkLimitBytes 이하의 chunk로 나눠 source–target ordered mesh 연결로 직접 전송한다. Source memory가 복원 원본이며 handoff payload를 Relocation Store에 저장하지 않는다. Public state DTO, TState, stateContractId, state class와 ZLinkMessage를 relocation surface에 두지 않는다. Framework는 capture가 정상 완료한 배열을 즉시 복사한다. Capture가 반환한 배열은 adapter가 계속 소유하며 completion 뒤 재사용하거나 변경해도 보존한 payload가 바뀌지 않는다. Restore에는 호출마다 보존한 payload의 fresh defensive copy를 전달하고 adapter는 stage가 끝난 뒤 그 배열을 보관하지 않는다. 길이가 0인 배열도 유효한 보존 state이며 recreateOnRelocation()을 선택한 것으로 해석하거나 restore를 생략하지 않는다. Adapter는 owner claim, relocation envelope, generation과 recovery phase를 받지 않는다.

Cross-node materialization에서 Actor factory가 preserveStateWith(...)를 사용하면 maintenance Actor relocation, remote User·Entry Spot join과 whole User Spot relocation의 각 Actor participant에 같은 Actor adapter를 사용한다. Same-node join과 disableRelocation() 또는 recreateOnRelocation()을 선택한 factory에서는 adapter를 호출하지 않는다. recreateOnRelocation()은 application state를 capture하지 않으므로 adapter가 없다.

Target은 restore와 accepted journal staging을 끝낸 뒤 owner를 commit한다. Lifecycle callback 뒤 저장된 기존 작업을 실제 Actor queue에 먼저 넣고 relocation temporary queue 작업을 그 뒤에 옮긴다. Temporary queue 등록을 제거하고 dispatch를 atomic하게 전환한 뒤 target을 READY로 연다. Source cleanup, COMPLETED 기록은 target message 처리를 막지 않는다. READY 뒤 target process가 종료되면 ordinary owner loss로 처리하며 이전 relocation을 자동 replay하지 않는다. 이 barrier를 조작하는 public phase API는 제공하지 않는다.

같은 source와 target process 안의 재시도에서 factory와 restore(...)를 두 번 이상 호출할 수 있다. capture(...)authority commit 전에 반복될 수 있다. Current owner와 attempt fence만 completion을 commit하고 admission을 열 수 있다. Callback에는 relocation ID를 추가하지 않으므로 application restore와 capture는 retry-safe해야 하며 exactly-once external side effect를 보장하지 않는다. Factory는 target attempt마다 fresh Actor instance를 만들고 Framework는 그 attempt의 restore(...)만 해당 instance에 호출한다. Source instance나 이전 target attempt의 instance를 새 attempt에 재사용하지 않으며 같은 attempt에서는 restore가 반복될 수 있다.

Capture stage가 exception으로 끝나면 authority publication 전에 attempt를 abort하고 source authority와 admission을 유지한다. Restore stage가 exception으로 끝나면 target admission을 sealed 상태로 유지하고 같은 target process에서 동일한 payload로 다시 시도할 수 있다. 다른 target을 자동 선택하지 않는다. Exception을 빈 payload나 정상 completion으로 바꾸지 않는다. Capture의 null stage와 null byte[], Restore의 null stage는 adapter contract 위반이다. Host relocation에서 deadline이 먼저 확정되지 않은 precommit adapter exception과 contract 위반은 Blocked/StateIncompatible로 분류한다. Deadline이 먼저 확정되면 Blocked/DeadlineExceeded를 사용하며 stale target attempt의 cancellation은 terminal result를 commit하지 못한다. Adapter는 반복 호출과 stale attempt overlap을 허용하도록 retry-safe해야 하며 callback 안의 외부 side effect를 exactly-once로 간주할 수 없다.

Relocated terminal reply accounting은 internal command ID 46 replyRelayAck를 사용한다. 이 command는 stable relocation ID, operation ID, 일치하는 request-source fence(owner ID, lease generation, node RID, node generation)와 status만 가지며 payload와 metadata를 싣지 않는다. Physical connection close는 terminal 증거가 아니다. ACK 또는 accepted record에 저장한 일치하는 request-source lease expiry만 terminal accounting을 완료하며 public ACK API는 없다.

Source는 connection-bound one-way를 포함해 admission한 모든 connection-bound work가 terminal accounting에 도달한 뒤에만 CAPTURED를 commit한다. Durable accepted journal은 일치하는 owner lease가 있는 source에서만 사용한다. Pre-CAPTURED drain이 deadline 안에 끝나지 않으면 relocation을 abort하고 host relocation을 BLOCKED/DEADLINE_EXCEEDED로 끝낸다. Durable abort와 source normalization이 끝나기 전에 source admission을 열지 않는다. Connection-bound one-way를 미완료 상태로 capture하는 예외는 없다.

Entry Spot과 PerActor User Spot의 Actor는 독립된 relocation unit이다. SpotWide User Spot member Actor만 Spot과 current member 전체를 하나의 aggregate로 함께 옮긴다. User Spot membership 자체는 relocation blocker가 아니며 participant 하나라도 disableRelocation()을 선택했거나 호환 target을 확보할 수 없을 때만 해당 Actor unit 또는 SpotWide aggregate를 차단한다. Relocation을 비활성화한 participant는 BLOCKED/RELOCATION_DISABLED다. 요청한 application version과 등록 factory/type·state 보존 adapter eligibility를 만족하는 target·capacity·reservation 부재는 BLOCKED/TARGET_UNAVAILABLE이다. Target 선택 뒤 전달한 state schema/type adapter 불일치는 BLOCKED/STATE_INCOMPATIBLE다. Actor unit은 target factory와 restore를 끝내고 accepted journal을 application handler가 실행하지 않은 staging queue로 준비한 뒤 NEW_OWNER CAS를 수행한다. 이 CAS는 owner, authority owner generation과 current Spot을 target execution shell로 원자적으로 바꾼다. Infrastructure relocation은 application membership callback을 호출하지 않는다. Journal·queue·Actor timer replay, source relay와 durable cleanup을 끝낸 뒤 dispatch를 개방한다. 이 순서를 제어하는 public phase API는 없다.

새 distributed Actor를 만들 때 Framework는 owner가 될 target 하나를 선택하고, 그 target에서 CREATING authority와 pending capacity를 하나의 reservation으로 함께 확보한다. Reservation을 확보한 target만 factory, initial Entry membership과 initialize를 수행한다. 성공하면 같은 reservation을 READY와 active capacity로 commit하고 실패하면 abort한다. CAS 경쟁에서 진 target은 별도 factory를 실행하지 않는다.

Actor Join call은 동기 defer()만 제공하며 submit(...)·yield(...)를 제공하지 않는다. defer()는 current handler에 immutable Join intent와 비활성 barrier만 등록하며 target 조회나 Store I/O를 시작하지 않는다. Handler가 정상적으로 끝나면 Join을 실행하고 실패하면 barrier를 폐기한다. 결과는 같은 128-bit operation ID의 onJoinCompleted(...) Actor callback으로 전달한다.

Operation ID는 completion idempotency ID이며 RelocationId, reservation ID나 aggregate commit ID가 아니다. Same-node와 cross-node completion retry는 current source와 target process lifetime으로 제한한다. Process 종료 뒤 다른 runtime이 completion을 자동 replay하지 않는다.

Request 없는 overload는 empty ZLinkMessage를 고정한다. Timeout 기본값은 5초이고 명시 값은 millisecond 올림 기준 유한한 1..Integer.MAX_VALUE ms다. defer()에서 monotonic absolute deadline을 고정한다.

public member inventory

아래 선언은 이 category의 Java public type과 member를 고정한다.

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는 UTF-8 1..255 bytes의 global logical ID다. ActorRef는 ActorId, positive signed-63-bit ObjectGeneration과 조회 시점의 MeshName·NodeRid를 보존한다. 일반 message는 ActorId만 받고 current authority를 resolve한다. Destroy와 session bind만 지정한 ref를 받는다.

creategetOrCreate call의 single-use, 중복 option과 terminal 재호출 오류는 Actor 모델 §6.2가 소유한다.

inMesh 생략 시 object-role Mesh가 하나면 자동 선택하고 0개이면 NOT_CONFIGURED, 둘 이상이면 INVALID_OPERATION이다. 명시한 Mesh가 없으면 NOT_FOUND다. Caller는 target RID나 placement callback을 지정하지 않는다. findfindSpot은 current Ready ref만 반환하며 directory와 resolver를 제공하지 않는다.

create는 Ready Actor가 있으면 ALREADY_EXISTS이며 새 attempt에서는 Created 또는 Rejected를 반환한다. getOrCreate는 같은 type의 Ready Actor를 callback 없이 Existing으로 반환한다. Creating이면 authority 변경을 기다리며 CAS loser는 별도 factory나 callback을 시작하지 않는다. 서로 다른 operation은 Ready 뒤 Existing을 받고 cleanup 뒤 새 reservation을 경쟁하며 앞선 application reply를 공유하지 않는다. 같은 source Node RID·lifecycle generation·OperationId의 재전송만 correlation-free creation-operation-terminal-v1 envelope를 읽고 현재 correlation·reply route로 reply를 다시 encode한다. Terminal은 original deadline 뒤 5분 동안 유지한다. Callback exception은 Rejected가 아니라 typed creation failure다.

ActorRef.objectGeneration()1..Long.MAX_VALUE다. Typed JSON은 required property actorId, objectGeneration, meshName, nodeRid를 사용하며 generation은 leading-zero 없는 decimal string으로 encode한다. Unknown property, duplicate property, required property 누락, 숫자 token과 범위 밖 값은 거부한다.

Actor request에 선언된 yield(...)는 현재 Actor handler가 SpotWide User Spot의 shared execution gate에서 실행 중일 때만 유효하다. Entry Spot Actor와 PerActor User Spot의 Actor가 호출하면 operation을 제출하거나 turn을 반환하지 않고 INVALID_OPERATION으로 완료한다. Actor Join은 현재 handler 안에서 defer()로만 등록하며 submit(...)yield(...)를 제공하지 않는다.