Skip to content

The Docker health check does not catch ConnectionRefusedError #3381

Description

@adamtheturtle

What happens

src/mock_vws/_flask_server/healthcheck.py catches three exception types:

    except TimeoutError, http.client.HTTPException, socket.gaierror:
        return False

ConnectionRefusedError is not among them, and is not a subclass of any of them. So when nothing is listening on the port yet, the function raises rather than returning False:

healthcheck against dead port 5399 -> UNCAUGHT ConnectionRefusedError [Errno 61] Connection refused

This is the state during container start-up, before Flask binds the port, which the Dockerfile's --start-period=5s exists to cover.

Why it matters

Behaviour is unaffected: Docker treats any non-zero exit as unhealthy, and an uncaught exception exits non-zero, so the container is correctly marked unhealthy either way. What is lost is the intent — the except clause is clearly meant to turn connection failures into False — and a traceback appears in the health check probe output for every probe until the app is up. #3147 added dumping of Health.Log on failure specifically so that output is readable, so noise there has a cost.

The file is listed in [tool.coverage] run.omit, so nothing imports it during the test run and the 100% coverage gate does not apply to it.

Suggested resolution

Add ConnectionRefusedError to the caught types, or catch OSError, which covers TimeoutError, socket.gaierror and ConnectionRefusedError together and leaves only http.client.HTTPException to list separately.

Worth a moment's thought about the coverage omission while there. The file is small and its one function is testable with a port nothing is listening on — which is how the behaviour above was observed — so it could come out of run.omit rather than being the one part of the codebase outside the gate.

Note

The check probes /some-random-endpoint and accepts 404, 401 or 403. It gets a 401, because the request is unauthenticated and the auth validator runs before anything else. That is why it does not trip over #3368, which needs a signed request to an unrouted path. Worth keeping in mind when fixing that issue, so the health check keeps working.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions