using Fleet.Rpc; namespace Fleet.Link; /// /// 可靠发件箱:至少一次投递。消息带 msg_id(UUIDv7),服务端按它去重,持久化之后才回复 ACK。 /// 有界(按字节和条数):满了丢弃最旧的,并累计缺口数通知产品。默认只在内存里,节点重启会丢失未上传的数据。 /// 重新连接之后,所有还没确认的消息按原顺序重发。 /// public sealed class Outbox { private readonly object _gate = new(); private readonly LinkedList _entries = []; private readonly Dictionary> _byId = []; private readonly long _maxBytes; private readonly int _maxCount; private TaskCompletionSource _signal = NewSignal(); private long _bytes; private long _inflightBytes; private long _dropped; public Outbox(long maxBytes, int maxCount = int.MaxValue) { _maxBytes = maxBytes; _maxCount = maxCount; } /// 因队列已满而丢弃的消息数(缺口)。 public long Dropped => Interlocked.Read(ref _dropped); public long PendingBytes { get { lock (_gate) { return _bytes; } } } public int PendingCount { get { lock (_gate) { return _entries.Count; } } } /// 每丢弃一批消息调用一次(参数是这一批的条数)。 public Action? OnGap { get; set; } public void Enqueue(byte channel, Guid msgId, ReadOnlySpan payload) { byte[] frame = OutboxFrame.Build(channel, msgId, payload); int dropped = 0; lock (_gate) { if (_byId.ContainsKey(msgId)) { return; } while (_entries.Count > 0 && (_bytes + frame.Length > _maxBytes || _entries.Count >= _maxCount)) { Remove(_entries.First!); dropped++; } _byId[msgId] = _entries.AddLast(new Entry(msgId, frame)); _bytes += frame.Length; Wake(); } if (dropped > 0) { Interlocked.Add(ref _dropped, dropped); OnGap?.Invoke(dropped); } } /// 连接断开之后:所有消息都视为没有发出,重连时重发。 public void ResetSent() { lock (_gate) { foreach (Entry entry in _entries) { entry.Sent = false; } _inflightBytes = 0; Wake(); } } public void Acknowledge(IEnumerable ids) { lock (_gate) { foreach (Guid id in ids) { if (_byId.TryGetValue(id, out LinkedListNode? node)) { Remove(node); } } Wake(); } } /// /// 取下一条还没发出的消息;在途字节达到 时等待 ACK(服务端慢时不会把内存撑爆)。 /// public async ValueTask TakeNextAsync(long maxInflightBytes, CancellationToken ct) { while (true) { Task wait; lock (_gate) { foreach (Entry entry in _entries) { if (!entry.Sent) { // 一条消息比在途上限还大时也要放行,否则会永远卡住 if (_inflightBytes == 0 || _inflightBytes + entry.Frame.Length <= maxInflightBytes) { entry.Sent = true; _inflightBytes += entry.Frame.Length; return entry.Frame; } break; } } wait = _signal.Task; } await wait.WaitAsync(ct); } } private void Remove(LinkedListNode node) { _entries.Remove(node); _byId.Remove(node.Value.Id); _bytes -= node.Value.Frame.Length; if (node.Value.Sent) { _inflightBytes = Math.Max(0, _inflightBytes - node.Value.Frame.Length); } } private void Wake() { _signal.TrySetResult(); _signal = NewSignal(); } private static TaskCompletionSource NewSignal() => new(TaskCreationOptions.RunContinuationsAsynchronously); private sealed class Entry { public Entry(Guid id, byte[] frame) { Id = id; Frame = frame; } public Guid Id { get; } public byte[] Frame { get; } public bool Sent { get; set; } } }