using System.Collections.Concurrent; using Fleet.Link; using Fleet.Rpc; using MoShiro.Measurement; using MoShiro.Protocol; namespace MoShiro.Agent; /// 一次会话内的运行状态:线路任务的积木、按需测试的阶段。方法由路由器在单个入站队列里顺序调用。 public sealed class DialSession { private const int MaxBlocks = 16; private readonly object _gate = new(); private readonly Dictionary _lineBlocks = []; private readonly Dictionary _lineKinds = []; private readonly ConcurrentDictionary _phases = new(); public DialSession(BlockEnv blockEnv, ClockEstimator clock, Outbox outbox, DialEnv env) { BlockEnv = blockEnv; Clock = clock; Outbox = outbox; Env = env; } public BlockEnv BlockEnv { get; } public ClockEstimator Clock { get; } public Outbox Outbox { get; } public DialEnv Env { get; } public int RoleCount { get { lock (_gate) { return _lineBlocks.Count; } } } /// 正在运行的 TCP 类线路积木数(它们自带套接字和重拨,不依赖 QUIC 数据通道)。 public int TcpBlockCount { get { lock (_gate) { return _lineKinds.Values.Count(IsTcpKind); } } } private static bool IsTcpKind(string kind) => kind is "tcp_ping" or "tcp_connect" or "tls_hold" or "tcp_transfer"; /// 调和线路任务:参数没有变化的积木继续运行,其余启动或停止。返回被拒绝的任务(不认识的 Kind、参数非法、超过上限)。 public async Task> ApplyAsync(LineTaskSet set) { var rejected = new List(); var wanted = new Dictionary(); foreach (LineTask task in set.Tasks) { BlockSpec? spec = BlockCatalog.DialerKinds.Contains(task.Kind) ? BlockSpec.Create(task.Kind, task.Params, task.StreamId, task.Port, new FragmentScope(task.Id, null, null)) : null; if (spec is null) { rejected.Add(new RejectedItem { Id = task.Id.Value, Reason = BlockCatalog.DialerKinds.Contains(task.Kind) ? "invalid_params" : "unsupported" }); } else if (wanted.Count >= MaxBlocks) { rejected.Add(new RejectedItem { Id = task.Id.Value, Reason = "budget_exceeded" }); } else { wanted[task.Id.Value] = spec; } } var stopped = new List(); lock (_gate) { foreach ((string id, RunningBlock running) in _lineBlocks.ToList()) { if (!wanted.TryGetValue(id, out BlockSpec? spec) || spec.Signature != running.Signature) { stopped.Add(running); _lineBlocks.Remove(id); _lineKinds.Remove(id); } } foreach ((string id, BlockSpec spec) in wanted) { if (!_lineBlocks.ContainsKey(id)) { _lineBlocks[id] = RunningBlock.Start(BlockCatalog.Find(spec.KindId)!.Dialer(BlockEnv, spec), spec.Signature); _lineKinds[id] = spec.KindId; } } } // 被停止的积木把已经稳定的行交出去(刚发出、还在等回复的行不交:它们不是丢失,只是没有等到) foreach (RunningBlock running in stopped) { await running.StopAsync(); running.Block.Flush(FlushMode.Settled); } return rejected; } public void FlushLineBlocks() { List blocks; lock (_gate) { blocks = [.. _lineBlocks.Values]; } foreach (RunningBlock running in blocks) { running.Block.Flush(FlushMode.Settled); } } /// 按需测试的一个阶段:起对应的拨号方积木(单向数据流是接收端)。超过本地上限(或不认识)则拒绝。 public bool StartPhase(TestPhaseStartParams start) { string? direction = start.Params.TryGetValue("direction", out System.Text.Json.JsonElement d) ? d.GetString() : null; if (_phases.ContainsKey(start.StreamId) || !BlockCatalog.DialerKinds.Contains(start.Kind) || BlockCatalog.Find(start.Kind)!.Supports(direction) is false) { return false; } if (start.Kind == "quic_stream") { double mbps = start.Params["mbps"].GetDouble(); long size = start.Params["size"].GetInt64(); long pps = (long)(mbps * 1_000_000 / 8 / Math.Max(1, size)); if (mbps > Env.Limits.MaxTotalMbps || pps > Env.Limits.MaxTotalPps) { return false; } } BlockSpec? spec = BlockSpec.Create(start.Kind, start.Params, start.StreamId, start.Port, new FragmentScope(null, start.TestId, start.Phase)); if (spec is null) { return false; } _phases[start.StreamId] = new PhaseState(start.TestId, start.Kind, RunningBlock.Start(BlockCatalog.Find(start.Kind)!.Dialer(BlockEnv, spec), spec.Signature)); return true; } /// 阶段结束:收尾、交出列块,返回块数。 public async Task EndPhaseAsync(TestPhaseEndParams end, CancellationToken ct) { if (!_phases.TryRemove(end.StreamId, out PhaseState? phase)) { return 0; } // 最后一批包(单向数据流)、最后几个回显的回复还在途中 await Task.Delay(TimeSpan.FromMilliseconds(phase.Kind == "quic_stream" ? 200 : 300), BlockEnv.Time, ct); await phase.Running.StopAsync(); return phase.Running.Block.Flush(FlushMode.All, end.SentCount); } public async Task AbortAsync(TestId test) { foreach ((uint stream, PhaseState phase) in _phases.ToList()) { if (phase.Test == test && _phases.TryRemove(stream, out _)) { await phase.Running.StopAsync(); } } } public async Task StopAllAsync() { List blocks; lock (_gate) { blocks = [.. _lineBlocks.Values]; _lineBlocks.Clear(); _lineKinds.Clear(); } blocks.AddRange(_phases.Values.Select(p => p.Running)); _phases.Clear(); foreach (RunningBlock running in blocks) { await running.StopAsync(); } } private sealed record PhaseState(TestId Test, string Kind, RunningBlock Running); }