AdapterPoolMemberService
Namespace: Meshmakers.Octo.Sdk.Common.Adapters
Runs this process as a member of an adapter pool (AB#4924): it opens the tenant-free management connection, registers on every connect, and heartbeats at the cadence the controller hands back.
public sealed class AdapterPoolMemberService : BackgroundService, IHostedService, IDisposable
Inheritance Object β BackgroundService β AdapterPoolMemberService
Implements IHostedService, IDisposable
Remarks:
This is the piece that turns AdapterPoolClient from a class into a running
member. Increments 6 and 7 built the client, the hub, the scheduler and the work item, but
nothing composed them into a process: AddAdapterPoolMember() registered the client
and no hosted service ever started it, so a correctly configured member connected to
nothing and was never leased.
π΄ Registration happens inside the connect callback, not once after
StartAsync. The controller binds a member to its SignalR connection
(AdapterPoolConnectionManager), so a reconnect leaves the old mapping behind. A
member that registered only once would sit there connected and healthy-looking, and never
receive another lease β the failure mode is silence, which is why this is a callback and
not a line of startup code.
The heartbeat cadence is not a local option. The controller returns it at
registration (PoolMemberRegistrationResultDto.HeartbeatIntervalSeconds), so the two
sides cannot drift apart and an operator has exactly one place to change it.
covers only the window before the first answer
arrives, and the case where an older controller answers with nothing.
Member recovery (AB#4924 AP-I5), the pool member's counterpart of AB#5409/AB#5415. Registration β not the connection β is the signal, recorded in AdapterPoolClient.RegistrationState and read by AdapterPoolHubReadinessHealthCheck and AdapterPoolHubRecoveryService. This service adds the parts that only the member itself can do:
Re-register instead of heartbeating into nothing. A registration the controller refused (a controller pod that is shutting down, an enforcing tenant binding) or a heartbeat it rejected leaves the connection up and the member unleasable. The heartbeat tick then registers again rather than sending a heartbeat nobody records.
Never offer a busy process as free β and never lose its lease (AB#5826). A member
that reconnects while its lease is still running registers through
ResumePoolMemberAsync, naming the lease: the controller takes it over (after a
controller restart it would otherwise not know it at all; after a dropped connection
it holds the work back for a reconnect grace instead of re-queuing it), and the
member's release then completes the execution. Only a controller that pre-dates the
method (or refuses it) gets the old behaviour: the registration is deferred until the
lease is released, because a plain registration would make it hand the busy member
the next lease at once.
Report what could not be reported (AB#5826). A release that failed because the connection was down is kept and re-sent on the next connect, before the member registers again β so a controller that holds the work back for this member learns the outcome before it learns that the member is idle.
π΄ A draining member never registers again (AB#5864). It takes no further lease, and the controller only knows that for the connection the drain was reported on β a registration on a new connection starts as available, the next grant lands on a process that refuses it, and before AB#5864 that refusal failed the borrower's execution. The member exits instead (AdapterPoolMemberDrainExitService).
Flush the CK model cache of the recently leased tenants after a reconnect
(AB#5415). CkModelChanged never reaches a member at all β it has no adapter-hub
connection β so per-lease load/unload (the host's lease participants) is what keeps a
member current between leases. What a reconnect can leave stale is the tenant of the
lease running across it and, for a host without such a participant, the last tenant
entered. Flushed before the registration, so no new lease can warm a cache that is
about to be dropped.
Propertiesβ
ExecuteTaskβ
public Task ExecuteTask { get; }
Property Valueβ
Constructorsβ
AdapterPoolMemberService(AdapterPoolClient, IAdapterPoolHubClient, IOptions<AdapterPoolMemberOptions>, ILogger<AdapterPoolMemberService>, IAdapterService)β
Creates the pool-member service.
public AdapterPoolMemberService(AdapterPoolClient poolClient, IAdapterPoolHubClient hubClient, IOptions<AdapterPoolMemberOptions> options, ILogger<AdapterPoolMemberService> logger, IAdapterService adapterService)
Parametersβ
poolClient AdapterPoolClient
The member's half of the lease protocol.
hubClient IAdapterPoolHubClient
The management connection.
options IOptions<AdapterPoolMemberOptions>
The pool this member belongs to.
logger ILogger<AdapterPoolMemberService>
Logger.
adapterService IAdapterService
The host's adapter service, whose IAdapterService.CkModelChangedAsync(String) drops a
tenant's CK model cache. Optional: a host without one has no cache to flush.
Methodsβ
ExecuteAsync(CancellationToken)β
protected Task ExecuteAsync(CancellationToken stoppingToken)
Parametersβ
stoppingToken CancellationToken
Returnsβ
TickAsync()β
One heartbeat tick: a heartbeat while registered, a registration attempt while the connection is up but the controller holds no registration, nothing while the connection is down (the SignalR client owns the reconnect, and its connect callback registers). Internal so the decision can be tested without the timer.
internal Task TickAsync()
Returnsβ
StopAsync(CancellationToken)β
public Task StopAsync(CancellationToken cancellationToken)
Parametersβ
cancellationToken CancellationToken
Returnsβ
OnConnectedAsync(Boolean)β
Runs on every connect and every reconnect. See the remark on the class for why registering once would be wrong. Internal for the tests.
internal Task OnConnectedAsync(bool reconnected)
Parametersβ
reconnected Boolean