Skip to content

Set an explicit gunicorn worker timeout in the website entrypoint #304

Description

@alexeygrigorev

Set an explicit gunicorn worker timeout in the website entrypoint

PM-groomed specification

Reviewed 2026-09-02 against origin/main at
2d3c28d1b8d1c2f3844f47f664945b42849d5eb9 (tree
f7af08192c09361a3d064e8f8282b11d568ff211). This is an XS target-native
deployment hardening issue. It is independent of the unrelated dirty local
worktree edits and does not authorize provider or production changes.

Product outcome

The web branch of the website's target-owned entrypoint.sh gives Gunicorn
an explicit 90-second worker timeout. A slow configured outbound call therefore
reaches its own bounded client timeout first and can follow its ordinary safe
failure path instead of being killed at Gunicorn's implicit 30-second default.

The value is evidence-based: the largest currently configured application
outbound timeout is Datamailer's 60 seconds, and the selected 90 seconds adds a
10-second completion/logging margin. This matches the reviewed CMP precedent in
e49c575ce3f567cc14a8f66a89d3ccaca3bfe2f4 without copying CMP files.

Current outbound-timeout inventory

Surface Configured timeout Runtime relevance
course_management/datamailer/client.py (DATAMAILER_TIMEOUT_SECONDS, default from website/settings/base.py) 60s Largest application HTTP client; deployment currently leaves the Datamailer URL/API key disabled. An explicit future environment override above 80s is outside this issue and must be reviewed together with the deployment timeout contract.
content_sync/course_repository_sync.py GitHub codeload fetch 30s Durable content-sync job/worker, not the Gunicorn web request path; included to show it is below 90s.
courses/validators/custom_url_validators.py injected HEAD/GET transport 3s Controlled/test transport only; production callers intentionally perform no synchronous user-controlled HTTP.
course_management/observability/cloudwatch_dashboard.py connect 1s/read 3s Staff Studio dashboard request; below 90s.
S3 development-content transport none explicitly configured Management-command transport; its SDK default is not a reviewed bound and is a separate hardening concern, not a reason to invent a Gunicorn value.

CI, E2E, deployment probes, local Git subprocesses, and test harness timeouts
are tooling/process budgets, not application outbound clients served by this
entrypoint. The website has no Relay client yet; the specifications require
future Relay network work to run only after commit through a durable worker.

Scope

  • Add the literal --timeout 90 pair to the Gunicorn command in the web) case
    of target-owned entrypoint.sh.
  • Extend core.source_policy.validate_gunicorn_entrypoint so it parses that
    web command and requires exactly one numeric --timeout value of 90; a
    missing, duplicated, malformed, interpolated, or lower timeout fails closed.
    Existing access-log privacy checks remain unchanged.
  • Add a focused regression to the existing source-policy test that reads the
    real entrypoint.sh, checks the parser, and mutates/removes the timeout to
    prove rejection. The regression must derive the safety assertion from
    course_management.datamailer.client.DEFAULT_TIMEOUT_SECONDS and assert
    gunicorn_timeout >= DEFAULT_TIMEOUT_SECONDS + 10, so a future change to the
    reviewed Datamailer default cannot silently invalidate the relationship.

Explicit non-goals

  • Do not copy CMP's Dockerfile change or courses/tests/test_deploy_config.py;
    the website's deployment contract is entrypoint.sh plus core.source_policy.
  • No Dockerfile/CMP source-pin/adoption-manifest change, container command
    redesign, worker/job retry change, or new timeout environment variable.
  • No change to Datamailer enablement, client behavior, Relay integration,
    email purpose/sender, S3/CloudWatch/URL-validator timeout policy, business
    semantics, provider/network calls, production data, deployment, commit, or
    push.
  • A deploy-time DATAMAILER_TIMEOUT_SECONDS override above 80 seconds is not
    silently accepted by this issue; it requires a separately reviewed change to
    the timeout contract/value.

Dependencies and ownership

This issue is target-owned and independently implementable. #305/#50 own the
Datamailer disablement/retirement and future Relay boundary; they are related
coordination, not prerequisites. No owner decision is required for this
fixed, source-evidenced 90-second hardening.

Acceptance criteria

  • entrypoint.sh's web) Gunicorn command contains exactly one literal
    --timeout 90; the worker) command is unchanged and no Dockerfile CMD is
    used as the source of truth.
  • validate_gunicorn_entrypoint extracts/parses the web command and fails
    closed for absent, duplicate, malformed, interpolated, or lower timeout
    values while preserving the existing access-log privacy contract.
  • The focused regression proves the real command passes, each timeout
    mutation fails, and the parsed 90 seconds is at least the current
    Datamailer default plus the 10-second margin. It also proves the reviewed
    30/3-second application client bounds do not exceed the selected value.
  • make migrations-check, the focused Django/source-policy tests, and the
    graph-selected uv-backed quality/type/CI/container checks pass. No
    migration is expected; a discovered model or deployment-schema change stops
    the issue for re-grooming.
  • The engineer leaves an uncommitted frozen handoff with exact base/head,
    plan digest, changed-file list, tests, and the timeout inventory. The
    independent tester recomputes the plan and verifies all criteria before PM
    acceptance.

Verification plan and browser evidence

This is an internal entrypoint/source-policy change with no product-page or
rendered error-surface change. The tester runs focused Django and applicable
control-plane/container checks plus the graph-selected backend Playwright tier;
screenshots are not_applicable only when the recomputed graph confirms no
render inputs changed. No browser/provider/network session is needed for the
acceptance claim.

After tester PASS, PM posts product acceptance. Only then may the engineer
create a focused commit with Closes #304; the orchestrator performs the
normal local no-ff merge/push and on-call observes resulting CI. No pull
request, deployment, or live send is part of this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Nice to have or laterbugSomething isn't workinginfraArea: infraoperationsArea: operationstestingArea: testing

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions