TL;DR — PrivateServer에서 메시지 게시와 Actor 실행을 연결하던 스케줄링 절차를 ToolKit의 공용 Execution 레이어로 정리했습니다.
Table of contents
Open Table of contents
들어가며
Actor 스케줄링 글에서는 메시지가 메일박스에 들어갔어도 실행 예약이 남지 않으면 처리되지 않는 lost wakeup을 다뤘습니다. PrivateServer에서는 Schedule Gate와 runnable permit으로 메시지 접수와 Actor의 실행 종료를 조정했습니다.
이후 Service Host를 만들면서, 이러한 실행 절차를 특정 Actor 구현에 종속되지 않는 공통 도구로 제공하려고 했습니다. 이 글은 그 과정에서 Akka의 구조를 참고하고, 작업을 보관하고 직렬로 소비하는 Work Lane과 이를 실행하는 Scheduler를 구성한 내용을 정리합니다.
이 글에서 다루는 내용:
- 기존 Actor 스케줄링과 ToolKit의 추상화 경계
- Akka에서 참고한 메일박스와 실행 예약의 관계
- Work Lane, Scheduler, Worker Pool의 내부 구성과 실행 흐름
Item, lane 선택,invoke로 사용자 로직을 연결하는 방법
사전 지식: 작업 큐, 스레드 풀, mutex와 C++ 함수 포인터에 대한 기본 이해를 전제로 합니다.
1. 기존 Actor 스케줄링과 공통화할 책임
1.1 메시지 접수와 실행 예약의 연결
PrivateServer에서는 IOCP Worker가 I/O 결과를 이벤트로 전달하고, Actor Worker가 해당 Actor의 메일박스를 소비합니다. 이때 ready queue에 들어가는 것은 개별 이벤트가 아니라 해당 Actor를 실행할 기회를 나타내는 token입니다.
flowchart TB
Producer["IOCP Worker
I/O 결과를 이벤트로 전달"]
subgraph Scheduling["Actor Scheduler"]
Admission["접수 처리
admission · mailbox 게시
예약 판단"]
Permit["Runnable permit
실행 허가 확보 · 반환"]
Ready["Ready Queue
Actor 식별 token"]
end
subgraph ActorUnit["Actor별 구성"]
Mailbox["Actor Mailbox
이벤트 보관"]
Gate["Schedule Gate
접수 중 producer · 실행 상태
drain 종료와 재예약 조정"]
Actor["Actor
이벤트 소비 · 상태 변경"]
end
Worker["Actor Worker
Ready token 소비"]
Executor["Actor Executor
drain 실행권 확인"]
Producer --> Admission
Admission --> Mailbox
Admission -.-> Permit
Admission -.-> Gate
Admission -->|"필요한 실행 예약"| Ready
Ready --> Worker
Worker --> Executor
Executor -.->|"실행 시작과 종료"| Gate
Executor --> Actor
Mailbox --> Actor
Executor -.->|"소비 결과"| Admission
실행 권한이 없는 Actor는 Mailbox에 이벤트를 게시하기 전에 permit을 먼저 확보해야 합니다. permit을 얻지 못하면 이벤트 게시 시도를 할 수 없고, 게시가 실패하면 획득한 permit 역시 반환해야 합니다. 이미 permit을 얻어 실행 중이거나 예약된 Actor는 별도의 permit 획득 절차가 필요하지 않습니다.
Schedule Gate는 producer의 이벤트 게시와 Actor Worker의 drain 종료를 함께 관리합니다. Worker가 소비를 마쳤더라도 producer의 게시가 아직 확정되지 않았다면 종료 판단을 보류하고, 마지막 producer의 게시 결과를 바탕으로 Worker의 예약을 최종 결정하게 됩니다.
그리고 이러한 상태 전이를 atomic 변수 기반의 CAS로 조정하여 producer의 생산과 Actor Worker의 실행 기회가 분리되지 않도록 구성했습니다.
1.2 Actor 관리와 실행 절차의 분리
기존 구조에서는 Actor의 실행 권한을 획득하기 위한 permit, Actor 단위의 스케줄링, 그리고 Actor Worker가 작업에 사용하는 Mailbox 등 producer의 생산 시점과 Worker의 소비 시점을 하나로 연결하기 위해 다뤄야 하는 복잡한 규칙들이 산재해 있었습니다.
그래서 ToolKit의 사용자는 메시지/작업을 생산하고 게시하면, 이후 실행 예약과 실제 작업 수행은 ToolKit이 제공하는 공통 실행 계층이 담당하는 경계를 만들고자 했습니다. 이를 통해 사용자는 생산한 메시지와 작업의 게시와, 그 작업을 어떤 방식으로 처리할지만 정의하면 공통 실행 계층이 제공하는 스케줄러가 이를 lost wakeup이 없도록 처리하여 예약과 실행을 할 수 있게 만드는 것입니다.
2. Akka에서 참고한 구조와 Execution 레이어
2.1 메일박스가 관리하는 실행 예약 상태
이 책임을 어떻게 나눌지 조사하면서 Akka의 Mailbox와 Dispatcher 구현을 참고했습니다. Akka의 메일박스에는 메시지 큐뿐 아니라 실행 예약 상태가 포함되어 있습니다.
Dispatcher는 메시지를 메일박스에 넣은 뒤 실행 등록을 시도합니다. 메일박스를 예약 상태로 바꾸는 데 성공한 경우 executor에 제출하며, 메일박스 실행이 끝나면 idle 상태로 전환한 뒤 다시 실행 등록을 요청합니다. 메시지 보관과 예약 상태는 메일박스에, 실행 제출은 Dispatcher에 배치되어 있습니다. Akka Mailbox, Dispatcher
기존 구조와 차이는 스케줄링에 필요한 상태를 어디에 배치하느냐입니다. 기존에는 Producer의 게시 경로에서 Scheduler가 별도의 Gate와 permit을 조합해 예약을 처리했습니다. Akka에서는 메일박스가 예약 상태를 관리하고, Dispatcher가 그 상태를 바탕으로 실행을 제출합니다.
Akka의 Actor 수명 관리나 상태 전이를 모두 반영할 필요는 없지만, 별도의 실행 계층과 메일박스에서 자체적으로 상태 관리를 한다는 점은 기존 구조에서 발생하는 복잡한 상태 관리와 스케줄링을 추상화할 수 있는 좋은 레퍼런스라 생각했습니다.
2.2 ToolKit에서 다시 구성한 실행 단위
Akka에서 자체적으로 상태를 관리하는 메일박스를 ToolKit에서는 Work Lane으로 만들었습니다. Lane은 MPMC 큐를 가지고 있고, Worker가 Lane을 점유해 이를 직렬화해 소비하는 단위입니다.
flowchart TB
subgraph User["사용하는 쪽"]
Model["Actor · 서비스 상태
생성 · 조회 · 수명 관리"]
Producer["Producer
Item 작성 · lane 선택"]
Logic["invoke
사용자 로직 실행"]
end
subgraph Execution["ToolKit Execution 레이어"]
Scheduler["Worker Scheduler
게시 접수 · 실행 예약 · 종료"]
Lane["Work Lane
작업 큐 · 예약 상태 · 직렬 소비"]
Pool["Worker Pool
Ready Queue · Worker Thread"]
end
Producer -->|"TryPost(handle, item)"| Scheduler
Scheduler -->|"게시 · 소비 요청"| Lane
Lane -.->|"예약 필요 여부"| Scheduler
Scheduler -->|"lane 실행 작업 제출"| Pool
Pool -->|"워커에서 소비 경로 실행"| Scheduler
Lane -->|"워커에서 callback 호출"| Logic
Logic --> Model
바뀐 구조에서도 핵심 조건은 같습니다. 생산자가 게시한 작업이 소비자에게 안전하게 전달되어야 하고, 각 Work Lane은 병렬로 실행될 수 있지만 내부에 들어간 로직은 순차적으로 실행되어야 하는 것입니다. ToolKit에서는 이 조건을 기존 Actor 단위에서 Work Lane과 이를 관리하는 스케줄러 사이의 계약으로 만들었습니다.
| 구분 | PrivateServer | ToolKit Execution 레이어 |
|---|---|---|
| 실행 단위 | Actor와 그 mailbox | 사용자가 작업을 묶는 Work Lane |
| 게시와 예약의 연결 | Actor Scheduler가 Gate·permit·mailbox 결과를 조합 | Scheduler가 lane 게시와 예약 판단 결과를 연결 |
| 예약 상태 관리 | 별도의 Schedule Gate | Work Lane 내부 상태와 mutex |
| Ready 용량 관리 | 새로 runnable이 되는 Actor의 permit 확보·반환 | 고정 lane 집합과 lane당 예약 제한에 맞춰 용량 확보 |
| 실제 로직 | Actor의 이벤트 처리 | lane에 등록한 invoke(context, item) |
3. Scheduler 내부의 소유 관계
3.1 Work Lane과 Worker Pool의 구성
TkWorkerScheduler<T>는 생성 시 등록한 모든 Work Lane과 Worker Pool 하나를 소유합니다. Scheduler 안의 lane은 같은 타입 파라미터 T, 같은 lane 용량, 같은 drain budget을 사용하고, 각 lane에는 사용자 context와 invoke callback을 등록합니다.
flowchart TB
Handle["Producer의 Lane Handle
Scheduler 식별 · lane slot"]
subgraph Scheduler["TkWorkerScheduler"]
Control["공통 제어
handle 검사 · 실행 예약
drain budget · 종료 조정"]
subgraph Entries["Lane Entries"]
LaneA["Lane Entry A
Work Lane 소유
queue · mutex · 실행 상태
context 참조 · invoke"]
LaneB["Lane Entry B
Work Lane 소유
queue · mutex · 실행 상태
context 참조 · invoke"]
end
subgraph Pool["TkWorkerPool"]
Ready["Bounded Ready Queue
lane을 소비할 WorkItem"]
Workers["Worker Threads
Ready 작업을 꺼내 실행"]
Ready --> Workers
end
Control --> LaneA
Control --> LaneB
LaneA ~~~ Ready
LaneB ~~~ Ready
Control -->|"실행 예약"| Ready
end
Handle --> Control
그림에는 두 종류의 큐가 있습니다. Lane 내부의 큐는 사용자 작업 T를 보관하고, Worker Pool 내부의 ready queue는 그 lane을 소비할 TkWorkItem을 보관합니다.
- 작업은 Lane의 큐에 들어갑니다.
- 스케줄러의 실행 예약은 Worker Pool 내부의 ready queue에 들어갑니다.
Worker는 ready queue에서 가져온 TkWorkItem을 실행합니다. 이 WorkItem의 callback은 Scheduler의 DrainLane()으로 연결되고, Scheduler가 대상 lane의 Drain()에 사용자 invoke와 context를 전달합니다. 사용자 callback은 TkWorkerSchedulerCreateInfo<T>의 workLanes로 전달하는 TkWorkLaneCreateInfo<T>에 등록합니다.
Worker Pool이 호출하는 것은 lane을 소비하기 위한 WorkItem의 callback이고, 사용자 invoke(context, item)는 Work Lane의 Drain()이 각 Item에 대해 호출합니다. 따라서 Worker Pool은 작업 타입 T의 내용이나 사용자 로직을 알 필요 없이 공통 WorkItem을 실행합니다.
3.2 Permit 관리 비용 제거
기존 구조에서는 새 실행 허가가 필요한 Actor가 메시지를 게시하기 전에 runnable permit을 확보했습니다. Permit은 Actor의 종류가 아니라 동시에 runnable 상태로 유지할 Actor의 수를 제한하여 Ready Queue의 예약 용량을 관리했습니다. 게시가 실패하거나 실행 허가가 더 이상 필요하지 않으면 permit을 반환했습니다.
ToolKit은 이러한 관리 절차를 줄이기 위해 Ready Queue에 예약하는 대상을 Work Lane으로 제한합니다. 생성 시 lane 집합을 고정하고, lane당 중복 예약을 제한하며, ready queue 용량을 등록 lane 수 이상으로 확보합니다. 이 조건을 결합하여 실행 중 등록된 lane의 예약이 ready queue 용량 때문에 실패하지 않도록 구성하므로, Actor마다 별도의 permit을 확보하고 반환할 필요가 없어집니다.
Ready Queue에 특정 Lane에 대한 작업 예약을 걸면, Worker Pool에 있는 Worker가 대기를 하고 있다 신호를 받아 특정 Lane에 대한 작업을 점유하게 됩니다. Worker가 Lane을 점유해 작업을 진행하면 기존 Actor Worker와 마찬가지로 invoke를 통해 실행되는 작업은 개념적으로 직렬화되어 처리되고, 다른 Actor의 작업이 들어와도 Lane 내부에 들어온 작업은 함께 처리할 수 있게 됩니다.
3.3 상태 변경과 사용자 로직의 실행 경계
Work Lane은 Idle, Scheduled, Draining 상태를 일반 변수로 가지고, lane 내부 mutex로 보호합니다.
| 상태 | 의미 |
|---|---|
Idle | 진행 중인 소비와 다음 실행 예약 책임이 없습니다. |
Scheduled | 다음 실행을 예약했거나, Scheduler가 예약을 제출할 책임을 확보했습니다. |
Draining | 한 실행자가 lane의 작업을 소비하고 있습니다. |
Scheduled는 해당 lane을 소비할 WorkItem이 ready queue에 제출된 상태뿐 아니라, 실제 제출 전이라도 Scheduler가 제출 책임을 확보한 상태를 포함합니다. 따라서 Scheduled 상태인 lane에 다른 producer가 새로운 Item을 게시해도 중복 예약을 만들지 않습니다.
게시와 예약 판단, drain 시작의 실행권 확보, drain 종료 후 잔여 작업 판단은 mutex 안에서 수행합니다. 반면 큐에서 작업을 꺼내고 invoke를 실행하는 동안에는 lane mutex를 잡지 않습니다. Draining 상태가 두 번째 소비자의 진입을 막으므로, 사용자 로직 전체를 mutex로 감싸지 않아도 같은 lane의 callback 실행은 직렬화됩니다.
4. Producer와 Worker를 연결하는 실행 흐름
4.1 게시와 lane 실행
Producer는 lane handle을 선택하여 TryPost()를 호출합니다. Scheduler는 handle과 종료 상태를 확인하고, 해당 lane에 작업을 게시합니다. Lane이 Idle이었다면 Scheduled로 바꾸고 예약 필요 여부를 반환합니다.
다음 그림의 Scheduler와 Work Lane은 별도 스레드가 아니라 논리적인 구성요소입니다. 앞부분의 게시 경로는 producer 스레드에서, 뒤쪽의 소비 경로는 풀의 worker 스레드에서 실행됩니다.
%%{init: {"sequence": {"actorMargin": 25, "width": 100, "messageMargin": 30, "mirrorActors": false}}}%%
sequenceDiagram
participant P as Producer
participant S as Scheduler
participant L as Work Lane
participant Q as Worker Pool
participant W as Worker
P->>S: TryPost(handle, item)
S->>L: TryPublish(item)
Note over L: mutex 안에서 게시와 예약 판단
L-->>S: 접수 결과 · 예약 필요 여부
opt 새 실행 예약 필요
S->>Q: lane 소비 WorkItem 제출
Q-->>W: 대기 중인 worker 깨우기
end
S-->>P: 접수 결과
W->>Q: Ready WorkItem 가져오기
Q-->>W: lane 소비 WorkItem
W->>S: WorkItem callback → DrainLane
S->>L: Drain(budget, context, invoke)
Note over L: mutex 안에서 Scheduled → Draining
loop budget 범위에서 작업 소비
Note over L,W: lane mutex 밖에서 Item을 꺼내 invoke 실행
end
Note over L: mutex 안에서 잔여 작업 확인과 상태 확정
L-->>S: 소비 결과 · 재예약 필요 여부
opt 남은 작업으로 재예약 필요
S->>Q: 다음 lane 소비 WorkItem 제출
end
S-->>W: 소비 경로 반환
그림에서는 이해를 위해 접수 결과 반환 뒤에 소비를 배치했습니다. 실제로는 예약을 제출한 직후부터 다른 worker가 실행할 수 있으므로, TryPost()의 반환보다 invoke가 먼저 시작할 수도 있습니다. 또한 접수 성공은 Worker의 작업 완료와는 다른 의미입니다.
Worker Pool은 ready queue의 대기 조건과 알림을 관리하고, 가져온 WorkItem은 pool mutex 밖에서 실행합니다. 같은 lane은 한 번에 하나의 소비 흐름을 가지지만, 서로 다른 lane은 사용 가능한 worker에 따라 병렬로 실행될 기회를 얻습니다.
4.2 게시와 drain 종료의 예약 책임
현재 구현은 lost wakeup을 막기 위해 producer의 게시와 예약 판단 및 worker의 소비 종료와 다음 상태 판단을 같은 lane mutex 안에서 수행합니다.
| 먼저 확정되는 동작 | 후속 예약을 책임지는 경로 |
|---|---|
Producer가 Draining 상태에서 작업 게시를 마칩니다. | Producer는 추가 예약하지 않습니다. Worker가 종료 판단에서 남은 작업을 확인하면 Scheduled로 바꾸고 Scheduler가 재예약합니다. 이미 현재 drain에서 소비했다면 추가 예약이 필요 없습니다. |
Worker가 빈 큐를 확인하고 Idle을 확정합니다. | 이후 producer가 작업을 게시하면서 Idle → Scheduled로 바꾸고 Scheduler가 예약합니다. |
Producer가 게시 중이면 worker는 같은 mutex를 얻을 때까지 기다린 뒤에 최종적인 종료 판단을 합니다. 반대로 worker가 먼저 종료 판단을 마치면 producer는 그 결과를 바탕으로 최종 상태를 결정합니다.
4.3 Drain budget과 다음 실행 기회
maxMessagesPerDrain은 한 번의 lane 소비에서 실행할 최대 작업 수입니다. Budget을 채웠는데 작업이 남아 있으면 현재 실행을 마치고 다음 lane 소비 WorkItem을 ready queue에 제출합니다. 이 사이에 다른 lane의 예약도 worker를 얻을 기회가 생깁니다.
Budget은 작업 개수 기준입니다. invoke 하나가 오래 걸리면 그 실행을 중간에 끊지 않으므로, 특정 Worker의 작업 시간을 제한하지는 않습니다.
5. Item과 lane 선택으로 사용자 로직 연결하기
5.1 데이터와 처리 함수의 정의
다음은 고정된 Actor 8개에 이벤트를 전달하는 예제입니다.
#include <pstk/execution/TkWorkerScheduler.hpp>
#include <array>
#include <cstddef>
#include <memory>
#include <utility>
constexpr std::size_t ActorCount = 8;
constexpr std::size_t LaneCount = 2;
struct ActorEvent
{
std::size_t actorIndex;
int value;
};
struct ExampleActor
{
int latestValue = 0;
void Apply(const ActorEvent& event) noexcept
{
latestValue = event.value;
}
};
struct ActorStore
{
std::array<ExampleActor, ActorCount> actors;
};
void InvokeActorEvent(void* rawContext, ActorEvent&& event) noexcept
{
ActorStore& store = *static_cast<ActorStore*>(rawContext);
store.actors[event.actorIndex].Apply(event);
}
ActorEvent가 lane에 저장할 Item입니다. ToolKit은 이 타입의 필드를 해석하지 않고, 등록한 invoke에 전달합니다. Actor의 상태 변경은 ExampleActor::Apply()에 남아 있습니다.
5.2 Lane 구성과 이벤트 게시
이 예제에서는 같은 Actor의 모든 이벤트를 actorIndex % LaneCount로 선택한 lane에 보냅니다. Actor 0·2·4·6은 lane 0에서, Actor 1·3·5·7은 lane 1에서 실행됩니다.
TkResult RunExample() noexcept
{
using Scheduler = pstk::execution::TkWorkerScheduler<ActorEvent>;
using LaneInfo = pstk::execution::TkWorkLaneCreateInfo<ActorEvent>;
using LaneHandle = pstk::execution::TkWorkLaneHandle<ActorEvent>;
ActorStore store;
const std::array<LaneInfo, LaneCount> laneInfos = {{
{&store, &InvokeActorEvent},
{&store, &InvokeActorEvent},
}};
pstk::execution::TkWorkerSchedulerCreateInfo<ActorEvent> info{};
info.workerCount = 2;
info.readyCapacity = 2;
info.workLaneCapacity = 64;
info.maxMessagesPerDrain = 8;
info.workLanes = laneInfos.data();
info.workLaneCount = laneInfos.size();
std::unique_ptr<Scheduler> scheduler;
TkResult result = Scheduler::Create(info, &scheduler);
if (result != TK_SUCCESS)
{
return result;
}
std::array<LaneHandle, LaneCount> handles;
for (std::size_t index = 0; index < handles.size(); ++index)
{
result = scheduler->GetWorkLaneHandle(index, &handles[index]);
if (result != TK_SUCCESS)
{
return result;
}
}
for (std::size_t actorIndex = 0; actorIndex < ActorCount; ++actorIndex)
{
ActorEvent event{actorIndex, 100};
const std::size_t laneIndex = actorIndex % LaneCount;
result = scheduler->TryPost(handles[laneIndex], std::move(event));
if (result != TK_SUCCESS)
{
return result;
}
}
return scheduler->Stop(pstk::execution::TkWorkerPoolStopMode::Drain);
}
Lane 구성 이후 producer가 작업마다 지정하는 것은 handle과 이벤트입니다. Gate를 조회하거나 permit을 확보하거나 ready queue에 별도로 예약을 넣는 코드는 없습니다.
두 lane은 같은 ActorStore를 참조하지만, 예제에서는 Actor 배열을 고정하고 각 Actor를 항상 같은 lane으로 보냅니다. 따라서 서로 다른 lane의 callback은 서로 다른 Actor 상태를 변경합니다. 실제 저장소가 가변 컨테이너라면 Actor별 상태뿐 아니라 컨테이너의 동시 조회·삽입·제거 규칙도 사용하는 쪽에서 정해야 합니다. 외부 입력의 Actor 식별자를 검증하는 일 역시 사용자 코드의 책임입니다.
5.3 Work Lane의 실행 보장과 사용자 로직
Work Lane은 한 번에 하나의 worker만 실행권을 얻어 해당 lane의 작업을 직렬로 처리하도록 합니다. 실행권을 얻은 worker는 각 Item을 lane에 등록된 invoke에 전달하고, 사용자 context와 함께 로직을 실행합니다.
Item에 어떤 작업을 담을지, invoke와 context를 통해 어떤 로직을 실행할지는 사용하는 쪽에서 결정합니다. 앞선 Actor 예제도 이 실행 보장을 사용하는 한 가지 구성입니다.
6. 추상화 이후의 사용자 책임과 종료 처리
Execution 레이어가 공통으로 제공하는 부분과 사용하는 쪽이 결정하는 부분은 다음과 같이 정리됩니다.
| 사용하는 쪽이 결정 | ToolKit이 제공 |
|---|---|
Item에 담을 데이터와 소유권 | 작업 보관과 용량 제한 |
| 어떤 작업들을 같은 lane에 보낼지 | 같은 lane의 직렬 실행 |
| Actor·서비스 상태의 생성·조회·수명 | 서로 다른 lane의 병렬 실행 기회 |
invoke에서 실행할 로직 | drain budget과 실행 예약 |
| 접수 실패의 처리 정책과 종료 방식 선택 | Drain / Discard에 따른 작업·worker 정리 |
Scheduler는 context가 가리키는 사용자 객체를 소유하지 않습니다. Item에 담은 포인터나 byte view 역시 원래 데이터의 수명을 자동으로 늘리지 않습니다. 앞선 예제는 데이터를 이벤트 값으로 보관하고, ActorStore를 Scheduler보다 먼저 생성하여 Scheduler가 종료될 때까지 유지합니다.
T는 예외를 던지지 않는 move 생성·move 대입·소멸을 지원해야 합니다. 게시 중 move는 lane mutex 안에서 실행되므로, 같은 lane으로 재진입하지 않는 단순한 전달 동작으로 유지해야 합니다. invoke는 noexcept callback이고, 처리 실패에 대한 보고나 응답은 사용자 로직이 정의합니다.
종료 절차도 공용 실행 계층에서 처리합니다.
| 종료 방식 | 처리 |
|---|---|
Stop(Drain) | 새 게시를 닫고 진행 중인 게시를 기다립니다. 접수된 작업을 모두 소비하여 lane들이 비고 Idle이 된 뒤 풀의 worker를 종료합니다. |
Stop(Discard) | 새 게시를 닫고 진행 중인 게시를 기다립니다. Worker를 join한 뒤 남은 ready 작업과 lane 작업을 차례로 파괴합니다. |
Discard도 이미 시작한 사용자 callback을 강제로 중단하지 않습니다. 진행 중인 lane 소비가 반환할 때까지 기다리며, 그 소비는 현재 budget 범위의 작업을 더 실행할 수 있습니다. 사용자 callback 안에서 같은 Scheduler의 Stop()을 호출하는 것은 자신을 기다리는 교착을 피하기 위해 거절합니다. 종료한 Scheduler는 다시 시작하지 않습니다.
이 구조에서 사용하는 쪽이 직접 작성하는 코드는 무엇을 전달하고, 어디까지 직렬화하고, 어떤 로직을 실행할지에 모입니다. 게시·실행·종료를 연결하는 공통 절차는 Work Lane과 Scheduler 안에서 유지됩니다.
정리하며
PrivateServer에서는 Actor별 메시지 접수와 실행 종료를 조정하며 lost wakeup을 방지했습니다. ToolKit에서는 반복되는 실행 절차를 Akka의 메일박스와 예약 상태 배치를 참고하여 Work Lane 중심의 Execution 레이어로 구성했습니다.
핵심 요약:
- Work Lane은 작업 큐, 예약 상태, 직렬 소비 절차를 함께 관리합니다.
- Scheduler는 고정 lane 집합과 Worker Pool을 소유하고, 게시와 소비 결과에 따라 실행을 예약합니다.
- Lane 수에 맞춘 ready 용량과 중복 예약 제한으로 Actor별 runnable permit 관리 절차를 대체합니다.
- 사용자는
Item, lane 선택,invoke로 데이터와 실행 범위를 정하고 Actor·서비스 상태의 수명을 관리합니다.
참고 자료
- Actor 스케줄링과 Lost Wakeup — 기존 PrivateServer의 메시지 접수와 실행 예약 관계를 다룬 글입니다.
- Service Host: 패킷과 서비스 로직을 연결하는 공통 파이프라인 — 공용 실행 도구를 마련하게 된 Service Host의 책임을 정리한 글입니다.
- Akka Mailbox — 메일박스의 예약 상태와 실행 종료 후 재등록 구조입니다.
- Akka Dispatcher — 메시지 게시와 메일박스 실행 제출의 연결입니다.
- PrivateServer Actor Scheduler — 기존 admission, runnable permit과 ready token 처리입니다.
- PrivateServer Schedule Gate — producer 접수와 drain 종료를 조정하는 상태 전이입니다.
- ToolKit Work Lane — 게시, 예약 상태와 직렬 소비의 구현입니다.
- ToolKit Worker Scheduler — lane과 Worker Pool 소유, 실행 예약과 종료 처리입니다.
- ToolKit Worker Pool — ready queue, worker의 대기·실행·종료 구현입니다.
이 게시물은 학습한 내용을 바탕으로 초안을 작성한 뒤, LLM의 도움을 받아 내용을 검수하고 다듬어 완성되었습니다.