Skip to main content
The async_fetch module is the core of the tif1 data fetching infrastructure. It provides async HTTP capabilities with HTTP/2 multiplexing, retry logic, circuit breaker protection, and multi-CDN fallback.

Overview

The async fetch system is built on niquests (a modern, actively-maintained fork of requests) and provides:
  • HTTP/2 Multiplexing: Single TCP connection for multiple parallel requests, which reduces latency
  • Connection Pooling: Connection reuse with configurable pool sizing and keep-alive
  • Parallel JSON Parsing: Offloads JSON parsing to thread pools to avoid blocking the async event loop
  • Rate Limiting: Semaphore-based concurrency control to prevent CDN overload
  • Automatic Retries: Exponential backoff with jitter and circuit breaker protection
  • Multi-CDN Fallback: Automatic failover across multiple CDN sources (jsDelivr, Hugging Face buckets, StaticDelivr, custom CDNs)
  • Smart Caching: SQLite-backed cache with in-memory LRU layer for hot data
  • Payload Validation: Optional pydantic-free fetch-path validation for data integrity
  • Resource Management: Automatic cleanup of connections, thread pools, and process pools
Most users do not need to interact with this module directly. The Session class handles async fetching automatically through methods like laps_async(), get_fastest_laps_tels_async(), and load(). This documentation is for advanced users who need fine-grained control over fetching behavior or want to understand the performance characteristics.

Architecture

Data Flow

The async fetch system follows this execution path:
  1. Cache Check: Query in-memory LRU cache, then SQLite cache (if enabled)
  2. Network Fetch: If cache miss, fetch from CDN with HTTP/2 multiplexing
  3. JSON Parsing: Offload parsing to thread pool (or process pool for non-telemetry data)
  4. Validation: Optional fetch-path validation based on payload type
  5. Cache Write: Persist to SQLite and in-memory cache (if enabled)
  6. Return: Deliver parsed dictionary to caller

Thread Safety

All async fetch operations are thread-safe:
  • HTTP session uses connection pooling with thread-safe access
  • Thread pool executor is lazily initialized with lock protection
  • Circuit breaker uses reentrant locks for nested calls
  • CDN manager tracks failures with atomic counters

Resource Lifecycle

Resources are managed automatically:
  • HTTP Session: Created on first use, reused across all requests, closed on shutdown
  • Thread Pool: Created on first use, sized based on max_workers config
  • Process Pool: Optional, created on first use if json_parse_workers > 0
  • Cleanup: All resources cleaned up via atexit handlers or explicit cleanup_resources() call

Core Functions

fetch_json_async

Asynchronously fetch and parse JSON from a CDN with caching, retry logic, circuit breaker protection, and multi-CDN fallback. This is the foundational async primitive used by all higher-level data loading methods.

Parameters

int
required
Season year (for example, 2025, 2024). Must be a valid F1 season year with available data.
str
required
Grand Prix name (for example, “Monaco Grand Prix”, “Bahrain Grand Prix”). Must match the exact event name in the schedule data.
str
required
Session name (for example, “Race”, “Qualifying”, “Practice 1”, “Sprint”). Must match the exact session name in the schedule data.
str
required
Path to JSON file relative to the session directory. Common paths:
  • "laps.json" - All lap times for the session
  • "drivers.json" - Driver metadata (names, numbers, teams)
  • "weather.json" - Weather data throughout the session
  • "rcm.json" - Race control messages (flags, safety car, etc.)
  • "laps/VER.json" - Lap times for a specific driver (for example, Verstappen)
  • "telemetry/VER_1_tel.json" - Telemetry for driver VER, lap 1
int | None
default:"None"
Maximum number of retries for failed requests. If None, uses global config value (default: 3).
  • Set to 0 to disable retries for the fastest cold-start fetch
  • Set to 3-5 for production use (balance between reliability and latency)
  • Set to 10+ for unreliable networks (maximum reliability)
When ultra_cold_skip_retries=True in config, this is automatically set to 0 for the first fetch.
int | None
default:"None"
Request timeout in seconds. If None, uses global config value (default: 30).
  • Set to 10-15 for fast networks (minimize latency)
  • Set to 30-60 for typical networks (balance between speed and reliability)
  • Set to 120+ for slow networks (maximum reliability)
bool
default:"True"
If True, check cache (in-memory + SQLite) before making network requests. Set to False to force fresh data from CDN.Performance impact: Cache hits are 1000x faster than network fetches (~0.1ms vs ~100ms).
bool
default:"True"
If True, persist successful network responses to cache (both in-memory and SQLite). Set to False for ephemeral data or testing.Note: Automatically disabled when ci_mode=True in config.
bool
default:"True"
If True, run fetch-path validation on the fetched data before returning. The fetch path is pydantic-free; public validate_* APIs remain for compatibility. Validation rules depend on the payload type:
  • rcm.json: Validate race control messages (strict=False, normalize=False)
  • weather.json: Validate weather data (strict=False)
  • *_tel.json: Validate telemetry data (strict=False, normalize=False)
  • laps.json / laptimes.json: Validate lap times (if validate_lap_times=True)
Set to False for ~10-20% faster parsing when data integrity is guaranteed.

Returns

dict[str, Any]
Parsed JSON data as a dictionary. Never returns None - raises an exception on error instead.The structure depends on the requested path:
  • laps.json: {"laps": [{"lap": 1, "time": "1:23.456", ...}, ...]}
  • drivers.json: {"drivers": [{"code": "VER", "name": "Max Verstappen", ...}, ...]}
  • weather.json: {"weather": [{"time": "2025-05-25T14:00:00", "temp": 25.5, ...}, ...]}
  • *_tel.json: {"tel": {"rpm": [15000, 15100, ...], "speed": [300, 305, ...], ...}}

Raises

exception
Raised when network request fails after all retry attempts. Context includes:
  • url: The URL that failed
  • status_code: HTTP status code (if available)
Common causes:
  • Network connectivity issues
  • CDN downtime (all CDN sources failed)
  • Circuit breaker open (too many recent failures)
  • Timeout exceeded
exception
Raised when the requested data does not exist (HTTP 404). Context includes:
  • year: Season year
  • event: Grand Prix name
  • session: Session name
Common causes:
  • Invalid year/event/session combination
  • Data not yet available for future events
  • Typo in event or session name
exception
Raised when JSON parsing or validation fails. Context includes:
  • reason: Description of the validation failure
Common causes:
  • Corrupted JSON data
  • Unexpected data structure
  • Validation schema mismatch

Examples

Performance Characteristics

  • Cache hit: ~0.1-0.5ms (in-memory) or ~1-5ms (SQLite)
  • Cache miss (single request): ~50-150ms (network + parsing)
  • Cache miss (parallel requests): ~200-400ms for 20 requests (HTTP/2 multiplexing)
  • JSON parsing overhead: ~5-20ms for typical payloads, ~50-200ms for large telemetry payloads
  • Validation overhead: ~5-15% additional parsing time (varies by payload type)

fetch_multiple_async

Fetch multiple JSON files in parallel with optimized concurrency control and graceful error handling. This is the primary function for bulk data loading operations. Use it to fetch all driver laps or all telemetry files for a session.

Parameters

list[tuple[int, str, str, str]]
required
List of (year, gp, session, path) tuples to fetch in parallel. Each tuple represents a single fetch operation.Example:
Performance tip: Group related requests together for optimal HTTP/2 multiplexing.
bool
default:"True"
If True, check cache before making network requests for each item. Cache hits are served immediately without consuming concurrency slots.
bool
default:"True"
If True, persist successful network responses to cache. Automatically disabled when ci_mode=True.
bool
default:"True"
If True, run fetch-path validation on each fetched payload.
int | None
default:"None"
Maximum number of retries per request. If None, uses global config value (default: 3).Note: Each request retries independently. A failed request does not block other requests.
int | None
default:"None"
Request timeout in seconds per request. If None, uses global config value (default: 30).
int | None
default:"None"
Maximum number of concurrent network requests. If None, uses global config value (default: 22).Tuning guide:
  • 10-20: Conservative, good for shared networks or rate-limited CDNs
  • 20-50: Balanced, good for most use cases
  • 50-100: Aggressive, good for dedicated networks and high-throughput scenarios
  • 100+: Ultra-aggressive, only for specialized scenarios (for example, telemetry prefetching with telemetry_prefetch_max_concurrent_requests)
Note: Higher concurrency does not always mean faster. The optimal value depends on network bandwidth, CDN rate limits, and CPU capacity for JSON parsing.

Returns

list[dict[str, Any] | None]
List of parsed JSON dictionaries or None for failed requests, in the same order as the input requests list.Graceful degradation: Failed requests return None and are logged as warnings. The function never raises exceptions - it always returns a list of the same length as the input.Special case: DataNotFoundError (404) is converted to None without logging a warning. Missing data is often expected (for example, driver did not participate in session).

Error Handling

Unlike fetch_json_async, this function does not raise exceptions. All errors are handled gracefully:
  • NetworkError: Logged as warning, returns None for that request
  • DataNotFoundError (404): Silently returns None (expected for missing data)
  • InvalidDataError: Logged as warning, returns None for that request
  • Other exceptions: Logged as warning, returns None for that request
This design allows partial success. When 18 of 20 requests succeed, the result contains 18 valid values and 2 None values.

Examples

Performance Characteristics

  • All cache hits: ~1-10ms total (parallel cache reads)
  • All cache misses (20 requests): ~200-400ms (HTTP/2 multiplexing + parallel parsing)
  • All cache misses (100 requests, 50 concurrent): ~1-2s (batched with semaphore)
  • Mixed hits/misses: Proportional to miss rate (cache hits do not consume concurrency slots)
Scaling behavior:
  • Linear scaling up to max_concurrent_requests limit
  • Beyond limit, requests are queued and processed in batches
  • HTTP/2 multiplexing provides ~3-5x speedup vs sequential fetching
  • Optimal concurrency depends on network bandwidth and CDN rate limits
Memory usage:
  • Each request consumes ~1-10KB for typical payloads
  • Telemetry payloads can be 100KB-1MB each
  • Peak memory = max_concurrent_requests × average_payload_size
  • Example: 50 concurrent × 500KB average = ~25MB peak memory

fetch_with_rate_limit

Execute an async function with rate limiting using a semaphore for concurrency control. This is a low-level utility for building custom async workflows with fine-grained concurrency management.

Parameters

Callable
required
Async function (coroutine function) to execute. Must be an async def function, not a regular function.
Any
Positional arguments to pass to coro_func.
asyncio.Semaphore | None
default:"None"
Optional semaphore for rate limiting. If None, creates a new semaphore based on max_concurrent_requests config (default: 22).Use cases:
  • Pass a shared semaphore to coordinate concurrency across multiple operations
  • Create a custom semaphore with specific limits for fine-grained control
  • Leave as None for automatic concurrency management
Any
Keyword arguments to pass to coro_func.

Returns

Any
The return value from coro_func execution. Type depends on what coro_func returns.

Raises

Any
Any exception raised by coro_func is propagated to the caller. No exception handling is performed by this function.

Examples

This is a utility function for custom concurrency control. Most users should use fetch_multiple_async(), which handles rate limiting automatically. Use this function only for fine-grained concurrency control or custom async workflows.

When to Use

  • Custom async workflows: Building specialized data fetching pipelines
  • Shared concurrency limits: Coordinating multiple async operations with a single semaphore
  • Fine-grained control: Need precise control over concurrency beyond what fetch_multiple_async provides
  • Integration: Integrating tif1 fetching with other async operations in the application

When NOT to Use

  • Standard bulk fetching: Use fetch_multiple_async() instead
  • Single requests: Use fetch_json_async() directly
  • Simple use cases: The higher-level functions handle rate limiting automatically

Resource Management

The async fetch system manages several types of resources that need proper cleanup:
  • HTTP Session: Persistent connection pool for network requests
  • Thread Pool Executor: Worker threads for JSON parsing and blocking operations
  • Process Pool Executor: Optional worker processes for CPU-intensive JSON parsing
  • Circuit Breaker: Failure tracking state for preventing cascading failures
All resources are cleaned up via atexit handlers when the Python process exits. For long-running applications, or for explicit control, use the functions below.

cleanup_resources

Clean up all async resources including HTTP sessions, thread pools, and process pools. This is the recommended way to shut down the async fetch system cleanly.

Behavior

  • Closes the shared HTTP session and releases all connections
  • Shuts down the thread pool executor (waits for pending tasks to complete)
  • Shuts down the process pool executor if enabled (waits for pending tasks)
  • Handles cleanup errors gracefully - logs failures without raising exceptions
  • Safe to call multiple times (idempotent)
  • Thread-safe (uses locks to prevent concurrent cleanup)

When to Call

  • Application shutdown: Call before the application exits
  • Test teardown: Call in test cleanup to prevent resource leaks
  • Long-running processes: Call periodically when many sessions are created and destroyed
  • Resource constraints: Call to release connections or memory

Examples

After calling cleanup_resources(), the next async fetch operation will recreate all resources. This is safe but incurs initialization overhead (~10-50ms). Do not call this in hot paths or between individual requests.

close_session

Close the shared niquests HTTP session and release all connections. The session will be automatically recreated on the next fetch operation.

When to Use

  • Explicit connection management: To force connection closure
  • Network changes: After network configuration changes (for example, VPN connect/disconnect)
  • Testing: To reset connection state between tests
  • Resource constraints: To free up connection pool resources
This only closes the HTTP session. Thread pools and process pools remain active. Use cleanup_resources() for complete cleanup.

close_executor

Shut down the thread pool executor used for async operations. Waits for all pending tasks to complete before shutting down. The executor will be automatically recreated on the next fetch operation.

When to Use

  • Thread pool management: To control the thread pool lifecycle
  • Resource constraints: To free up thread resources
  • Testing: To reset executor state between tests
This only closes the thread pool executor. HTTP session and process pool remain active. Use cleanup_resources() for complete cleanup.

Performance Characteristics

The async fetch system is optimized for:
  • HTTP/2 multiplexing: Single connection for multiple requests
  • Connection pooling: Reuses connections across requests
  • Parallel JSON parsing: Offloads JSON parsing to thread pool
  • Rate limiting: Prevents overwhelming CDN with concurrent requests
  • Automatic retries: Exponential backoff with jitter
Typical performance:
  • Single lap fetch: ~50-100ms
  • 20 driver laps in parallel: ~200-300ms (vs 1-2s sequential)
  • Full session telemetry (20 drivers × 50 laps): ~10-15s (vs 50-100s sequential)

Configuration

Async fetch behavior is controlled by global configuration:

Error Handling

The async fetch system uses a hierarchy of exceptions:

Advanced Usage

Custom concurrency limits

Disable caching for fresh data

Disable validation for performance


Implementation Details

The async fetch system uses niquests with HTTP/2 support, allowing multiple requests to share a single TCP connection. This reduces latency for parallel requests.
JSON parsing is offloaded to a thread pool executor to avoid blocking the async event loop. A process pool is optional for non-telemetry data. The system uses orjson for fast parsing and can parse multiple responses in parallel. Telemetry payloads use thread-based parsing to avoid cross-process IPC overhead.
A semaphore-based rate limiter ensures no more than max_concurrent_requests requests are in flight simultaneously. This prevents overwhelming the CDN and triggering rate limits. The default is 22 concurrent requests, configurable via global config.
Failed requests are retried with exponential backoff and jitter. The backoff formula is: min(backoff_factor^attempt + random(0, jitter_max), max_delay) seconds, where defaults are backoff_factor=2.0, jitter_max=1.0, and max_delay=60.0. The system also includes circuit breaker logic to prevent cascading failures and special handling for connection pool exhaustion.
Last modified on September 8, 2026