修复 icpctools 的 “Contest not done updating” 问题
又是一年校赛时,年年修锅,但年年有新锅。笔者今年 6 月毕业,校赛举办时已不在学校,所以今年的环境配置工作是接任我的人做的。
比赛流程大体上算是顺利的,但是最后在使用 icpctools 的 Awards/Resolver 工具滚榜时遇到了问题,工具一直提示 “Contest not done updating”。尽管该情况下可以配置并上传奖项,但 Resolver 中的队伍提交数据总是不全,无法正确展示榜单。在尝试 Finalize DOMjudge 比赛并在 CDS 中多次点击 “End of updates” 无果后,最终只好妥协直接使用 Event Feed 滚榜。由于没有做好相关预案,滚榜时就没有校徽和队伍照片了。
想跳过废话,直接看解决方案跳转 “解决方案” 部分即可。
问题定位
Awards 和 Resolver 使用的是 CDS 的 REST API 获取 Event Feed 数据。离线滚榜时,在 CDS 页面点击 “Event Feed” 能够在浏览器中下载完整的文件,文件内容也显示比赛已经是 end_of_updates 状态,而且我们使用 Resolver 离线滚榜也是正常的。这说明 Event Feed 的数据没有问题,那么可能需要从 icpctools 的 Event Feed 获取这块着手定位问题。

从 icpctools 仓库 下载源码导入 IDEA 编译,发现最新版在我电脑上甚至队伍都加载不出了。我用旧版的 icpctools 好歹还能加载出有些,这最新版本直接给我干没了。但在疑惑的同时也注意到 “Contest not done updating” 出现得比旧版要快。于是直接搜索这段文本,定位到其在 AwardUI 中:

追踪看了下 isDoneUpdating 的实现,可以快速确定它就是判断 Event Feed 中有没有出现 end_of_updates 的时间戳判断的,也就是说 Awards 收到的 Event Feed 是不完整的。查看 waitForContest 的实现,发现它会先 waitForContestLoad. waitForContestLoad 的超时时间只有 2s,总的加载超时时间是 10s,我在这打了个日志,发现笔者轻松连接超时(笔者不在校内,通过 VPN 连接校内的服务器很慢)。


延长了这两块的超时时间,再启动 Awards,不再提示 “Contest not done updating” 了,数据可以正常加载。埋点发现连接、加载确实远超原先的 timeout 时长:

所以本次滚榜出锅都怪:Event Feed 大(这次比赛的 Event Feed 有 13 MB); icpctools 给的 timeout 时间不够长。一整个无语住了。
JSON 解析优化
这是后话了,但放在前面说。这是在测试解决方案中遇到的另一个问题。
我定位问题之后首先想的是本地预载 Event Feed,通过代理的方式避免 icpctools 那边加载的时间过长,也不用动 icpctools 源代码。结果令人惊讶:连接延迟几乎消除了,但是 read 的时间竟然几乎没有变化,还是大约 8 秒。使用 IntelliJ Profiler 定位了一下加载线程的热点,发现是 icpctools 手搓的 JSON Parser 在解析字符串时进行了大量的 StringBuilder 分配:

简单做了一下 StringBuilder 实例复用,性能大幅提升,时间缩减到仅需 1~2 秒:

解决方案
根因还是对部分 event feed 来说,网络 timeout 时间不太够。解决方案有以下三种:
检查网络质量
可能是批话但也许有用。像笔者一样使用校园 VPN 访问确实缓慢,容易导致 timeout 发生。直接用校园网或者尝试直连服务器(避开校园网限速),或许会有用。
本地预载 Event Feed 并代理
可以先把 Event Feed 拉下来,这样 icpctools 直接访问本地代理服务器,返回完整的 Event Feed,转嫁下载 Event Feed 的时间到代理服务器启动时间上。
实现上也还算比较简单,只要将 icpctools 的比赛 URL 换成本地代理服务器的即可。
用 Codex 生成的本地代理服务器及使用说明:cds-local-proxy
笔者实测了一下是有效的。
使用修改版的 icpctools
如果以上方案都不适用,那只能通过修改 icpctools 源码来解决了(例如超大 Event Feed,解析时间超过 10 秒)。
我已经修改好并编译了一个版本(JDK 21),可以从 这里 下载。
连接时长宽限到 10 秒,加载时长 30 秒,同时做了 JSON 解析优化,应该可以满足绝大部分 Event Feed 的需求。







