using System.Text.Json; using Fleet.Core; using Fleet.Rpc; namespace Fleet.Server; /// 状态:期望状态推送、在线状态、节点事件。 internal sealed partial class NodeConnection { public void SignalStateChanged() => _stateSignal.Writer.TryWrite(true); private async Task StatePushLoopAsync(CancellationToken ct) { while (await _stateSignal.Reader.WaitToReadAsync(ct)) { _stateSignal.Reader.TryRead(out _); bool complete = await PushStateAsync(ct); if (!complete) { await Task.Delay(s_statePushRetry, _host.Time, ct); SignalStateChanged(); } } } private async Task PushStateAsync(CancellationToken ct) { NodeRecord? fresh = await _host.Store.GetNodeAsync(_node!.Id, ct); if (fresh is null) { return true; } _node = fresh with { NodeVersion = _node.NodeVersion ?? fresh.NodeVersion }; if (fresh.Status == NodeStatus.Disabled) { await RevokeAsync("disabled", ct); return true; } if (fresh.Status != NodeStatus.Active || !_helloDone) { // pending 不下发状态;还没有 hello 的连接等 hello 之后再推送。 return true; } bool complete = true; foreach (IStateProvider provider in _host.StateProviders) { StateDocument? document = await provider.GetAsync(_node, ct); if (document is null || (_lastPushedVersion.TryGetValue(provider.Type, out long last) && last == document.Version)) { continue; } Result raw = await RequestAsync( FleetMethods.StateSync, JsonSerializer.SerializeToElement( new StateSyncParams { Type = provider.Type, Version = document.Version, Doc = document.Doc }, FleetRpcContext.Default.StateSyncParams), ct); if (!raw.TryGet(out JsonElement value, out FleetError? error)) { FleetLog.StatePushFailed(_host.Logger, _node.Id.Value, provider.Type, error.Code.Value); complete = false; continue; } StateSyncResult? result = null; try { result = value.Deserialize(FleetRpcContext.Default.StateSyncResult); } catch (JsonException) { // 按缺失处理 } if (result is null) { complete = false; continue; } _lastPushedVersion[provider.Type] = document.Version; await provider.OnAppliedAsync(_node, document.Version, result, ct); } return complete; } private async Task UpdatePresenceAsync(bool online, CancellationToken ct) { Interlocked.Exchange(ref _lastPresenceTicks, _host.Time.GetTimestamp()); await _host.Store.UpdatePresenceAsync( _node!.Id, new Presence(online, _host.Time.GetUtcNow().ToUnixTimeMilliseconds(), _session?.NodeVersion, _session?.Capabilities ?? [], _session?.HelloMetaJson), ct); } private async Task FireAsync(NodeEventKind kind, CancellationToken ct, HelloParams? hello = null, JsonElement? extra = null) { var nodeEvent = new NodeEvent(kind, _node!, _host.Time.GetUtcNow().ToUnixTimeMilliseconds(), hello, extra, kind == NodeEventKind.Offline ? _closeReason : null, _remoteIp); foreach (INodeEventSink sink in _host.Sinks) { try { await sink.OnEventAsync(nodeEvent, ct); } catch (OperationCanceledException) when (ct.IsCancellationRequested) { throw; } catch (Exception ex) { // 产品的事件处理失败不能影响连接。 FleetLog.SinkFailed(_host.Logger, ex, kind.ToString(), _node!.Id.Value); } } } }