Claude:
executor/sandbox.py caps each case with
exit_status = self.client.wait(
container,
timeout=5 * self.time_limit // 1000,
)
time_limit is in ms, so any limit under 200ms floors to timeout=0. docker-py 7.1.0 passes the value straight into requests, and urllib3 2.x refuses it on the spot:
ValueError('Attempted to set connect timeout to 0, but the timeout cannot be set to a value less than or equal to 0.')
I reproduced this with the pinned deps (requests 2.33.1, urllib3 2.6.3): the call fails in about 3.5ms. The ValueError lands in the broad except in Sandbox.run(), becomes JudgeError, and the case is judged JE. So a problem with a 100ms time limit gets JE on every case, instantly. Nothing upstream prevents such a problem from existing: the backend stores time_limit as a required int with no minimum, and our own Meta.Task.timeLimit is a bare int. The backend floor is tracked in Normal-OJ/Back-End#361.
Some archaeology on where the formula comes from, because nobody ever chose it:
- dcc8421 (2019-12-03, "migrate to low level api and fix some hardcode") introduced
timeout=5 * self.time_limit / 1000. That was in the repo's first weeks, three weeks before dispatcher/ existed.
- b287115 (2020-05-25, "Correct except syntax") changed the
/ to // in an otherwise unrelated fix. That is where the truncation started.
- Since then the file has only been moved around (module split in 2022, the executor/ rename this month). The line itself was never revisited.
This matters more after #57: the dispatcher-level 300s timer is gone (the PR description covers why), which leaves this line as the only per-case wall-clock bound in the runner.
Suggested fix: the multiplicative shape is wrong regardless of the constant. Real wall time is container startup plus the time limit plus teardown, so the relation is additive, and something like time_limit // 1000 + MARGIN_SEC fits what actually happens. The current formula gives a 100ms problem half a second (before truncation ate even that) and a 10s problem 50 seconds, and neither number means anything. We need to pick the margin value. The compile path currently ends up with 5 * 20000 // 1000 = 100s, worth picking on purpose while we are in there.
Claude:
executor/sandbox.pycaps each case withtime_limitis in ms, so any limit under 200ms floors totimeout=0. docker-py 7.1.0 passes the value straight into requests, and urllib3 2.x refuses it on the spot:I reproduced this with the pinned deps (requests 2.33.1, urllib3 2.6.3): the call fails in about 3.5ms. The ValueError lands in the broad
exceptinSandbox.run(), becomesJudgeError, and the case is judged JE. So a problem with a 100ms time limit gets JE on every case, instantly. Nothing upstream prevents such a problem from existing: the backend storestime_limitas a required int with no minimum, and our ownMeta.Task.timeLimitis a bareint. The backend floor is tracked in Normal-OJ/Back-End#361.Some archaeology on where the formula comes from, because nobody ever chose it:
timeout=5 * self.time_limit / 1000. That was in the repo's first weeks, three weeks beforedispatcher/existed./to//in an otherwise unrelated fix. That is where the truncation started.This matters more after #57: the dispatcher-level 300s timer is gone (the PR description covers why), which leaves this line as the only per-case wall-clock bound in the runner.
Suggested fix: the multiplicative shape is wrong regardless of the constant. Real wall time is container startup plus the time limit plus teardown, so the relation is additive, and something like
time_limit // 1000 + MARGIN_SECfits what actually happens. The current formula gives a 100ms problem half a second (before truncation ate even that) and a 10s problem 50 seconds, and neither number means anything. We need to pick the margin value. The compile path currently ends up with5 * 20000 // 1000 = 100s, worth picking on purpose while we are in there.