TensorRT model download fails after OAuth2 token acquisition timeout

Problem

In an event‑driven inference pipeline, each job pulls a TensorRT engine from a secured model registry. The registry requires an OAuth2 access token. After a recent update to the token‑service, the OAuth2 flow intermittently times out, causing the model download request to abort. The symptom appears as failed inference jobs that never receive a model artifact.

Root Cause

The OAuth2 client used by the TensorRT loader performs a synchronous POST /token request with a hard‑coded 5 second timeout. The token service, now behind an API‑gateway that adds additional latency (TLS termination, rate‑limiting, and request tracing), occasionally exceeds this window. When the timeout expires, the client throws a requests.exceptions.Timeout exception, aborts the download, and propagates the error up to the inference runtime.

Key contributing factors:

  • Network path includes an extra hop (gateway) that adds ~150 ms of TLS handshake plus ~200 ms of request inspection.
  • The token service enforces a per‑client rate limit of 10 rps; bursty queue consumption can trigger 429 responses, which the client treats as a failure instead of retrying.
  • The TensorRT loader does not retry token acquisition; it fails fast.

Debug

Step‑by‑step investigation performed in the production environment.

Log evidence


2026-10-02 14:03:12,874 ERROR token_service - Token request timed out after 5 seconds (url=https://auth.example.com/oauth2/token)
2026-10-02 14:03:12,875 ERROR model_loader - Failed to download model 'resnet50.trt' from https://registry.example.com/models/resnet50.trt: Token acquisition failed
2026-10-02 14:03:12,876 INFO job_manager - Inference job id=42ab7c aborted

Network trace


$ sudo tcpdump -i eth0 -nn -s 0 -w token.pcap host auth.example.com and port 443
# Capture shows TLS handshake (1.2 ms) + HTTP POST (3.8 s) → total 5.0 s before timeout

Command‑line token request


$ time curl -X POST -d "grant_type=client_credentials&client_id=svc&client_secret=***" \
    https://auth.example.com/oauth2/token -o /dev/null -w "%{http_code} %{time_total}\n"
200 5.23

The same request from a developer laptop returns 200 0.42, confirming the extra latency in the production path.

Configuration inspection


# model_loader/config.yaml (before)
token_endpoint: https://auth.example.com/oauth2/token
request_timeout_seconds: 5
max_retries: 0

Solution

Update the OAuth2 client to use a resilient acquisition strategy:

Code changes

Introduce exponential back‑off retries and increase the timeout to accommodate the gateway latency.

# model_loader/oauth_client.py (before)
def get_access_token():
    resp = requests.post(
        CONFIG['token_endpoint'],
        data={'grant_type': 'client_credentials',
              'client_id': CONFIG['client_id'],
              'client_secret': CONFIG['client_secret']},
        timeout=CONFIG['request_timeout_seconds']
    )
    resp.raise_for_status()
    return resp.json()['access_token']
# model_loader/oauth_client.py (after)
import time
import random
import requests

def get_access_token():
    max_retries = 3
    backoff_base = 1  # seconds
    timeout = max(5, CONFIG.get('request_timeout_seconds', 5))

    for attempt in range(1, max_retries + 1):
        try:
            resp = requests.post(
                CONFIG['token_endpoint'],
                data={'grant_type': 'client_credentials',
                      'client_id': CONFIG['client_id'],
                      'client_secret': CONFIG['client_secret']},
                timeout=timeout
            )
            resp.raise_for_status()
            return resp.json()['access_token']
        except (requests.exceptions.Timeout, requests.exceptions.HTTPError) as e:
            if isinstance(e, requests.exceptions.HTTPError) and resp.status_code == 429:
                # Respect Retry‑After header if present
                retry_after = int(resp.headers.get('Retry-After', 0))
                sleep_time = max(retry_after, backoff_base * (2 ** (attempt - 1)))
            else:
                sleep_time = backoff_base * (2 ** (attempt - 1))

            # Add jitter to avoid thundering herd
            jitter = random.uniform(0, 0.5)
            time.sleep(sleep_time + jitter)

            if attempt == max_retries:
                raise

Configuration update

# model_loader/config.yaml (after)
token_endpoint: https://auth.example.com/oauth2/token
request_timeout_seconds: 10   # increased to cover gateway latency
max_retries: 3
backoff_base_seconds: 1

Deployment adjustments

Rebuild the Docker image and roll out the new version using the existing CI/CD pipeline. No changes to the model registry are required.

Verify

Validate the fix with both unit tests and an end‑to‑end smoke test that simulates a burst of queue events.

Log verification


2026-10-02 14:15:03,112 INFO oauth_client - Token acquired successfully (expires_in=3600)
2026-10-02 14:15:03,115 INFO model_loader - Downloaded model 'resnet50.trt' (size=42.3 MiB)
2026-10-02 14:15:03,120 INFO job_manager - Inference job id=7f9d2a completed

Metrics

Metric Before After
Token acquisition timeout errors (rate) 12 /min 0 /min
Model download retries 5 /min 0 /min
Inference job success rate 93 % 99.8 %

Functional test


$ python -m pytest tests/integration/test_model_download.py::test_successful_download -q
.
1 passed in 2.34s

Prevent

  • Configure the token client with a configurable timeout and retry policy; avoid hard‑coded values.
  • Instrument the token acquisition path with Prometheus counters (oauth_token_requests_total, oauth_token_failures_total) and latency histograms.
  • Set up an alert on oauth_token_failures_total > 5 % over a 5‑minute window.
  • Coordinate with the API‑gateway team to keep TLS handshake latency under 200 ms; if it exceeds, adjust the client timeout accordingly.
  • Document the expected latency budget in the service‑level objective (SLO) for token acquisition.

Related Topic Hub: Model Serving Troubleshooting Hub

FAQ

  1. Why does increasing the timeout alone not fix the issue? The token service can also return HTTP 429 when rate‑limited. Without retries, the client treats 429 as a fatal error, leading to the same failure.
  2. Can I cache the access token across inference jobs? Yes. The OAuth2 client can store the token in memory and refresh only when expires_in is near expiry. This reduces request volume on the token endpoint.
  3. What if the gateway adds additional latency in the future? Keep the timeout configurable via environment variables and monitor the oauth_token_latency_seconds histogram to detect regressions early.
  4. Is it safe to increase max_retries to a high number? Excessive retries can amplify load during an outage. A small retry count (2‑3) with exponential back‑off and jitter balances resilience and load.
  5. Do I need to modify the model registry client? No. The registry client only requires a valid bearer token; fixing token acquisition resolves the downstream download failures.