系统时间回拨后超时变长:用单调时钟共享截止时间
一个导出请求给总流程 5 秒,连接、读取和重试分别重新获得 5 秒,用户可能等远超过约定的时间。如果还用系统时间计算差值,校时回拨会进一步拉长等待。进入请求时用单调时钟计算一次截止时间,每进入下一阶段都从它扣减。
1. 不改系统时间,直接注入时钟
保存为 budget_lab.py,运行 python budget_lab.py。
import time
from dataclasses import dataclass
@dataclass
class Clock:
wall: float = 100.0
steady: float = 200.0
def deadline(seconds, clock=time.monotonic):
expires = clock() + seconds
def remaining():
return max(0.0, expires - clock())
return remaining
clock = Clock()
wall_left = deadline(5, lambda: clock.wall)
left = deadline(5, lambda: clock.steady)
clock.wall += 2
clock.steady += 2
assert left() == 3
print("after connection:", left(), "seconds left")
clock.wall -= 60
clock.steady += 1
assert wall_left() == 63 and left() == 2
print("after simulated rollback: wall=", wall_left(), ", monotonic=", left(), sep="")
clock.steady += 2
assert left() == 0
print("before retry:", left(), "seconds left")
本机实测输出如下。
after connection: 3.0 seconds left
after simulated rollback: wall=63.0, monotonic=2.0
before retry: 0.0 seconds left
2. 每次进入阻塞操作前都查预算
先算剩余时间,已耗尽就结束请求。否则把剩余时间和单阶段上限取最小值,再传给库。退避等待也要计入预算,醒来后重新计算。若库的 read timeout 表示两次读取之间的等待,持续流入少量数据可以一直延后结束,这时需要在上层加总时限或可取消的异步调用。
墙上时间适合日志和跨进程协议。单调时钟的参考点未定义,适合在本进程按差值计时。跨进程传预算时定义清楚时延和计时方式,避免把本地 monotonic 的绝对值写到数据库当作时间戳。
小结
同一次请求共享单调时钟的截止时间。验收时覆盖系统时间前跳、回拨和最后一次重试之前预算耗尽。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| Python time | https://docs.python.org/3.11/library/time.html#time.monotonic | 单调读数、参考点和系统校时 |
核验说明
- Python 3.11.9 实测注入时钟的预算分支。回拨由变量模拟,未修改机器时间。