Skip to content

Docker wait timeout truncates to 0 when time_limit < 200ms (instant JE) #58

Description

@as535364

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug[medium]Something isn't working, may be fixed after the release

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions