连接二十分钟后,提醒出现了。它下面的计时器从昨天早上开始走,或者从账号上次认证时开始走,而提醒里没有任何文字说明是哪一个。这就是症状:一条绑在别的东西上、而不是绑在眼前这条连接上的提醒。

解决办法不是更好的提醒。而是先确定需求指的是哪一只钟,再把计时器绑到那只钟里存在的对象上。

什么被触发了,它又在数什么

三个可能的来源会产生同一条消息。日历规则在固定的本地时间运行,对隧道一无所知。客户端里的计数在任意连接存续期间不断累加,并能跨越重连。网关策略在服务端让会话过期,并先推送一条警告。

只有后两个与某个会话有关,而它们彼此也不一致。客户端计数衡量的是本地进程观察到的东西;网关衡量的是它认证并计费的东西。休眠、网络切换或观察者重启可以让两者相差数小时,这正是警告与强制到期逐渐分离的原因。

一个需求背后的三只钟

先把你真正想要的那句话写下来。在网关结束这条连接之前发出警告,与在一天连接时间之后提醒,并不是同一个需求。它也不等于报告“这个会话已经持续 23 小时”。每个版本命名的是不同的事件,也要由不同的组件来回答。

然后检查数据是否存在。本地开始时间戳能在不依赖服务端的情况下给出会话年龄。网关策略能给出剩余寿命,但前提是网关愿意暴露它。用量报告给的是事后的时长,它无法驱动提前通知。

把计时器绑到会话上,而不是账号上

先选对象。一个账号可能挂着多台设备和多个同时进行的会话,所以按用户名绑定的提醒说不清是哪条连接到了那个年龄。把它绑到配置文件加上那一次激活上,并在激活结束时取消待发的提醒。

Evidence you can readWhat it establishesWhere it misleads
Connection state changes to activeA session started nowDoes not carry the gateway's own session identifier
Duration reported at disconnectTotal service time for that sessionArrives too late for any advance notice
Interim usage updateTime accounted up to that updateStaleness equals the update interval
Local timestamp taken at activationAge of your observationResets whenever the observer restarts

那张表是给你自己的通知器用的契约,不是任何客户端回调 API 的说明。已安装产品对连接状态记录了什么,才是应当绑定的来源;如果它只暴露认证事件,就大声说出来,并接受更粗的行为,而不是去推断一个会话。

用很短的阈值去验证

永远不要用一整天的等待去调试一整天的计时器。加一个可配置的阈值,把它设成三十秒,然后按顺序跑四种转换:连接、等待、断开、重连。每种转换应当只产生一次状态变化,而重连应当安排新的期限,而不是继承旧的。

# current connection state and profile identity
nmcli -f GENERAL.STATE,GENERAL.CONNECTION connection show work

# the notifier unit you wrote: does it restart the deadline or resume it?
journalctl --user -u vpn-timer.service --since "-5 min" -o cat

看时间戳,而不是看措辞。带着上一个会话开始时间到达的提醒绑错了对象;同一个期限到来两次的提醒,说明已发送标志从来没有被持久化。

休眠、重连与错过的那次投递

休眠会让两种朴素设计同时失效。单调倒数在机器睡眠时停住,挂钟期限继续移动,而睡眠期间触发的计时器会延迟送达或干脆不送达。先决定你的需求要哪种行为,然后让机器跨过阈值睡一觉再确认。

投递也值得同样的谨慎。如果提醒无法显示,就先排队,并在最终显示之前重新确认目标会话仍然存在;否则用户会被提醒一条已经结束的连接。强制断开和凭据续期留在这条路径之外,因为一条悄悄改变连接命运的提醒,是披着通知外衣的策略决定。

相关文章