.NET RouteMesh/MeshNode Public Interface¶
.NET per-language interface table of contents · Common Topology · MeshNode · Message Model
1. Scope¶
This document fixes ZLink Framework's .NET RouteMesh/MeshNode public interface. The target audience is .NET application developers and public provider implementers. This document owns the C# signature for physical mesh registration, logical channel membership, manual peer, handler, Spot/Actor registration, and runtime weight change.
2. Registration Interface¶
public interface IZLinkFrameworkOptions
{
TimeSpan DefaultRequestTimeout { get; set; }
TimeSpan DefaultSocketSendTimeout { get; set; }
TimeSpan SessionReplacementCallbackTimeout { get; set; }
long ApplicationVersion { get; set; }
string? MaintenanceWave { get; set; }
IZLinkCodecRegistryBuilder Codecs { get; }
IZLinkWorkerOptions Worker { get; }
void AddHandlersFromAssemblyOf<TMarker>();
void AddHandlersFromAssemblyOf(Type markerType);
void AddHandlersFromAssembly(System.Reflection.Assembly assembly);
void DisableImplicitHandlerAutoRegistration();
IZLinkMetadataPolicyBuilder ConfigureMetadata();
void AddLocationStore(IZLinkLocationStore store);
void AddRelocationStore(IZLinkRelocationStore store);
ZLinkLocationOptions ConfigureLocations();
IZLinkNetworkOptions ConfigureNetwork();
IZLinkDispatchOptions ConfigureDispatch();
IZLinkInboundDispatchOptions ConfigureInboundDispatch();
IZLinkStreamCompressionBuilder ConfigureStreamCompression();
void UseFilter<TFilter>() where TFilter : class, IZLinkHandlerFilter;
IZLinkMeshNodeBuilder AddRouteMesh(string meshName);
IZLinkClientServerChannelRoleBuilder AddClientServerChannel(string channelName);
IZLinkFanoutChannelBuilder AddFanoutChannel(string channelName);
IZLinkStreamNodeBuilder AddStreamNode(string streamNodeName);
}
public enum ZLinkCoreHwmProfile
{
Compact = 0,
LowLatency = 1,
Balanced = 2,
Throughput = 3
}
public enum ZLinkApplicationJobQueueProfile
{
Compact = 0,
LowLatency = 1,
Balanced = 2,
Throughput = 3
}
public enum ZLinkApplicationJobQueuePressureState
{
Running = 0,
Paused = 1
}
public interface IZLinkMeshNodeBuilder
{
IZLinkMeshChannelRoleBuilder Channel(string channelName);
IZLinkMeshNodeBuilder Listen(string endpoint);
IZLinkMeshNodeBuilder Listen(int port = 0);
IZLinkMeshNodeBuilder SetBindHost(string bindHost);
IZLinkMeshNodeBuilder SetAdvertiseHost(string advertiseHost);
IZLinkMeshNodeBuilder SetRoutingId(RoutingId routingId);
IZLinkMeshNodeBuilder SetRoutingIdPrefix(string prefix);
IZLinkMeshNodeBuilder SetPlacementWeight(int weight);
IZLinkMeshNodeBuilder SetActorLimit(int limit);
IZLinkMeshNodeBuilder SetSpotLimit(int limit);
IZLinkMeshNodeBuilder SetActivationConcurrency(int limit);
IZLinkMeshNodeBuilder SetInstanceSpotIdleTimeout(TimeSpan timeout);
IZLinkMeshObjectRoleBuilder Objects();
IZLinkMeshNodeSocketConfig ConfigureRouterSocket();
IZLinkSpotPublisherConfig ConfigureSpotPublisher();
IZLinkMeshPeerConnections PeerConnections { get; }
IZLinkMeshNodeBuilder SetDefaultRequestTimeout(TimeSpan timeout);
IZLinkMeshNodeBuilder AddRouteSendHandler<THandler, TMessage>(
string? packetName = null)
where THandler : class, IZLinkRouteSendHandler<TMessage>;
IZLinkMeshNodeBuilder AddRouteSendHandler<THandler>(string? packetName = null)
where THandler : class;
IZLinkMeshNodeBuilder AddRouteRequestHandler<THandler, TRequest, TReply>(
string? packetName = null)
where THandler : class, IZLinkRouteRequestHandler<TRequest, TReply>;
IZLinkMeshNodeBuilder AddRouteRequestHandler<THandler>(string? packetName = null)
where THandler : class;
}
public interface IZLinkMeshObjectRoleBuilder
{
IZLinkMeshObjectClientBuilder Client();
IZLinkMeshObjectServerBuilder Server();
}
public interface IZLinkMeshObjectClientBuilder
{
}
public interface IZLinkMeshObjectServerBuilder
{
IZLinkMeshObjectServerBuilder AddEntrySpot<TEntrySpot>()
where TEntrySpot : class, IZLinkEntrySpot;
IZLinkMeshObjectServerBuilder AddSpotFactory<TSpot>(
string spotType,
Action<IZLinkUserSpotFactoryBuilder<TSpot>> configure)
where TSpot : class, IZLinkSpot;
IZLinkMeshObjectServerBuilder AddInstanceSpotFactory<TSpot>(
string instanceSpotType,
Action<IZLinkInstanceSpotFactoryBuilder<TSpot>> configure)
where TSpot : class, IZLinkInstanceSpot;
IZLinkMeshObjectServerBuilder AddActorFactory<TActor, TFactory>(
string actorType,
Action<IZLinkActorFactoryBuilder<TActor>> configure)
where TActor : class, IZLinkActor
where TFactory : class, IZLinkActorFactory<TActor>;
}
public enum ZLinkUserSpotExecutionMode
{
SpotWide = 0,
PerActor = 1
}
public enum ZLinkSpotRelocationCoordinationMode
{
FrameworkManaged = 0,
ApplicationSignaled = 1
}
public interface IZLinkActorFactoryBuilder<TActor>
where TActor : class, IZLinkActor
{
IZLinkActorFactoryBuilder<TActor> DisableRelocation();
IZLinkActorFactoryBuilder<TActor> RecreateOnRelocation();
IZLinkActorFactoryBuilder<TActor> PreserveStateWith<TAdapter>()
where TAdapter : class, IZLinkActorRelocationAdapter<TActor>;
}
public interface IZLinkUserSpotFactoryBuilder<TSpot>
where TSpot : class, IZLinkSpot
{
IZLinkUserSpotFactoryBuilder<TSpot> StableTypeLimit(int limit);
IZLinkUserSpotFactoryBuilder<TSpot> ExecutionMode(
ZLinkUserSpotExecutionMode mode);
IZLinkUserSpotFactoryBuilder<TSpot> RelocationCoordinationMode(
ZLinkSpotRelocationCoordinationMode mode);
IZLinkUserSpotFactoryBuilder<TSpot> DisableRelocation();
IZLinkUserSpotFactoryBuilder<TSpot> RecreateOnRelocation();
IZLinkUserSpotFactoryBuilder<TSpot> PreserveStateWith<TAdapter>()
where TAdapter : class, IZLinkSpotRelocationAdapter<TSpot>;
}
public interface IZLinkInstanceSpotFactoryBuilder<TSpot>
where TSpot : class, IZLinkInstanceSpot
{
IZLinkInstanceSpotFactoryBuilder<TSpot> StableTypeLimit(int limit);
IZLinkInstanceSpotFactoryBuilder<TSpot> DisableRelocation();
IZLinkInstanceSpotFactoryBuilder<TSpot> RecreateOnRelocation();
IZLinkInstanceSpotFactoryBuilder<TSpot> PreserveStateWith<TAdapter>()
where TAdapter : class, IZLinkSpotRelocationAdapter<TSpot>;
}
public interface IZLinkNetworkOptions
{
string BindHost { get; set; }
string? AdvertiseHost { get; set; }
}
public interface IZLinkMeshChannelRoleBuilder
{
IZLinkMeshChannelClientBuilder Client();
IZLinkMeshChannelServerBuilder Server();
}
public interface IZLinkMeshChannelClientBuilder
{
}
public interface IZLinkMeshChannelServerBuilder
{
IZLinkMeshChannelServerBuilder SetWeight(int weight);
IZLinkMeshChannelServerBuilder AddHandlerGroup(string groupName);
IZLinkMeshChannelServerBuilder AddSendHandler<THandler, TMessage>(
string? packetName = null)
where THandler : class, IZLinkSendHandler<TMessage>;
IZLinkMeshChannelServerBuilder AddSendHandler<THandler>(string? packetName = null)
where THandler : class;
IZLinkMeshChannelServerBuilder AddRequestHandler<THandler, TRequest, TReply>(
string? packetName = null)
where THandler : class, IZLinkRequestHandler<TRequest, TReply>;
IZLinkMeshChannelServerBuilder AddRequestHandler<THandler>(string? packetName = null)
where THandler : class;
}
public interface IZLinkClientServerChannelRoleBuilder
{
IZLinkClientServerChannelClientBuilder Client();
IZLinkClientServerChannelServerBuilder Server();
}
public interface IZLinkClientServerChannelClientBuilder
{
IZLinkClientServerChannelClientBuilder Connect(string endpoint);
}
public interface IZLinkClientServerChannelServerBuilder
{
IZLinkClientServerChannelServerBuilder Listen(int port = 0);
IZLinkClientServerChannelServerBuilder SetBindHost(string bindHost);
IZLinkClientServerChannelServerBuilder SetAdvertiseHost(string advertiseHost);
IZLinkClientServerChannelServerBuilder SetWeight(int weight);
IZLinkClientServerChannelServerBuilder AddHandlerGroup(string groupName);
IZLinkClientServerChannelServerBuilder AddSendHandler<THandler, TMessage>(
string? packetName = null)
where THandler : class, IZLinkSendHandler<TMessage>;
IZLinkClientServerChannelServerBuilder AddRequestHandler<THandler, TRequest, TReply>(
string? packetName = null)
where THandler : class, IZLinkRequestHandler<TRequest, TReply>;
}
public interface IZLinkEndpointConnections
{
void Connect(string endpoint);
void Disconnect(string endpoint);
IReadOnlyList<string> ListConnections();
}
public interface IZLinkFanoutChannelBuilder
{
IZLinkFanoutChannelBuilder EnablePublisher(string endpoint);
IZLinkFanoutChannelBuilder EnablePublisher(int port = 0);
IZLinkFanoutChannelBuilder SetBindHost(string bindHost);
IZLinkFanoutChannelBuilder SetAdvertiseHost(string advertiseHost);
IZLinkFanoutChannelBuilder SetRoutingId(RoutingId publisherRoutingId);
IZLinkFanoutChannelBuilder SetRoutingIdPrefix(string prefix);
IZLinkFanoutChannelBuilder EnableSubscriber();
IZLinkFanoutChannelBuilder Connect(string endpoint);
IZLinkEndpointConnections SubscriberConnections { get; }
IZLinkFanoutChannelBuilder AddHandler<THandler, TEvent>(
string? packetName = null)
where THandler : class, IZLinkFanoutHandler<TEvent>;
}
public interface IZLinkStreamNodeBuilder
{
IZLinkStreamNodeBuilder Bind(string endpoint);
IZLinkStreamNodeBuilder Bind(int port = 0);
IZLinkStreamNodeBuilder SetBindHost(string bindHost);
IZLinkStreamNodeBuilder SetAdvertiseHost(string advertiseHost);
IZLinkStreamNodeBuilder MaxMessageSize(long bytes);
IZLinkStreamSocketConfig ConfigureSocket();
IZLinkStreamNodeBuilder EnableActorDispatch();
IZLinkStreamNodeBuilder SetTlsServer(
string certificatePath,
string keyPath,
bool requireClientCertificate = false);
IZLinkStreamNodeBuilder AddSession<TSession>()
where TSession : class, IZLinkSession;
}
public interface IZLinkStreamCompressionBuilder
{
IZLinkStreamCompressionBuilder UseDefault();
IZLinkStreamCompressionBuilder UseLz4();
IZLinkStreamCompressionBuilder Use(IZlinkStreamCompressionCodec codec);
IZLinkStreamCompressionBuilder Disable();
}
public interface IZLinkMetadataPolicyBuilder
{
IZLinkMetadataPolicyBuilder AllowSessionToActor(string key);
IZLinkMetadataPolicyBuilder AllowActorToSession(string key);
}
IZLinkStreamNodeBuilder.MaxMessageSize(long bytes) defaults to
64 KiB. It is used only when a StreamNode's Core STREAM inbound path checks
a complete client-to-server message, measured as header bytes plus payload
bytes and excluding the 6-byte prefix. 0 maps to Core -1, so Framework
adds no limit; a negative value is a startup configuration error. A message
over the limit is never partly delivered to the handler. The server records
EMSGSIZE and a diagnostic trace, then closes the connection. The raw client
observes the close rather than a separate wire error code. The Framework
limit doesn't apply to server-to-client outbound messages. ClientServer and
RouteMesh SS don't gain this setting.
The declaration of IZLinkCodecRegistryBuilder and the codec
extension is owned by Serialization.
AddRouteMesh(meshName) registers one process-local
MeshNode. Registering the same
meshName twice in the same process fails host startup with
ZLinkConfigurationException. After Channel(channelName), call exactly
one of Client() or Server(). Client() only creates the send path,
and only Server() provides
weight and handler registration.
A MeshNode with no Server membership
can also start.
An automatic RouteMesh
compares RID in canonical byte order, and only the MeshNode with the
smaller RID connects to the counterpart endpoint. Connection intent isn't
created only when both local and remote object roles are Client and
neither has RouteMesh Channel Server membership. Channel Client
membership alone doesn't connect. If either side has Channel Server
membership, connection is needed even if weight is 0. A manual
topology can connect from one or both sides depending on application
endpoint configuration. If bidirectional connection or automatic
discovery contention/a stale snapshot creates a duplicate candidate,
handshake and admission check the same RID and lifecycle generation and
keep only one in ready state.
If a manual endpoint's remote object role and RouteMesh Server membership
can't be known before connect, they're confirmed in the handshake. Only
when both sides are Object Client and neither has RouteMesh Channel
Server membership does admission end with a NotRequired terminal and
close the socket before ready. Background reconnect isn't repeated for
the same endpoint and configuration generation. If the endpoint, expected
RID, or configuration generation changes, it's re-confirmed once as a new
intent.
Listen(string endpoint), Bind(string endpoint), and
EnablePublisher(string endpoint) are provided, and the host/port
combination overload expresses the same listener configuration.
AddClientServerChannel(channelName) can register Client() and
Server(), either one or both, and each role is registered at most
once. The registration key is (ChannelName, Role), and Client and
Server share one ClientServer topology through separate registrations.
Registering the same role twice fails startup. The RouteMesh ChannelName
conflict rule stays the same. A Client can use both the registered manual
endpoint and the server endpoint of the same
ChannelName automatically
discovered from the location store as connection targets. If the two
sources point to the same Server RID and
lifecycle generation,
the connection intent and ready target are merged into one. In both
automatic and manual, only Client connects to server — Server doesn't
look for a client endpoint or start an outbound connect. Server only
provides the received send/request handler and request reply, and
doesn't start a new business call to a connected client.
If a Server role is also registered on the same process, a local Server that finished listener and service admission is put in the same candidate set as a remote Server. The same Ready, positive weight, and non-draining conditions apply, with no local priority or remote exclusion rule. After selection, the actual transport message is delivered from the Client DEALER to the Server ROUTER, without calling the handler directly.
ConfigureNetwork()'s default BindHost is 127.0.0.1, and if
AdvertiseHost is omitted, a non-wildcard
BindHost is used. An
automatic discovery
listener binds to port 0 if the port on Listen()/Bind()/
EnablePublisher() is omitted, or if the listener call itself is
omitted. In manual mode, if the endpoint can't be obtained from a
different discovery source, the listen port and remote endpoint are
specified explicitly. A per-listener host setting takes priority over the
root default.
A fanout publisher that registered a
location store has the
framework generate a per-lifecycle RID and publish a dedicated
descriptor. A publisher with no Store can still be used as a target with
a fixed RID and manually delivered listener endpoint. EnableSubscriber(),
which takes no endpoint, discovers every valid publisher of the same
ChannelName from the location store. Connect(endpoint) configures a
manual subscriber that only uses the specified endpoint. Configuring both
an automatic subscriber and a manual subscriber on one fanout channel
fails startup. An automatic subscriber needs a location store, but it
isn't needed for a host that only uses a manual publisher and manual
subscriber. A publisher only publishes a
descriptor and doesn't start
an outbound connect to a subscriber endpoint. Only the subscriber
connects to the publisher endpoint, and an automatic subscriber creates
one connection intent per Publisher RID and lifecycle generation.
Automatic RID has the format prefix-<lowercase-canonical-uuid-v4>.
UUID v4 is represented as a lowercase canonical string in 8-4-4-4-12
digit groups. Prefix is ASCII [A-Za-z0-9._-] 1..64 characters, and the
full RID is at most 255 UTF-8 bytes. On conflict with an active owner, it
fails immediately with RoutingIdConflict instead of retrying with a
new UUID. A fixed RID is allowed in an automatic discovery topology and on a MeshNode that has an
object role. Implementation and test scenarios sometimes need to name a specific peer, and an
auto-assigned UUID cannot be named. When a node using a fixed RID restarts and collides with the
previous active owner claim, the same rule as an automatic RID applies — it does not retry with a
new value but fails immediately with a conflict, and the restart succeeds once the previous owner
lease expires. Slot count, allocation
group, and a public allocation provider aren't provided.
The Object Server's Entry Spot ID also uses the same prefix, but with a
UUID v4 generated separately from the MeshNode RID attached. The format
is <prefix>-entry-<lowercase-canonical-uuid-v4>, and the caller doesn't
specify a fixed Entry Spot ID. This ID's global conflict and the reserved
format validation for a caller-specified Spot ID are defined by the
Spot Model. The prefix and the
generated RID/Spot ID aren't interpreted as placement, shard, or stable
application identity.
A registered MeshNode descriptor must be at most 1 MiB. The Spot type and stateful object capability collection are each at most 1024. Exceeding the bound fails startup — it doesn't apply only some of the registrations.
SubscriberConnections is a runtime handle for the manual subscriber
endpoint set. It provides connect, disconnect, and current-list query
targeting the same set of endpoints registered on the builder. An
automatic subscriber's discovery results aren't changed by this handle.
AddHandlersFromAssemblyOf(...) and AddHandlersFromAssembly(...) only
add the specified assembly to the handler scan scope. The
declaration of the method, group, and packet attributes used for the
scan is owned by Common Runtime.
EnableActorDispatch() only activates a STREAM node's Actor dispatch
capability. If the same host has no Mesh whose object role is Client
or Server, and no Location Store, startup fails. Since the global
ActorId determines the current Mesh and owner route, this setting doesn't
take a MeshName.
DefaultRequestTimeout's default is 30 seconds, and
DefaultSocketSendTimeout's default is 1 second.
SessionReplacementCallbackTimeout is the maximum time an actor-binding replacement callback may
run before Framework force-closes the retired session; its default is 30 seconds. Worker sets the
worker's minimum/maximum thread count, idle timeout, and queue cap before
host startup.
ConfigureStreamCompression() and IZLinkStreamCompressionBuilder pick
the STREAM payload compression. This builder doesn't configure the
service transport lifecycle or a relocation codec.
ApplicationVersion is set once for the whole host, in the range
0..long.MaxValue, defaulting to 0. Every local MeshNode publishes
this value, and a negative value is rejected with
ZLinkConfigurationException before startup. MaintenanceWave is a
stable ID that, when null, means no wave exclusion is used.
The object role of a MeshNode that didn't call Objects() is None.
Client() provides a manager and an ID-only message client, but doesn't
become a placement target. Server() includes Client capability and
registers Entry Spot and factory. Both roles require a Location Store.
The role can only be selected once.
A MeshNode that selected Objects().Client() can also register
Channel(...).Server(). This combination needs a peer connection to
process Channel request/send. Even if Server weight is 0, Server
capability and the need for connection are kept — it's only excluded
from the selection candidates of a new Channel operation. An application
Node direct handler such as AddRouteSendHandler(...)/
AddRouteRequestHandler(...) can't be registered on an Object Client and
fails with ZLinkConfigurationException before socket bind.
Between two Object Clients, an automatic or manual peer connection isn't needed only when neither side has RouteMesh Channel Server membership. The same applies when only Channel Client membership is registered. If either side has RouteMesh Channel Server membership, the connection is kept. ClientServer and classic fanout are separate physical topologies, so they aren't included in this judgment. A connection between an Object Client and Object Server, or between Object Servers, is kept.
The Actor/User Spot/Instance Spot factory fixes stable type, per-object-kind factory options, and explicit relocation policy in the same registration. There's no overload that omits the policy. Stable type is UTF-8 1..255 bytes, and a duplicate type is a startup error. The Entry Spot ID is issued by the framework.
Node placement weight is 0..10000, defaulting to 100. An out-of-range
value is ZLinkConfigurationException in both startup config and
runtime change. The default 0 for Actor/Spot population limit means no
limit, and the default for pending activation concurrency is 128. If a
per-type limit is null, it shares the node limit; if it has a value, it
must be 1..int.MaxValue, and a value smaller than the node limit
applies. Capacity is applied before weight, and if there's no eligible
node, it's CapacityExceeded.
SetInstanceSpotIdleTimeout(...) is the reference time for cleaning up
an idle Instance Spot. The default is TimeSpan.Zero, and
TimeSpan.Zero means no cleanup. The allowed range is TimeSpan.Zero
and positive values — a negative value is ZLinkConfigurationException
before startup. The value is fixed before the MeshNode lifecycle starts,
and a runtime setter isn't provided. It's a separate setting from
ZLinkWorkerOptions.IdleTimeout, and they don't inherit each other's
value. Only Instance Spot is a cleanup target — Entry Spot and User Spot
aren't affected by this setting. The idle judgment condition, the
delivery of ZLinkSpotCloseReason.IdleEvicted, and the cold-activation
rule after cleanup are owned by
Spot Model §6.2.
3. Manual Peer¶
public readonly record struct ZLinkMeshPeerConnection(
string Endpoint,
RoutingId? ExpectedRoutingId);
public interface IZLinkMeshPeerConnections
{
void Connect(string endpoint);
void Connect(RoutingId expectedRoutingId, string endpoint);
void Disconnect(string endpoint);
IReadOnlyList<ZLinkMeshPeerConnection> ListConnections();
}
If both MeshNodes specified via Connect(...) are Object Client and
neither has RouteMesh Channel Server membership, the configuration intent
can remain in the list but doesn't become a ready peer. Once handshake
ends with NotRequired, it doesn't reconnect for the same configuration
generation, and isn't included in the public RouteMesh status's ready
peer count or liveness targets. If either side has Channel Server
membership, including weight 0, the regular peer admission and
liveness rule applies.
A handler filter is a public extension point the application implements
and registers on the root. Calling next runs the remaining filters and
handler. A request that doesn't call it ends with Rejected, and the
filter doesn't build a business reply directly. The applicable scope,
execution order, and fanout isolation are determined by the common
Framework API.
public enum ZLinkHandlerDispatchKind
{
NodeDirectSend = 0,
NodeDirectRequest = 1,
ChannelSend = 2,
ChannelRequest = 3,
ClassicFanout = 4
}
public interface IZLinkHandlerFilterContext : IZLinkMessageContext
{
ZLinkHandlerDispatchKind DispatchKind { get; }
}
public delegate ValueTask ZLinkHandlerFilterNext();
public interface IZLinkHandlerFilter
{
ValueTask InvokeAsync(
IZLinkHandlerFilterContext context,
ZLinkHandlerFilterNext next,
CancellationToken cancellationToken);
}
ChannelSend and ChannelRequest include both RouteMesh and
ClientServer. The RouteMesh and Node direct context provides MeshName,
and ClientServer and ClassicFanout provide null. A filter calls
next at most once. A second call fails with
ZLinkFrameworkErrorKind.InvalidOperation and doesn't re-run the
handler. If next isn't called on a request, a
ZLinkFrameworkErrorKind.Rejected reply is sent. Behavior where a filter
substitutes for the business reply isn't provided via a compatibility
overload or adapter.
AddInstanceSpotFactory's type name can't be empty and must be at most
255 UTF-8 bytes. Per-type active and pending limits can be omitted, but
an explicit value is 1..int.MaxValue. On the same MeshNode, the same
stable type or the same implementation class can't be duplicated across
a User Spot factory and an Instance factory. If TSpot also implements
the closed generic IZLinkUserSpotActorLifecycle<TActor>, it conflicts
with the actor-free contract, so startup fails. Both options apply per
local MeshNode and per Instance type. The registered type set is fixed
before the descriptor is first published, and doesn't change after
startup.
The factory configure callback sets options and relocation policy on one
builder. The callback must call exactly one of DisableRelocation(),
RecreateOnRelocation(), PreserveStateWith<TAdapter>(). Selecting
none or more than one is a startup configuration error before socket
bind. The Actor builder only takes
IZLinkActorRelocationAdapter<TActor>, and the User/Instance Spot
builder only takes IZLinkSpotRelocationAdapter<TSpot>. If the factory
target and adapter kind don't match, it fails with the same error. There
is no separate registration API.
The framework synchronously runs the callback exactly once inside the registration call. Once the callback returns, the builder configuration is fixed. If the application keeps the builder outside the callback and calls it again, it's a configuration error. If the callback throws, the factory isn't registered and the same exception is propagated to the caller.
A User Spot that selected ZLinkUserSpotExecutionMode.PerActor only
allows RecreateOnRelocation(). Registering DisableRelocation() or
PreserveStateWith<TAdapter>() together is a startup configuration
error before socket bind. A PerActor Spot is a stateless execution
shell, and the member Actor's relocation policy and adapter each handle
Actor state. Shared state and Spot-level schedules that must be kept are
placed in an external store the application owns, such as Redis or a
database.
Execution mode defaults to SpotWide, and relocation coordination mode
defaults to FrameworkManaged. ApplicationSignaled is only allowed with
SpotWide. Registering it together with PerActor is a startup
configuration error before socket bind. The callback uses IZLinkSpot's
default no-op implementation, so an application override isn't required.
If expected RID is omitted, the admission handshake determines the remote identity. If expected RID is specified, the connection isn't admitted if the handshake identity differs. A manual connection also uses the same MeshName/RID/ ChannelName/security validation as an automatic discovery connection.
4. Dispatch Scope Of Handler And Filter¶
A DI scope is created each time a Node direct/Channel send/request and classic fanout subscription handler runs. The handler and filter are each created once by the framework in this scope, and use the same scoped dependency. If a classic fanout message matches multiple subscription handlers, a separate scope is created per subscription handler. Even if the application registers the handler or filter type as singleton/scoped/transient, this lifetime doesn't change. Once dispatch finishes, the framework cleans up the instances it created first, then cleans up the scope.
A Channel handler is distinguished by (ChannelName, message kind,
packet name). A RID direct route handler is registered on the MeshNode
builder and uses a route handler context that provides source RID.
Duplicate registration of the same key is a startup error, and the same
packet name can be registered across different channels or route
families.
AddHandlerGroup(groupName) exposes, on that ChannelName, the send/
request handlers found by scanning that have the same
ZLinkHandlerGroupAttribute value. TicTacToe's manual topology does not mean
manual handler registration. The .NET sample exposes its handlers through
assembly scanning and AddHandlerGroup(...); use typed
AddSendHandler(...)/AddRequestHandler(...) directly only for a separate
example that intentionally demonstrates direct registration.
The weight of IZLinkMeshChannelServerBuilder and
IZLinkClientServerChannelServerBuilder is 0 to 10000, defaulting to
100. An out-of-range value is ZLinkConfigurationException in both
startup config and runtime change. Weighted selection, including node
placement, computes the sum of candidate weight using at least a 64-bit
integer. 0 is only excluded from that channel's new select-one and a
RouteMesh Logical Multicast remote target. It doesn't affect a RID
direct route, other membership, or an already-submitted operation.
5. Publisher And Runtime Option¶
public interface IZLinkSpotPublisherConfig
{
ulong SendHighWaterMark { get; set; }
TimeSpan? SendTimeout { get; set; }
TimeSpan? Linger { get; set; }
}
public interface IZLinkSpotSubscriberConfig
{
ulong ReceiveHighWaterMark { get; set; }
TimeSpan? ReceiveTimeout { get; set; }
TimeSpan? Linger { get; set; }
}
public interface IZLinkSocketConfig
{
long MaxMessageSize { get; set; }
ulong SendHighWaterMark { get; set; }
ulong ReceiveHighWaterMark { get; set; }
int SendBufferSize { get; set; }
int ReceiveBufferSize { get; set; }
TimeSpan? Linger { get; set; }
TimeSpan? ReceiveTimeout { get; set; }
TimeSpan? SendTimeout { get; set; }
TimeSpan? ConnectTimeout { get; set; }
TimeSpan? HandshakeInterval { get; set; }
bool IPv6 { get; set; }
bool TcpNoDelay { get; set; }
bool Immediate { get; set; }
int Weight { get; set; }
}
public interface IZLinkStreamSocketConfig
{
ulong SendHighWaterMark { get; set; }
ulong ReceiveHighWaterMark { get; set; }
int SendBufferSize { get; set; }
int ReceiveBufferSize { get; set; }
TimeSpan? Linger { get; set; }
TimeSpan? ReceiveTimeout { get; set; }
TimeSpan? SendTimeout { get; set; }
TimeSpan? ConnectTimeout { get; set; }
TimeSpan? HandshakeInterval { get; set; }
bool IPv6 { get; set; }
bool TcpNoDelay { get; set; }
bool Immediate { get; set; }
}
public interface IZLinkRouteConfig
{
bool RequireKnownPeer { get; set; }
bool AllowPeerHandover { get; set; }
bool EnablePeerProbe { get; set; }
RoutingId ConnectRoutingId { get; set; }
}
public interface IZLinkOutboundRouteConfig
{
bool ProbeRouterOnConnect { get; set; }
}
public interface IZLinkRouteMeshRuntimeOptions
{
IZLinkMeshPlacementRuntimeOptions Mesh(string meshName);
IZLinkMeshChannelRuntimeOptions Channel(string channelName);
}
public interface IZLinkMeshPlacementRuntimeOptions
{
int PlacementWeight { get; set; }
}
public interface IZLinkMeshChannelRuntimeOptions
{
int Weight { get; set; }
}
public interface IZLinkMeshNodeSocketConfig
{
ulong SendHighWaterMark { get; set; }
ulong ReceiveHighWaterMark { get; set; }
ulong MailboxMessageBudget { get; set; }
ulong MailboxByteBudget { get; set; }
TimeSpan? ReceiveTimeout { get; set; }
TimeSpan? SendTimeout { get; set; }
}
The ClientServer application listener's default MaxMessageSize is 16 MiB.
0 means the framework adds no separate single-message cap and is not cross-validated
with the Core HWM budget or application job queue setting. This setting does not apply to
RouteMesh ServerServer.
ConfigureSpotPublisher() doesn't provide a publish-only delivery
policy option. Logical Multicast
starts once it secures source-local execution capacity within the send
timeout, and completes normally with no return value. It doesn't wait
for or aggregate per-target admission/failure results into public
monitoring, and doesn't automatically retry the whole publish due to
some target's failure. It completes normally even with no targets.
IZLinkRouteMeshRuntimeOptions is a public DI singleton. Querying
unregistered membership is ZLinkConfigurationException.
MailboxMessageBudget and MailboxByteBudget are the caps on message
count and byte sum for the per-owner
application mailbox. Byte accounting doesn't count only payload size —
it adds payload size + metadata size + a fixed per-job cost. Even if
payload is empty, one job isn't 0 bytes, and even for a large payload,
the fixed cost is still added. If the sum exceeds the ulong
representable range, it's pinned to ulong.MaxValue and that submit is
rejected. The accounting rule is owned by
Framework API §8.2.
0 uses the Framework profile's finite default. Both values are set
before startup in ConfigureRouterSocket(), and Logical Multicast's
local target drop also follows this public capacity setting.
At runtime, Mesh(meshName).PlacementWeight and
Channel(channelName).Weight can be changed. The two weights are
independent of each other, and node weight is only used for object
create/relocation target selection. ChannelName uniquely selects a local
RouteMesh or ClientServer Server registration. HWM and timeout are set
before startup in ConfigureRouterSocket().
IZLinkMeshNodeSocketConfig doesn't provide a RouteMesh ServerServer
MaxMessageSize. An SS sender or receiver doesn't reject a message because
of a Framework-level complete-message cap. HWM, mailbox byte budgets, and
service-wire representation bounds remain separate resource and wire guards.
ConfigureDispatch() returns host-wide diagnostics and unhandled-dispatch settings.
The IZLinkInboundDispatchOptions returned by ConfigureInboundDispatch() owns the Core HWM
profile and the independent application-job-queue profile, manual cap, and pause/resume
thresholds. Core memory limit, manual budget, and profile are forwarded to Core. The .NET
binding forwards a positive finite GC.GetGCMemoryInfo().TotalAvailableMemoryBytes as its
runtime memory hint. Both profiles independently default to Balanced, and the pressure
thresholds default to 80/60. Manual job cap is 1..2,147,483,647; omission uses the common
startup CPU snapshot and 32/64/128/256 coefficients. Pause threshold is an integer in 1..100,
resume threshold is an integer in 0..99, and resume must be less than pause. Violating these
bounds or their ordering, or overflowing capacity, fails before socket bind, and runtime does
not recompute the result.
6. Messaging Metadata¶
The Node direct, ChannelName, Spot direct, Actor send/request, and
Logical Multicast call builders commonly have the following overload.
The handler context provides an immutable ZLinkMessageMetadata
snapshot.
public interface IZLinkMetadataCall<TSelf>
{
TSelf Metadata(string key, string value);
TSelf Metadata(ZLinkMessageMetadata metadata);
}
Setting the same key multiple times sends the last value. The whole
metadata's UTF-8 encoded size can't exceed 1024 bytes. A reply doesn't
automatically copy request metadata, and a regular reply doesn't have a
metadata setter. The allowlist applied to STREAM session and Actor relay
is owned by the root's ConfigureMetadata().
7. Location Store And Startup¶
A host using automatic discovery, distributed Spot/Actor address, or Actor relocation must explicitly register a location store. The official Redis location store package is the production default implementation. Without registration, host startup fails. A process-local in-memory implementation can only be registered in a single-process contract test. The store capability and the Redis constructor/options are owned by .NET Location And Maintenance.