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.

Activity

  1. added
    bug[medium]Something isn't working, may be fixed after the release
    on Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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