| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 |
- namespace Admin.NET.Plugin.AiDOP.Infrastructure;
- /// <summary>
- /// 本实例「是否被指派为 ETL 执行机」的内存投影。由 <see cref="EtlInstanceRegistrar"/>
- /// 在每次心跳时发布,由 <see cref="AidopJobGate"/> 读取。
- ///
- /// <para><b>为什么要这层投影,而不是让闸门直接查库</b>:
- /// <see cref="AidopJobGate.ShouldRun"/> 的调用面是 21 个 <c>IJob</c> 加 2 个 Worker,
- /// 其中 <c>ModuleRebuildWorker</c> 每 5 秒轮询一次。若每次求值都查一次
- /// <c>ado_etl_instance</c>,闸门自身就会变成新的连接压力源——那正是 2026-09-24
- /// 连接耗尽事故的成因,等于把病因又造一遍。
- /// 注册器本来就要按心跳周期写库,顺带把 <c>is_runner</c> 读回来发布到这里,
- /// 热路径便回到纯内存读,零额外查询。</para>
- ///
- /// <para><b>过期即未知</b>:注册器若卡死或持续查库失败,这里的值会过期。
- /// 超过 <see cref="FreshnessWindow"/> 未刷新即视为「未知」,由调用方回退到配置值,
- /// 而不是继续用一个可能已被运维改掉的旧指派。</para>
- ///
- /// <para>纯内存 + <c>volatile</c>,可在任意线程反复读写。</para>
- /// </summary>
- public static class AidopRunnerState
- {
- /// <summary>
- /// 新鲜度窗口。取注册器心跳周期的 3 倍:允许偶发一两次心跳失败而不立刻退化为未知,
- /// 又能保证指派变更最迟在一个窗口内生效。
- /// </summary>
- public static readonly TimeSpan FreshnessWindow = TimeSpan.FromSeconds(60);
- private static volatile bool _assigned;
- private static long _publishedAtTicks; // DateTime.UtcNow.Ticks;0 = 从未发布
- /// <summary>注册器成功读到本实例指派位后调用。</summary>
- public static void Publish(bool assigned)
- {
- _assigned = assigned;
- Interlocked.Exchange(ref _publishedAtTicks, DateTime.UtcNow.Ticks);
- }
- /// <summary>
- /// 注册器查库失败时调用,立即把状态打回未知。
- /// 不保留上一次的值:指派可能已经被改掉,继续沿用会让两台实例同时自认执行机。
- /// </summary>
- public static void Invalidate()
- {
- Interlocked.Exchange(ref _publishedAtTicks, 0);
- }
- /// <summary>
- /// 取新鲜的指派值。返回 false 表示「未知」,调用方须回退到配置值。
- /// </summary>
- public static bool TryGetFresh(out bool assigned)
- {
- assigned = false;
- var ticks = Interlocked.Read(ref _publishedAtTicks);
- if (ticks == 0)
- return false;
- var age = DateTime.UtcNow - new DateTime(ticks, DateTimeKind.Utc);
- if (age > FreshnessWindow)
- return false;
- assigned = _assigned;
- return true;
- }
- /// <summary>仅供测试重置,勿在业务代码调用。</summary>
- internal static void ResetForTests() => Interlocked.Exchange(ref _publishedAtTicks, 0);
- }
|