C++ 바인딩 최종 구조¶
이 장이 정의하는 것 — C++ 라이브러리의
Contracts/Runtime형태와 필수 의미 범위.
이 문서는 C++ 라이브러리의 공개 계약과 소유 구조를 정의한다.
모든 메서드를 빠짐없이 열거하지 않는다. 구체적인 공개 계약은
bindings/cpp/include/zlink/Contracts/에 있다.
Contracts/, 설치되는 헤더 투영, 테스트, 샘플, perf runner와 runtime
동작이 모두 core/include/zlink.h의 안정적인 코어 기능을 C++ 관용 타입으로 매핑한다.
이 README는 ../README.md의 공통 정책을 C++ binding에 적용하는 방법을 정의한다.
이 바인딩은 공통 바인딩 아키텍처 지도를 C++ 네이밍으로 따른다. Contracts/는 설치되는
공개 헤더를 소유하고, Runtime/은 src/ 아래 비공개 구현을 소유한다. 폴더 이름은 저장소
구성을 위한 것이지 사용자가 의존해야 하는 네임스페이스 분절이 아니다.
| 절 | 다루는 내용 |
|---|---|
| 공개 계약 소스 | Contracts/Runtime 소스 위치, 언어 기준, 비동기 표면 정책 링크 |
| 저장소 레이아웃 | 정렬된 디렉터리 트리와 파일 세분화 정책 |
| .NET 계약 카테고리 투영 | .NET 레이아웃을 C++ 네이밍으로 투영하는 방법 |
| 공개 계약 한눈에 보기 | 영역 → 공개 객체 → 소유 헤더 표, 구체 파사드 예시 |
| 코어 기능 소유 규칙 | 새 기능을 추가할 때 따르는 절차 |
| 라이브러리 형태 | RAII, snake_case, Pimpl, 빌더 필수화 규칙 |
| 계약/런타임 배치 규칙 | 공개 선언과 런타임 헬퍼의 경계 |
| 빌드 및 패키징 정책 | zlink_cpp 타깃 빌드·링크·설치 규칙 |
| 계약 폴더 레이아웃 | Contracts/ 하위 카테고리별 소유 범위 |
| 표준 인터페이스 규칙 | recv 시그니처, 빌더, handler 이름 규칙 |
| 64-bit byte HWM과 monitoring 계약 | byte_count_t 표현과 monitor snapshot field |
| Receive flow state | receive-flow 상태 타입, setter와 monitor 표면 |
| 기능 범위 | 완성된 공개 헤더가 다루는 그룹 |
| 수명과 ownership | 리소스 클래스 해제·move·수신 저장소 규칙 |
| 에러와 result 정책 | 실패 표현과 result 도메인 |
| 성능 정책 | hot path·링크 대상 제약 |
| 완성 구조 요구사항 | 완성 선언 전 확인 항목 |
| Actor와 Spot 라우트 결과 | 라우트 결과 타입과 Actor 대상 send/request |
공개 계약 소스¶
- 공개 계약:
bindings/cpp/include/zlink/Contracts/. - 런타임 구현:
bindings/cpp/src/Runtime/. - 공개 진입점 투영:
bindings/cpp/include/zlink.hpp. - 설치되는 투영:
bindings/cpp/include/zlink.hpp및bindings/cpp/include/zlink/Contracts/.... - 컴파일된 라이브러리: C++ 바인딩은 코어 네이티브
zlink라이브러리와 별도로zlink_cpp같은 C++ 라이브러리 타깃을 빌드하고 설치한다. - 언어 기준: C++20.
- 네임스페이스: 모든 공개 타입은
zlink아래에 둔다. service 타입은zlink::service아래에 둔다. - 내부 구현: 네이티브 브리지 헬퍼, 콜백 트램펄린, 요청 진행 헬퍼, 비공개
detail헬퍼, 비공개 구현 헤더,.cpp파일은bindings/cpp/src/Runtime/아래에 둔다. -
문서의 역할: 이 README는 형태, 경계, 필수 의미 범위를 정의한다. 정확한 멤버 목록은
Contracts/가 소유하며, 설치되는 헤더는 이를 의도적으로 투영한다. -
C++는 더 이상 header-only 바인딩으로 모델링하지 않는다.
- 두 번째
bindings/cpp/src/zlink/Contracts/트리를 만들지 않는다. - 공개 계약은 설치되는 헤더에 남고, 구현은
.cpp파일과 비공개 런타임 헤더 뒤로 옮긴다. 계약/런타임 분리는 그대로다. Contracts/는 사용자 표면을 선언하고,src/Runtime/은 그 표면을 위한 구현 지원을 담는다.- Java나 .NET의 인터페이스 중심 레이아웃을 C++에 그대로 복사하지 않는다.
-
C++는 설치되는 헤더, RAII 클래스, 구체 값, 불투명 구현 상태를 자연스러운 경계로 사용한다.
-
C++20은 bindings 라이브러리의 최소 지원 범위다.
- bindings 라이브러리는 직접
co_await하는 move-onlyasync_result_t<T>를 제공하지만, coroutine executor, framework handler executor, framework dispatcher를 소유하지 않는다. - framework coroutine은 같은 awaitable을 직접 기다리며 optional promise hook으로 serial turn과 ambient context의 continuation handoff만 제공한다.
- 언어별 비동기 실행 표면 기준은 바인딩 비동기 실행 표면 정책을 따른다.
저장소 레이아웃¶
완성된 C++ 바인딩은 다음 경로를 일관되게 사용한다. 아래 파일 이름은 목표 소유 지도이며, 공개 개념이나 런타임 책임이 독립적으로 변할 이유가 있을 때만 카테고리를 더 쪼갠다.
bindings/cpp/
+-- CMakeLists.txt
+-- include/
| +-- zlink.hpp
| +-- zlink/
| +-- Contracts/
| | +-- Core/
| | | +-- capability.hpp
| | | +-- context.hpp
| | | +-- context_options.hpp
| | | +-- routing_id.hpp
| | | +-- utilities.hpp
| | +-- Messaging/
| | | +-- message.hpp
| | | +-- received.hpp
| | | +-- topic_message.hpp
| | | +-- subscription_event.hpp
| | | +-- operation_contracts.hpp
| | | +-- request_result.hpp
| | +-- Sockets/
| | | +-- socket_contracts.hpp
| | | +-- message_socket_contracts.hpp
| | | +-- routed_socket_contracts.hpp
| | | +-- pubsub_socket_contracts.hpp
| | | +-- stream_socket.hpp
| | | +-- socket_options.hpp
| | | +-- results.hpp
| | +-- Eventing/
| | | +-- monitor.hpp
| | | +-- poller.hpp
| | | +-- poll_event.hpp
| | | +-- timers.hpp
| | | +-- events.hpp
| | | +-- status.hpp
| | +-- Service/
| | | +-- spot_node.hpp
| | | +-- spot.hpp
| | | +-- actor.hpp
| | | +-- spot_node_models.hpp
| | | +-- actor_models.hpp
| | | +-- operation_contracts.hpp
| | +-- Errors/
| | +-- errors.hpp
| | +-- results.hpp
+-- src/
| +-- Runtime/
| +-- zlink_cpp.cpp
| +-- Core/
| | +-- capability.cpp
| | +-- context.cpp
| | +-- utilities.cpp
| | +-- operation_detail.hpp
| | +-- runtime_helpers.hpp
| | +-- types_impl.hpp
| +-- Messaging/
| | +-- message.cpp
| +-- Errors/
| | +-- error.cpp
| +-- Eventing/
| | +-- monitor.cpp
| | +-- poller.cpp
| | +-- timers.cpp
| +-- Sockets/
| | +-- base_socket.cpp
| | +-- pair.cpp
| | +-- dealer.cpp
| | +-- pubsub.cpp
| | +-- router.cpp
| | +-- stream.cpp
| | +-- detail.hpp
| +-- Options/
| | +-- socket_options.cpp
| +-- Service/
| | +-- actor.cpp
| | +-- actor_ops.cpp
| | +-- detail.hpp
| | +-- request_reply.cpp
| | +-- spot.cpp
| | +-- spot_node.cpp
| | +-- actor_detail.hpp
| | +-- spot_state.hpp
| | +-- spot_submit.hpp
| +-- Native/
| +-- socket_handle.hpp
| +-- native_message_parts.hpp
| +-- native_parts.hpp
| +-- native_options.hpp
| +-- native_send_result.hpp
+-- native/
+-- samples/
+-- tests/
+-- perf/
CMakeLists.txt는 컴파일된 C++ 바인딩 타깃(예: zlink_cpp)을 정의하고 코어 네이티브
zlink 라이브러리에 링크한다. 샘플, 테스트, perf 바이너리, 애플리케이션은 비공개 런타임
소스를 직접 컴파일하지 않고 이 타깃에 링크한다.
Contracts/는 bindings/cpp/include/zlink/ 아래에 설치되는 공개 계약 표면이다.
Runtime/은 bindings/cpp/src/Runtime/ 아래의 비공개 구현 지원이다. zlink
네임스페이스와 zlink.hpp는 그 계약을 C++로 투영한 결과다. Contracts나 Runtime을
네임스페이스 분절로 노출하지 않는다.
런타임 헬퍼 헤더는 공개 계약 API가 아니다. 공개 샘플, perf, 테스트는 <zlink.hpp>를
include하고 C++ 바인딩 라이브러리에 링크한다. 런타임 헬퍼 경로는 include하지 않는다.
include/zlink/message.hpp, include/zlink/services/spot.hpp,
include/zlink/sockets/dealer.hpp 같은 래퍼 헤더는 완성된 레이아웃의 일부가 아니다.
완성된 트리는 이들을 포워딩 헤더로 대체하지도 않는다.
monitor, poller, timer 계약은 공통 Eventing/ 카테고리 아래에 둔다. Contracts/Monitoring/
은 완성된 공개 계약의 일부가 아니며, 완성된 트리는 Monitoring/ 포워딩 헤더를 유지하지
않는다.
파일 단위 세분화는 ../README.md의 공통 정책을 따른다. 독립적인 공개 개념 하나, 또는
긴밀한 operation/모델 묶음 하나당 파일 하나를 둔다. 아주 작은 marker, delegate, enum,
pass-through 헬퍼 파일은 공개 형태가 더 잘 읽힐 때 인접한 계약 파일에 합친다.
.NET 계약 카테고리 투영¶
C++ 바인딩은 .NET 공개 계약 카테고리 레이아웃을 분류 표준으로 사용한다. 이는 카테고리와
책임의 투영이지 C# 형태를 복사한 것이 아니다. C++은 C++20 명명 규칙, 헤더, RAII facade,
이동 의미론, 구체 값 타입을 유지한다.
.NET의 파일 목록을 이 문서에 그대로 옮기지 않는다. .NET의 단일 기준은
.NET 바인딩 청사진, 특히 Contract Folder Layout과 Runtime Folder
Layout 섹션이다. 이 C++ README는 그 카테고리의 C++ 투영만 정의한다.
카테고리 소유 규칙은 엄격하다. 런타임 구현이 다른 위치에 두기 쉽다는 이유로 공개 C++ 타입이
다른 카테고리로 이동하지 않는다. 런타임 헬퍼 코드는 src/Runtime/ 아래에서 더 세분화될 수
있지만, 공개 계약 소유자는 해당 카테고리에 그대로 남는다.
- 이 투영은 C# 인터페이스 스타일을 엄격하게 따르지 않는다.
.NET의 socket role 인터페이스는 공개 계약에서 socket role을 식별한다. C++가 기본적으로isocket_t,istream_socket_t,ISocket,IStreamSocket을 노출하도록 요구하지 않는다.- 사용자가 진짜 substitutable한 동작을 필요로 하지 않는 한 구체 RAII facade를 사용한다.
- substitutable한 role이 필요하면 인터페이스를 좁게 유지하고, send/receive/poll/dispatch 핫패스에서 회피 가능한 virtual dispatch가 없도록 한다.
공개 계약 한눈에 보기¶
완성된 C++ 바인딩은 인터페이스 전용 계층을 추가하지 않고도 공개 계약을 보이도록 만든다.
사용자는 <zlink.hpp>에서 출발해 다음 지도로 소유 계약 헤더를 찾는다.
.NET 기준의 원본 세부사항은 .NET 바인딩 청사진에 두며,
이 문서에는 C++ 투영만 둔다.
| 영역 | 공개 객체와 역할 | 소유 계약 헤더 |
|---|---|---|
| Core | context_t, context 옵션, routing id, version/역할 헬퍼 |
Contracts/Core/ |
| Messaging | message_t, received_t, topic_message_t, subscription_event_t, multipart 헬퍼 |
Contracts/Messaging/ |
| Sockets | pair_socket_t, dealer_socket_t, router_socket_t, pub_socket_t, sub_socket_t, xpub_socket_t, xsub_socket_t, stream_socket_t, send/recv/request/reply 빌더 |
Contracts/Sockets/ |
| Eventing | socket_monitor_t, monitor 이벤트, poller, poll 이벤트, timer, readiness 헬퍼 |
Contracts/Eventing/ |
| Service | spot_node_t, spot_t, actor_ref_t, actor 생명주기 모델, service operation 빌더 |
Contracts/Service/ |
| Errors | 공개 예외와 result 도메인 타입 | Contracts/Errors/ |
poller_t는 void add(socket_monitor_t &monitor_, poll_event_flag_t events_, std::uintptr_t slot_),
void modify(socket_monitor_t &monitor_, poll_event_flag_t events_), bool remove(socket_monitor_t &monitor_)로 socket monitor를
source로 받는다(공통 spec "Poller의 monitor source"). monitor mask는 pollin 또는 none만 유효하고 다른 bit는
config_error_t(config_result_t::invalid_argument)로 거절한다. ready 뒤 socket_monitor_t::recv(DONTWAIT)로 drain한다.
위 지도는 공개 API 색인이다. 계약 표면 개요의 C++ 등가물이며, IContext, ISpot,
IActor 같은 추상 인터페이스를 의미하지 않는다. 공개 리소스 객체는 호출자가 진정한 대체
동작을 필요로 하지 않는 한 구체 RAII 파사드로 유지한다. 좁은 인터페이스는 codec, callback,
handler, poll target처럼 사용자가 자연스럽게 교체하는 역할에만 허용한다.
공개 계약은 두 단계로 읽는다.
<zlink.hpp>에서 시작해 C++ 바인딩이 포함하는 공개 계약 카테고리를 본다.- 해당
Contracts/...헤더를 열어 구체 공개 타입과 그 공개 멤버 목록을 살핀다.
예를 들어 완성된 SPOT 표면은 인터페이스/구현 쌍이 아니라 구체 파사드로 보인다.
namespace zlink::service {
class spot_t {
public:
spot_t(spot_t&&) noexcept = default;
spot_t(const spot_t&) = delete;
send_operation_t send();
reply_operation_t reply();
int recv(received_t& out, recv_flags_t flags = recv_flags_t::none);
void close();
};
} // namespace zlink::service
zlink.hpp는 이 파사드들의 공개 목차 역할을 한다.
#include "zlink/Contracts/Core/capability.hpp"
#include "zlink/Contracts/Core/context.hpp"
#include "zlink/Contracts/Core/context_options.hpp"
#include "zlink/Contracts/Core/routing_id.hpp"
#include "zlink/Contracts/Messaging/message.hpp"
#include "zlink/Contracts/Messaging/received.hpp"
#include "zlink/Contracts/Messaging/topic_message.hpp"
#include "zlink/Contracts/Messaging/subscription_event.hpp"
#include "zlink/Contracts/Messaging/operation_contracts.hpp"
#include "zlink/Contracts/Sockets/message_socket_contracts.hpp"
#include "zlink/Contracts/Sockets/routed_socket_contracts.hpp"
#include "zlink/Contracts/Sockets/pubsub_socket_contracts.hpp"
#include "zlink/Contracts/Eventing/poll_event.hpp"
#include "zlink/Contracts/Eventing/poller.hpp"
#include "zlink/Contracts/Service/spot_node.hpp"
#include "zlink/Contracts/Service/spot.hpp"
#include "zlink/Contracts/Service/actor.hpp"
#include "zlink/Contracts/Errors/errors.hpp"
런타임 세부사항은 파사드 뒤에 둔다. 공개 헤더는 불투명 구현 상태를 이름지을 수 있으나 네이티브 핸들, 콜백 트램펄린, part 루프, request 펌프, marshalling 헬퍼를 노출하지 않는다.
namespace zlink::service {
class spot_t {
public:
spot_t(spot_t&&) noexcept;
spot_t(const spot_t&) = delete;
~spot_t();
send_operation_t send();
reply_operation_t reply();
void close();
private:
struct impl;
std::unique_ptr<impl> impl_;
};
} // namespace zlink::service
이 구조는 공개 표면을 훑어보기 쉽게 유지하면서 C++ ownership 의미를 보존한다.
spot_t, spot_node_t, actor_ref_t가 계약이고, src/Runtime/...과 비공개
zlink::detail 헬퍼는 구현 지원이다.
코어 기능 소유 규칙¶
C++가 노출하는 모든 안정 코어 기능은 다음 소유 규칙을 따른다.
- 올바른
bindings/cpp/include/zlink/Contracts/카테고리에 공개 타입 또는 메서드를 추가한다. bindings/cpp/include/zlink.hpp와 의도적으로 설치하는 투영 헤더를 갱신한다.- C++ 도메인 소유자를 결정한다: context, message, socket, monitor, timer, service, SPOT, actor, error, option 중 하나.
- raw C 핸들 접근, whole-message 배열 marshalling, callback userdata, 트램펄린 상태, 네이티브 marshalling
헬퍼는
src/Runtime/헤더와.cpp파일에 둔다. - 새로운 기능이 사용자 워크플로 또는 측정에 영향을 줄 때는 공개 헤더 테스트와 최소 하나의 샘플/perf 갱신을 추가한다.
- 새로운 공개 API가 얕은 C 래퍼에 머무르지 않는지 확인한다. ownership, 검증, 형태를 개선하지 않고 단순히 위임만 한다면 내부로 유지한다.
- 사용하지 않게 된 공개 이름은 남기지 않는다. 별칭 deprecated, 포워딩 오버로드, 대체 공개 헤더는 이후 문서가 이 C++ 정책을 명시적으로 바꾸지 않는 한 두지 않는다.
명시적인 Spot routing-id 확보는 C++ 바인딩이
spot_node_t::get_or_create_spot(routing_id_t)로 노출하며,
zlink_spot_node_spot_get_or_new(...)에 직접 매핑한다. 이 메서드는 소유된 spot_t
파사드와 생성 플래그를 반환한다. 이 동작을 spot_lookup()과 create_spot()을 조합해
구현하지 않는다.
라이브러리 형태¶
C++ 바인딩은 코어 C 계약 위에 얹힌 작은 네이티브 C++ 라이브러리처럼 느껴진다.
- 공개 리소스 객체는 문서화된 수명에 따라 네이티브 핸들을 소유하거나 빌려 쓰는 RAII 클래스다.
- 소멸자는 호출자가 네이티브 close 순서를 몰라도 리소스를 해제한다. 리소스 소멸자와 그
외 단순하지 않은 메서드는
.cpp파일에서 out-of-line으로 정의한다. - 작은 값 타입 연산은 네이티브 ownership, callback 상태, request 상태, marshalling 세부를 노출하지 않을 때 inline으로 남겨도 된다.
- 공개 메서드는
snake_case를 사용한다. - message, routing id, received metadata, topic message, result, error, enum, option 같은 공개 값 타입은 구체로 유지한다.
- 공개 리소스 헤더는 네이티브 핸들 레이아웃, callback 상태, request 상태, ABI 민감 저장소가 계약에 새어 나가는 것을 막기 위해 Pimpl 등 불투명 구현 상태를 쓴다.
- 템플릿, 오버로드, move 의미는 호출자 ownership을 단순화하거나 복사를 피할 때만 쓴다. 명확한 도메인 타입의 대체로 템플릿 기계장치를 노출하지 않는다.
- 가상 인터페이스는 호출자가 대체 동작을 필요로 할 때만 쓴다. 기본적으로 모든 핸들을 추상 인터페이스로 감싸지 않는다.
- multipart send, publish, request, reply, actor, SPOT operation은 빌더를 필수로 한다. 이렇게 해야 네이티브 request 상태가 숨고 ownership이 분명해진다.
계약/런타임 배치 규칙¶
- 공개 선언과 사용자에게 보이는 동작은
Contracts/에 둔다. - 공개 free function, static 헬퍼, extension 스타일 헬퍼, 빌더 편의 헬퍼는 사용자가 직접
호출할 수 있을 때
Contracts/에 둔다. - 런타임 핸들 소유자, socket 커널, request 펌프, callback 트램펄린, part 루프 헬퍼는
src/Runtime/에 둔다. - FFI 선언, raw C 핸들, 네이티브 struct mirror, marshalling 헬퍼, 플랫폼 로딩 코드는
src/Runtime/Native/에 둔다. zlink.hpp는Contracts/를 투영한다.Runtime/헬퍼 경로를 공개 include 스타일로 만들지 않는다.- 계약 헤더는 비공개 런타임 헤더를 include하지 않는다. 공개 클래스가 구현 상태를
필요로 하면 불완전한
impl타입이나 다른 불투명 비공개 멤버만 노출하고 동작은.cpp에서 정의한다. - 런타임 구체 클래스는 사용자 진입점이 아니다. 공개 동작이라면
Contracts/가 선언하고Runtime/이 구현한다.
빌드 및 패키징 정책¶
C++가 header-only를 벗어나면 바인딩은 컴파일된 산출물을 하나 더 가진다. 따라서 완성된 바인딩은 다음 빌드 규칙을 유지한다.
- C++ 바인딩은
zlink_cpp라이브러리 타깃을 빌드한다. zlink_cpp는 코어 네이티브zlink라이브러리에 링크되며 코어 라이브러리와 버전 호환성 규칙을 가진다.- Linux, macOS, Windows 패키지는 지원되는 아키텍처와 런타임 툴체인마다 C++ 라이브러리를 빌드한다.
- CMake install/export 메타데이터는 애플리케이션이 공개 헤더와 컴파일된 C++ 바인딩 타깃을 함께 소비할 수 있게 한다.
- 샘플, 테스트, perf 러너는 애플리케이션이 사용하는 동일한 설치 스타일 C++ 타깃에 링크한다. 비공개 런타임 소스 경로에 의존하지 않는다.
- 런타임 검색 경로, DLL 조회 규칙, 패키징된 네이티브 산출물은 테스트한다. 이제 애플리케이션은 코어 네이티브 라이브러리와 C++ 바인딩 라이브러리를 함께 로드하기 때문이다.
- 공개 헤더는 ABI 민감 구현 저장소 노출을 피한다. 공개 메서드 시그니처는 C++ 관용을 유지해도 되지만, 네이티브 핸들 레이아웃, callback 상태, request 상태, marshalling 버퍼는 설치되는 헤더 밖에 둔다.
계약 폴더 레이아웃¶
Contracts/는 공개 C++ 선언의 소스 소유 지도다. zlink.hpp는 이 카테고리들을 zlink
네임스페이스로 투영한다.
Core/: context, context 옵션, routing id, utility 리소스, 그리고 version 또는 역할 헬퍼 같은 공개 free function.Messaging/: message, received metadata, topic message, subscription event, stream packet 값, 빌더 payload 헬퍼. Codec 배포 범위는 공통 raw payload 정책을 따른다.Sockets/: socket 동작, socket family, 타입 지정 옵션, request/reply, publish/subscribe 표면.Eventing/: monitor, monitor snapshot/event, poller, poll event, timer, 공개 poll 헬퍼.Service/: SPOT node, SPOT handle, 토폴로지 모델, actor ref, actor 생명주기, operation 빌더.Errors/: 예외 또는 타입 지정 error-result 도메인.- enum, flag, result 타입은 의미를 정의하는 카테고리 안에 둔다. 문법별로 묶기 위한
Enums/폴더를 만들지 않는다.
표준 인터페이스 규칙¶
- data-plane
recv, routed recv, subscribe, subscription-event 수신은 호출자가 제공하는 출력 저장소(예:received_t&,topic_message_t&,subscription_event_t&)를 사용한다. message_t::from(...)은 호출자가 넘긴 바이트를 독립적으로 복사한다. 호출자가 소유한 버퍼를 복사 없이 메시지로 넘겨야 할 때는 고급 API인external_message_t::from(span, free_fn, hint)오버로드를 사용한다. 이 오버로드는 버퍼를 메시지에 맡기고, 메시지가 버퍼를 해제할 때free_fn(data, hint)를 한 번 호출한다.- send, routed send, publish, request, reply, SPOT operation, Actor location/session operation은 move-only fluent 빌더를 반환한다.
- 빌더 시작 메서드는 대상 identity, topic, channel, routing ID와
reply_token_t만 받는다. payload, flag, timeout, async submit 선택은 빌더 단계에서 한다. - SPOT 채널 대상 operation은
send_to_channel(...)과request_to_channel(...)을 쓴다. SPOT topic publish는publish(topic)을 그대로 쓴다. - operation 시작 메서드와 같은 이름의 단일 payload 단축 오버로드를 추가하지 않는다.
send(message),send(routing_id, message),publish(topic, message),send_to_channel(channel, message),send_to_spot(..., message)는 공개 계약 멤버가 아니다. 호출자는send(...).message(message).submit()을 쓴다. - multipart payload는
message(...)를 반복 호출해 쌓는다.messages(...)편의 메서드는 동일한 빌더 계약에 위임하고Contracts/에 선언될 때만 허용한다. - Dealer socket은
request_frame(...)이나reply(request_token, parts)같은 프로토콜 envelope 헬퍼를 노출하지 않는다. Dealer는request()로 request를 시작할 수 있지만 API 수준 peer routing id가 없으므로 임의 token에 reply할 수 없다. send_no_wait,publish_with_flags,request_async같은 operation 시작 오버로드 계열을 추가하지 않는다. operation 이름은 하나로 유지하고 변형은 빌더가 흡수한다. 종단 빌더 메서드의 언어별 이름은 바인딩 비동기 실행 표면 정책을 따른다.- Send의 blocking
submit()은 CoreNONEadmission을 사용하고async()는 CoreDONTWAITcompletion을 기다린다. SocketSNDTIMEO는 blocking admission wait의 상한이다. Binding은 payload 재전송 queue를 만들지 않는다. - C++ binding은 outbound 경로에 자체 lock이나 gate를 두지 않는다. Builder가 모은 모든 part를 native 배열로 만든 뒤 Core whole-message API를 한 번 호출한다. Core는 배열 전체를 하나의 record로 원자적으로 제출하고 모든 슬롯을 소비하지만, binding의 별도 native view가 공개 C++ message를 보존한다. 여러 thread의 독립된 제출은 Core가 처리하며 binding은 직렬화하거나 대기하거나 재시도하지 않는다. close와 in-flight 제출의 경합도 Core lifecycle gate가 담당한다.
- Request는 blocking
submit()과async()를 제공하고 builder의 reply timeout을 유지한다. Target은 operation 생성 때 capture하며 physical connection identity를 public target으로 사용하지 않는다. - request timeout은 Core 소유다(
ZLINK_REQUEST_TIMED_OUT). builder의timeout(...)은 그 Core-owned reply deadline을 지정한다. 제출 실패는submit_error_t로 던지고, 수용 후에는 Core reply lifecycle이 완료를 소유한다. Awaitable drop은 waiter만 detach하고 late completion은 runtime drain이 정리한다. publish_operation_t(PUB/XPUB)의 terminal은 synchronoussubmit() -> bool하나다. PUB/XPUB는 lossy publish semantics를 가지므로 publish builder에는async()가 없다.ZLINK_PUB_OPT_NODROP의 backpressure는 동기 submit에서만 표면화한다.- Raw ROUTER/
received_treply의 terminal은reply_submit_operation_t::submit() -> void인 동기 one-shot이다. Terminal reply와 error reply를 native 호출 한 번으로 제출한다. DEALER peer에는 Application HWM(queue의 byte 보관량을 제한하는 기준)·PAUSED와SNDTIMEO를 적용하여BACKPRESSURED가 될 수 있고, ROUTER peer에는 HWM 없는 Completion connection을 사용한다.NOT_CONNECTED,TERMINATED,INVALID_ARGUMENT와 그 밖의 submit 실패는 즉시submit_error_t로 전달한다.
64-bit byte HWM과 monitoring 계약¶
- Socket HWM과 context Core HWM memory limit·budget은
byte_count_t로 표현한다. - 이 값 타입은
uint64_tbyte만 보관하며bytes(...)생성 함수와bytes()조회 함수로 단위를 드러낸다. - 이전
message_count_t는 alias나 adapter로 유지하지 않는다. 0은 HWM에서 무제한을 뜻하며, 수동 기본값은4,096,000 bytes다.
auto options = socket.options ();
options.send_hwm (zlink::byte_count_t::bytes (send_limit)); // Send pipe의 byte HWM을 정한다.
options.recv_hwm (zlink::byte_count_t::bytes (0)); // 0은 무제한 receive HWM이다.
auto context_options = context.options ();
context_options.core_hwm_memory_limit_bytes (
zlink::byte_count_t::bytes (memory_limit));
context_options.core_hwm_budget_bytes (
zlink::byte_count_t::bytes (core_budget));
context_options.core_hwm_profile (zlink::auto_hwm_profile::balanced);
const auto snapshot = context.core_hwm_budget_snapshot ();
context.reset_core_hwm_budget_metrics ();
core_hwm_memory_limit_bytes(...)와 core_hwm_budget_bytes(...)의 0은 각각
명시 입력과 수동 Core budget이 없다는 뜻이다. Binding은 profile 비율, connection 수 또는
queue별 HWM을 계산하지 않고 exact uint64_t 값을 Core context option으로 전달한다.
C++ binding은 runtime memory hint를 만들지 않는다. 입력 우선순위는 수동 Core budget,
명시 memory limit, Core fallback 순서다. Core가 감지한 finite hard limit보다 명시 입력이
크면 EINVAL을 그대로 전달하고 clamp하지 않는다.
core_hwm_budget_snapshot_t는 Core ABI v1 필드와 flag를 단위 변환 없이 투영하며
core_hwm_budget_snapshot()이 ABI version과 struct size 초기화를 소유한다. 사용자가
send_hwm(...) 또는 recv_hwm(...)을 호출한 방향은 기존처럼 수동 override다.
Snapshot은 configured/runtime/resolved memory limit, configured/effective budget,
planned/applied/manual-reserved HWM, Core queue/application/current/peak/provisional accounted
byte, completion current/peak/pending과 total messaging byte, monitor/instance aggregate,
application/completion queue count, outstanding_application_lease_count, retired_queue_count,
deferred_origin_credit_bytes, oversize·blocked·aggregate flag, budget_generation과
measurement_epoch을 빠짐없이 노출한다. application_accounted_bytes와 위 세
owner-lifecycle 필드는 ABI 예약 필드이며 항상 0이다. Metrics reset은
current·pending·queue count를 유지하고 budgeted/completion peak를 각 current로
재기준화하며 epoch counter를 0으로 만든 뒤 measurement_epoch을 증가시킨다.
계산·수동 override·admission은 Core HWM 계약을 따른다.
0 bytes는 무제한이며 message 한 건을 허용한다는 뜻이 아니다.
socket.monitor_open(events, monitor_hwm_bytes)와
socket_monitor_t::open(socket, events, monitor_hwm_bytes)는 byte_count_t를 받는다.
0은 Core monitor 기본값을 선택하고, 양수는 정확한 monitor queue byte HWM으로
변환 없이 전달한다. Message-count overload나 alias는 없다.
recv(received_t&), subscribe(topic_message_t&), recv(message_t&), subscribe_part(...)의
출력 객체는 C++ copy·close()·소멸자로 part와 routing/topic/request metadata의 수명을 관리한다.
수신 회계와 결과 수명의 경계는 공통 수신 ownership 계약을 따른다.
Legacy auto_hwm_msg_unit_bytes, slot·size-cap·connection-bucket planner property는 alias 없이
제거한다. Monitor snapshot은 Core monitoring ABI v4의 byte pending field를 투영하고,
pending message count와 snd_pending_bytes·rcv_pending_bytes를 별도 값으로 유지하며,
context-wide budget·accounting·queue count는 core_hwm_budget_snapshot_t에서 조회한다.
Receive flow state¶
zlink::receive_flow_state_t는 int 기반 enum class이며 running = 0, paused = 1이다.
Setter는 void socket_t::set_receive_flow_state(receive_flow_state_t)이며 실패한
native result를 담은 config_error_t를 던진다.
상태·결과·monitor 투영은 공통 receive-flow 계약을 따른다.
기능 범위¶
완성된 C++ 바인딩의 공개 헤더는 다음 그룹을 다룬다.
- Core: context, version/역할 헬퍼, context 옵션, shutdown, 자동 HWM 재계산,
atomic_counter_t,stopwatch_t,thread_t. - Messaging: message ownership, 빌더 multipart 입력, received metadata, topic message, subscription event, routing id, callback 타입.
- Socket family: pair, dealer, router, pub, sub, xpub, xsub, stream, stream-bound actor snapshot, 공통 옵션, 타입 지정 socket 옵션, bind/connect/disconnect, TLS, callback, request/reply 표면.
- Eventing: socket monitor, monitor event, monitor snapshot, poller, one-shot
poll(...), poll event, timer, readiness flag. - Services: SPOT node, SPOT handle, 토폴로지 snapshot, actor ref, actor 생명주기, actor operation.
- Errors: 코어 result 도메인을 보존하는 타입 지정 예외 또는 error-result 표면.
C++ 표면은 raw 네이티브 핸들, whole-message 배열 marshalling, callback userdata, 내부 inproc endpoint, request 펌프 객체를 공개 개념으로 노출하지 않는다.
수명과 ownership¶
C++ 호출자는 C 핸들 정리를 추론하지 않아도 된다.
- 리소스 클래스는 소멸자에서 네이티브 핸들을 해제하고, close가 실패할 수 있을 때는 명시적
close또는 동등한 생명주기 메서드를 지원한다. - mutable 핸들을 공유 소유하는 대신 move-only 리소스 클래스를 선호한다.
- 메시지 값은 효율적인 move를 지원하고, 명시적 payload 공유·이전·복제는 공통 계약의
Copy/Move/Clone(각각 Czlink_msg_copy/zlink_msg_move및 deep copy)을 따른다. C++ 시그니처는message_t::copy()(공유, 새 값 반환) ·message_t::move(message_t&)(소유권 이전, 호출자 empty) ·message_t::clone()(독립 버퍼 깊은 복사)이며,move는 C API를 직접 감싼다(C++ move 시맨틱과 별개). 정의는 Message ownership 공통 계약 §"명시적 Copy / Move / Clone". - data-plane 수신과 subscribe 경로는 호출자가 제공하는 저장소를 쓴다.
- 수신 결과의 수명 API는 64-bit byte HWM과 monitoring 계약의 C++ 출력 객체 설명을 따른다.
- Actor join 요청 수신처럼 service 제어/입장 수신 경로는 C++ 호출자에게 더 명확하면 optional이나 타입 지정 결과 반환을 써도 된다. 다만 data 없음과 강한 수신 실패는 여전히 구분해야 한다.
- callback은 네이티브 callback 수명과 사용자 callable 수명을 내부에서 일관되게 유지한다.
에러와 result 정책¶
바인딩은 예외 또는 타입 지정 result 객체 중 무엇을 써도 되지만, 공개 형태는 코어 의미를 보존한다.
- data 없음과 일시적 backpressure는 강한 실패와 구분해 유지한다.
- request, submit, recv, bind, connect, config, handler, close 실패는 result 도메인 의미를 유지한다.
pollout은 send 복구 readiness 신호이며 일반적인 writable 비트가 아니다.- ROUTER/PUB 기본값, SPOT HWM 기본값, SPOT dispatch worker 의미는 코어 헤더를 따른다.
성능 정책¶
- multipart 값은 Core whole-message receive가 채운 배열에서 직접 만든다.
- hot path에서 불필요한 힙 할당, 회피 가능한 복사, reflection 같은 동적 dispatch, 숨겨진 대기, sleep, busy wait, 광범위한 lock, join을 피한다.
- perf와 샘플은 설치되는 공개 헤더만 include한다.
- perf와 샘플은 공개 C++ 바인딩 타깃에 링크한다. 비공개 런타임 object 파일이나 헬퍼 소스 디렉터리에 링크하지 않는다.
- C++ perf 의미는
bindings/c/perf와 일치한다. 같은 패턴 의미, 같은 transport 의미, 같은 클라이언트 수 정책을 따르며 비공개 fast path는 두지 않는다.
완성 구조 요구사항¶
완성된 C++ 바인딩은 다음 요구사항을 만족한다.
- 설치되는 헤더가 안정적인 사용자 대상 코어 기능을 모두 노출한다.
- C++ 바인딩은 공개 헤더 외에 컴파일된 C++ 라이브러리 타깃을 빌드하고 설치한다.
Contracts/Eventing/이 유일한 공개 eventing 카테고리다.Contracts/Monitoring/은 사라졌고,zlink.hpp는 Eventing 헤더를 include한다.- 옛 래퍼 include 경로는 사라졌다. 애플리케이션, 샘플, perf, 테스트는
<zlink.hpp>또는 의도적인Contracts/...헤더만 include한다. - 공개 헤더와 컴파일된 C++ 바인딩 타깃만으로 애플리케이션, perf, 샘플, framework 어댑터가 필요한 것을 모두 갖춘다.
- 사용자는 비공개 헬퍼 헤더와 비공개 런타임 소스 경로가 필요 없다.
- 추상화가 실제 복잡도를 줄이지 않는 한 값 타입은 구체로 남는다.
- 공개 API는 네이티브 part 루프, raw 핸들, callback userdata를 숨긴다.
- handler 등록은
set_..._handler이름을 쓰고, 공개on_...별칭은 두지 않는다. - 공개 헬퍼/free function과 빌더 편의 메서드는 런타임 헬퍼가 아니라
Contracts/에 선언한다. - service 제어/입장 수신 예외는 data-plane의 호출자 제공 저장소와 다를 때 문서화한다.
- perf 테스트는 C perf와 동일한 측정 의미를 쓴다.
Actor와 Spot 라우트 결과¶
C++는 Actor와 Spot 라우트 조회 결과를 구체 계약 타입으로 노출한다.
actor_route_t는 해석된 Actor ref,actor.node_rid,current_spot_rid,current_spot_kind를 보존한다.spot_route_t는spot_rid,owner_node_rid,spot_kind를 보존한다.spot_kind는 Entry Spot과 사용자 Spot을 구분한다. 잘못된 kind는 성공한 라우트 결과가 아니다.-
spot_node_spot_entry_t와spot_node_actor_entry_t는 코어 snapshot과 같은 Spot kind/현재 Spot 필드를 노출한다. -
C++는 resolve된 Actor ref를 인자로 받는
spot_node_t::send_to_actor(actor_ref_t)와spot_node_t::request_to_actor(actor_ref_t)를 노출한다. send_to_actor는 submit이 성공하면 하나 이상의 message part 소유권을 넘기고, Actor 소유자 mailbox가 인계를 받으면 완료된다.request_to_actor는 submit이 성공하면 요청 part의 소유권을 넘기고, Actor handler가 만든 reply part를 native awaitable 결과로 전달한다.- C++는 제거된 Discovery route table이나 resolver API를 compatibility helper로 되살리면 안 된다.
Pull completion 공개 계약¶
C++ package 정보는 배포 metadata를, Core ABI 버전은 Core release metadata를 따른다.
C++는 blocking submit()과 결과 객체(send_submission_t/request_submission_t: result와 admitted, request는 reply)를 돌려주는 async()를 제공한다.
완료 대기 객체의 수명 종료는 async_result_t drop으로 표현한다.
Native completion ID·user_context·raw drain은 public API에 노출하지 않는다.
제출 결과는 공통 결과 투영을, 완료 합류·수명과
poll_event_flag_t::pollcompletion의 진행 조건은 비동기 실행 모델을 따른다.
ROUTER REQUEST receive만 reply_token_t를 만든다. Token은 ROUTER wrapper가 만든 shared owner
tag와 opaque value를 함께 보유한다. Equality·hash와 reply owner 검증은 두 값을 사용한다.
Public numeric constructor, raw accessor, ordering, serialization과 close를 제공하지 않는다.
stream_packet_t는 move-only reusable output이며 recv 진입 때 이전 payload를 먼저 비운다.
같은 output의 concurrent recv는 invalid-state다. Header/body reference는 다음 recv 진입이나
close() 전까지만 유효하다. Receive mode setter는 첫 bind/connect 전에 raw·packet만 받고
unspecified를 거부한다.
Public interface¶
struct send_submission_t {
zlink_submit_result_t result; // OK | BACKPRESSURED, 제출 시점 스냅샷
async_result_t<void> admitted; // OK면 완료 상태
};
struct request_submission_t {
zlink_submit_result_t result;
async_result_t<void> admitted;
async_result_t<std::vector<message_t>> reply; // admitted 성공 뒤 완료
};
class send_submit_operation_t {
public:
send_submit_operation_t&& message(message_t&) &&;
send_submit_operation_t&& message(message_t&&) &&;
void submit() &&;
send_submission_t async() &&;
};
class request_submit_operation_t {
public:
request_submit_operation_t&& message(message_t&) &&;
request_submit_operation_t&& message(message_t&&) &&;
request_submit_operation_t&& timeout(std::chrono::milliseconds) &&;
std::vector<message_t> submit() &&;
request_submission_t async() &&;
};
class reply_token_t final {
public:
reply_token_t() = delete;
reply_token_t(const reply_token_t&) = default;
reply_token_t& operator=(const reply_token_t&) = default;
friend bool operator==(const reply_token_t&, const reply_token_t&) noexcept;
private:
reply_token_t(std::shared_ptr<const void> owner, uint64_t value) noexcept;
std::shared_ptr<const void> owner_;
uint64_t value_;
friend struct detail::received_access_t;
friend struct reply_token_hash_t;
};
struct reply_token_hash_t {
std::size_t operator()(const reply_token_t&) const noexcept;
};
class reply_submit_operation_t {
public:
reply_submit_operation_t&& message(message_t&) &&;
void submit() &&;
};
enum class stream_recv_mode_t : int {
unspecified = 0,
raw = 1,
packet = 2
};
class stream_packet_t final {
public:
stream_packet_t() = default;
~stream_packet_t();
stream_packet_t(stream_packet_t&&) noexcept = default;
stream_packet_t& operator=(stream_packet_t&&) noexcept = default;
stream_packet_t(const stream_packet_t&) = delete;
stream_packet_t& operator=(const stream_packet_t&) = delete;
bool empty() const noexcept;
const std::optional<routing_id_t>& routing_id() const noexcept;
message_t& header();
message_t& body();
void close() noexcept;
private:
std::optional<routing_id_t> routing_id_;
std::optional<message_t> header_;
std::optional<message_t> body_;
friend class stream_socket_t;
};
bool stream_socket_t::recv_packet(
stream_packet_t& out, recv_flags_t flags = recv_flags_t::none);
stream_recv_mode_t stream_socket_options_t::recv_mode() const;
void stream_socket_options_t::recv_mode(stream_recv_mode_t mode);
Operation 시작 signature는 PAIR send_operation_t send(), DEALER
send_operation_t send()·request_operation_t request(), ROUTER
send_operation_t send(const routing_id_t&)·
request_operation_t request(const routing_id_t&)·
reply_operation_t reply(const routing_id_t&, reply_token_t), STREAM
send_operation_t send(const routing_id_t&)다. Send factory는 target을 builder에 capture한다.
received_t::reply_token()은 const std::optional<reply_token_t>&를 반환한다.
received_t::send()은 source target을 capture한 send_operation_t, received_t::reply()는 source
RID와 token을 capture한 reply_operation_t를 반환한다.
Public C++ surface에는 send/request/reply .flags(...), send .timeout(...), request callback
overload/type, async_result_t::cancel(), STREAM packet handler, monitor on_event·ignore_event,
timer on_fire, pair/generation member와 exact-pair method가 없다. PAIR·STREAM send의
submit()도 bool을 반환하지 않는다. routed_send_operation_t와
routed_send_submit_operation_t도 public type이 아니다.
Monitor는 optional<monitor_event_t> recv(recv_flags_t = none)·status()·close()를,
timer는 start(duration, uint64_t = 0)·stop()·optional<uint64_t> recv()·close()를
제공한다. Pending native option 이름은 ZLINK_OPT_PENDING_MAX_MSGS와
ZLINK_OPT_PENDING_MAX_BYTES다.
Monitor event의 connection_id는 진단과 correlation에만 사용하며 send·reply target이나
reconnect fence로 사용하지 않는다.
Pending native option은 public high-level option façade를 추가하지 않는다.
구현 및 contract test 검증 요구¶
Public C++ interface, 반환값·exception과 poller event만으로 다음을 확인한다. 각 항목은 contract test 하나로 이어진다.
Operation과 완료
- 모든 socket의 send factory가
send_operation_t를 반환하고 blockingsubmit()과async()가 각각NONE과DONTWAIT완료 경계를 관찰한다. - Request는 timeout·blocking result·
async()만 제공한다. - 완료·cancellation·poller의 공통 관측은 실행 모델 검증 요구를 따른다.
- Raw reply를 DEALER peer로 제출해 HWM·PAUSED 대기가 만료하면
submit_error_t의BACKPRESSURED가 관찰되고, ROUTER peer로 제출하면 Completion connection의 HWM-free 결과가 유지된다.
ReplyToken과 STREAM
- 같은 ROUTER owner와 opaque value의 token은 같고 다른 owner의 token은 다르며, 다른 owner token의 reply는 native 호출 전에 실패한다.
- ROUTER wrapper move는 owner tag identity를 보존하고 close·recreate 뒤 stale token은 새 wrapper에 사용할 수 없다.
recv_packet()은 성공 때 output을 채우고NO_DATA·오류 때 empty로 유지하며,close()뒤 같은 output을 다시 사용할 수 있다.
Pull eventing
- Monitor DONTWAIT no-data는
nullopt, timer no-data도nullopt이며 pull lifecycle로 event와 fire count를 한 번씩 관찰한다.