fix(linkage): ClassIsland IPC 连接统一与联动刷新修正 - #285
Conversation
1. 新增ClassIslandIpcConnection统一管理IPC连接,替换原分散在两个服务中的连接代码 2. 重构课程联动服务的快照比较逻辑,忽略倒计时等动态字段避免误触发状态变更 3. 调整课程联动刷新策略,仅在完全离线时轮询并延长轮询间隔 4. 统一通知服务的IPC连接逻辑,复用新的连接管理类
1. 为IPC连接断开添加500ms延迟重连,避免和ClassIsland的广播循环竞争 2. 新增对IPC连接断开异常的捕获处理,分别在存活检测和通知发送场景中静默处理断开情况并触发内置回退
重置下次连接尝试时间为最小值,当前重试延迟为最小延迟,允许立即重连,避免与 ClassIsland 的广播循环产生竞争。
在 #284 的基础上补齐合入前必须修掉的问题: - 刷新时始终写入新快照,只用语义比较决定是否通知订阅者。完全丢弃倒计时会让 课前解禁、课后禁用延迟、课前重置和刷新调度读到陈旧值(#274 的自激循环来自 事件通知,不来自快照写入) - 恢复 SecRandom4Ci 插件版本门槛(1.2.0.0),旧插件不再被当作可用 - 退避期内立即返回,不再每次空等 10 秒;通知路径只等 1 秒,避免内置回退通知 与抽取前就该打开的 QuickDraw 结果窗口被拖后 - 恢复 IsAlive/ShowNotification 的 1 秒 IPC 调用超时,失败改走 _logger - 清理死常量与不准确的注释,Dispose 不再释放仍在使用的信号量 - 补测试:倒计时变化不通知但快照必须更新、课前解禁窗口按时打开、退避期快速返回、 插件版本门槛;PluginDrawServiceTests 注册新的连接类型 fix #274
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6bc046dbcb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| using ClassIsland.Shared.IPC; | ||
| using ClassIsland.Shared.IPC.Abstractions.Services; |
There was a problem hiding this comment.
Keep ClassIsland types inside the schedule adapter
This new shared connection class directly imports and exposes ClassIsland IPC types, even though the app-layer contract requires ClassIsland types to remain inside ClassIslandScheduleSource. Keep the ClassIsland-specific connection implementation in that adapter, or place a ClassIsland-free interface between it and consumers, so the linkage/notification services do not acquire a second ClassIsland integration boundary.
AGENTS.md reference: SecRandom/AGENTS.md:L166-L166
Useful? React with 👍 / 👎.
| // Only poll when completely unavailable - otherwise rely on events | ||
| if (Settings.DataSource == LinkageDataSource.ClassIsland && !_snapshot.IsAvailable) | ||
| return TimeSpan.FromSeconds(5); | ||
| return TimeSpan.FromMinutes(5); |
There was a problem hiding this comment.
Retry unavailable ClassIsland connections promptly
When SecRandom starts before ClassIsland, or ClassIsland has not yet restarted after a failed connect, the source is unavailable and this five-minute delay is used. No connected peer exists to raise StateChanged, so the hosted scheduler cannot wake early; confirmed break restrictions, floating-window hiding, and pre-class resets remain inactive for up to five minutes after ClassIsland becomes available. Keep a short retry interval while the ClassIsland source is unavailable, or have the connection manager schedule its own retry.
AGENTS.md reference: SecRandom/AGENTS.md:L166-L166
Useful? React with 👍 / 👎.
| private static readonly TimeSpan JsonRouteReadyDelay = TimeSpan.FromSeconds(1); | ||
| private static readonly TimeSpan IpcCallTimeout = TimeSpan.FromSeconds(1); | ||
| private static readonly TimeSpan LessonsWaitTimeout = TimeSpan.FromSeconds(10); | ||
| private static readonly TimeSpan NotificationWaitTimeout = TimeSpan.FromSeconds(1); |
There was a problem hiding this comment.
Allow the first external notification to finish connecting
With linkage disabled (the normal case), the first external draw is also the first IPC connection attempt. The notification caller waits only one second, while the connector necessarily spends the same one second on JsonRouteReadyDelay after Connect() plus probe work before publishing the service, so that first awaited ClassIsland animation always returns without sending. This drops the first external rolling animation and delays the draw; give the notification bootstrap a budget larger than the connection setup, or start the connection before the first draw.
AGENTS.md reference: SecRandom/AGENTS.md:L151-L151
Useful? React with 👍 / 👎.
| var service = NotificationService; | ||
| if (service is not null) | ||
| return service; | ||
|
|
||
| await WaitForLessonsServiceAsync(NotificationWaitTimeout, cancellationToken).ConfigureAwait(false); | ||
| return NotificationService; |
There was a problem hiding this comment.
Reprobe a notification plugin that was unavailable initially
If the initial connection succeeds for lessons but SecRandom4Ci is still loading, times out during its first probe, or is enabled later, _notificationService is stored as null. Subsequent calls enter this method, immediately return from WaitForLessonsServiceAsync because lessons are cached, and return the same null notification service without ever attempting another plugin probe; external notifications stay disabled until the entire ClassIsland peer disconnects. Retry the optional notification proxy independently when it is absent.
AGENTS.md reference: SecRandom/AGENTS.md:L151-L151
Useful? React with 👍 / 👎.
在 @CreeperAWA 的 #284 基础上做了 rebase 与合入前修正,他的三个 commit 原样保留(作者仍是他本人),只在末尾追加一个维护者修复 commit。
原 PR 的 base 是
0737daf0,与当前 master 冲突,且它的ClassIslandScheduleSource.cs还停留在旧的IsLessonConfirmed门槛与Breaking/_ => Unknown映射上——直接合并会回退刚修好的 #278。这里已按当前 master 重新整合。修复的问题
RefreshAsync在语义比较相等时直接return,不再写入新快照,而课前解禁、课后禁用延迟、课前重置和刷新调度只从快照读倒计时(没有第二个时钟来源)。结果是课前解禁窗口不会按时打开、课前重置基本不会触发。现在改为「始终写入快照,只用语义比较决定是否通知订阅者」——疑似与ClassIsland联动出错 #274 的自激循环来自事件通知,不是来自快照写入。GetPluginVersion()校验,MinimumPluginVersion成了死常量,旧插件会被当作可用。GetLessonsServiceAsync在退避期内立即返回;通知路径单独用 1 秒预算,避免内置回退通知与「抽取前就该打开」的 QuickDraw 结果窗口被拖后最多 10 秒。IsAlive()/ShowNotification()重新包上 1 秒WaitAsync,ClassIsland 卡住时发送路径不会一直挂着;失败改走_logger(原来是Debug.WriteLine,Release 下进不了data/logs)。Dispose不再释放仍在使用的信号量、PluginDrawServiceTests补注册新连接类型(原 PR 会让 3 个既有测试失败)。测试
新增/更新后全量 543 通过 / 0 失败,其中新增:
StateChanged,但Snapshot.TimeUntilNextCourse必须更新StateChanged)GetLessonsServiceAsync/GetNotificationServiceAsync立即返回fix #274