Liking cljdoc? Tell your friends :D

me.lomin.sinho.timeout

Adaptive Self-Clocking Timeout Guard for CLJ/CLJS

This implementation provides a cooperative timeout mechanism that can efficiently stop CPU-bound computations after a specified timeout period without requiring external watchdog threads or complex coordination.

Overview

The timeout guard uses an A* search-inspired adaptive algorithm that automatically adjusts how frequently it checks the system clock based on observed timing patterns. This minimizes the overhead when far from the timeout deadline while ensuring responsive detection near the timeout boundary.

Key Algorithm Components

Adaptive Stride-Based Sampling

Instead of checking the clock on every call, the algorithm dynamically calculates a 'stride' - the number of calls to skip before the next time check. This stride is continuously adapted based on:

  • EWMA (Exponentially Weighted Moving Average): Tracks mean time per step
  • EWVAR (Exponentially Weighted Variance): Estimates timing variability
  • Decayed Maximum: Hedges against occasional long steps that could cause overshoot
  • Panic Mode: Forces frequent sampling when time is being consumed too rapidly

Target Overshoot Envelope

The algorithm aims to detect timeouts within a bounded overshoot envelope:

p99 overshoot ≤ max(S, min(5% × timeout, 10ms))

where S is the p99 cost of a single atomic step between checks.

Time-Based Ceiling

To prevent excessively long periods without sampling, the stride calculation includes a time-based ceiling that ensures samples occur at least every 10ms of wall-clock time.

Usage Pattern

(let [timeout? (make-timeout 200)]          ; 200ms timeout
  (reduce (fn [acc x]
            (if (timeout?)                  ; Check at natural boundaries
              (reduced acc)                 ; Return current value on timeout
              (expensive-step acc x)))      ; Continue processing
          initial-value
          large-dataset))

Performance Characteristics

  • Fast Path: Simple counter increment and comparison (zero allocation)
  • Sample Path: Single monotonic time read plus statistical updates
  • Steady-State Overhead: Target <1-2% when far from deadline
  • Cross-Platform: Works on JVM, Node.js, and modern browsers

Thread Confinement Model

Each timeout guard is designed for single-threaded use (one guard per compute thread). Multiple guards can run concurrently in different threads without coordination.

Constants and Tuning

The algorithm uses fixed constants tuned for general-purpose workloads:

  • Z95 = 1.64485 (95th percentile multiplier for normal distribution)
  • ALPHA = 0.05 (EWMA/EWVAR smoothing factor)
  • SAFETY = 2.0 (conservative stride multiplier)
  • DECAY = 0.98 (decayed maximum decay rate per sample)
  • BURN_RATIO = 0.33 (panic threshold: >33% of remaining time consumed)

These constants are intentionally not configurable to maintain API simplicity and ensure consistent behavior across deployments.

Adaptive Self-Clocking Timeout Guard for CLJ/CLJS

This implementation provides a cooperative timeout mechanism that can efficiently
stop CPU-bound computations after a specified timeout period without requiring
external watchdog threads or complex coordination.

## Overview

The timeout guard uses an A* search-inspired adaptive algorithm that automatically
adjusts how frequently it checks the system clock based on observed timing patterns.
This minimizes the overhead when far from the timeout deadline while ensuring
responsive detection near the timeout boundary.

## Key Algorithm Components

### Adaptive Stride-Based Sampling
Instead of checking the clock on every call, the algorithm dynamically calculates
a 'stride' - the number of calls to skip before the next time check. This stride
is continuously adapted based on:

- **EWMA (Exponentially Weighted Moving Average)**: Tracks mean time per step
- **EWVAR (Exponentially Weighted Variance)**: Estimates timing variability  
- **Decayed Maximum**: Hedges against occasional long steps that could cause overshoot
- **Panic Mode**: Forces frequent sampling when time is being consumed too rapidly

### Target Overshoot Envelope
The algorithm aims to detect timeouts within a bounded overshoot envelope:
```
p99 overshoot ≤ max(S, min(5% × timeout, 10ms))
```
where S is the p99 cost of a single atomic step between checks.

### Time-Based Ceiling
To prevent excessively long periods without sampling, the stride calculation
includes a time-based ceiling that ensures samples occur at least every 10ms
of wall-clock time.

## Usage Pattern

```clojure
(let [timeout? (make-timeout 200)]          ; 200ms timeout
  (reduce (fn [acc x]
            (if (timeout?)                  ; Check at natural boundaries
              (reduced acc)                 ; Return current value on timeout
              (expensive-step acc x)))      ; Continue processing
          initial-value
          large-dataset))
```

## Performance Characteristics

- **Fast Path**: Simple counter increment and comparison (zero allocation)
- **Sample Path**: Single monotonic time read plus statistical updates
- **Steady-State Overhead**: Target <1-2% when far from deadline
- **Cross-Platform**: Works on JVM, Node.js, and modern browsers

## Thread Confinement Model
Each timeout guard is designed for single-threaded use (one guard per compute thread).
Multiple guards can run concurrently in different threads without coordination.

## Constants and Tuning
The algorithm uses fixed constants tuned for general-purpose workloads:
- Z95 = 1.64485 (95th percentile multiplier for normal distribution)
- ALPHA = 0.05 (EWMA/EWVAR smoothing factor)
- SAFETY = 2.0 (conservative stride multiplier)
- DECAY = 0.98 (decayed maximum decay rate per sample)
- BURN_RATIO = 0.33 (panic threshold: >33% of remaining time consumed)

These constants are intentionally not configurable to maintain API simplicity
and ensure consistent behavior across deployments.
raw docstring

IGuardclj/sprotocol

set-stride-max!clj/s

(set-stride-max! this val)

get-target-overshoot-usclj/s

(get-target-overshoot-us this)

set-mean-us!clj/s

(set-mean-us! this val)

get-start-usclj/s

(get-start-us this)

get-check-countclj/s

(get-check-count this)

set-var-us!clj/s

(set-var-us! this val)

get-panic-countclj/s

(get-panic-count this)

set-burn-max!clj/s

(set-burn-max! this val)

set-decayed-max-us!clj/s

(set-decayed-max-us! this val)

set-stride-shrink-count!clj/s

(set-stride-shrink-count! this val)

get-last-sample-usclj/s

(get-last-sample-us this)

set-steps-since!clj/s

(set-steps-since! this val)

get-steps-sinceclj/s

(get-steps-since this)

get-mean-usclj/s

(get-mean-us this)

get-stride-minclj/s

(get-stride-min this)

get-stride-shrink-countclj/s

(get-stride-shrink-count this)

get-timed-outclj/s

(get-timed-out this)

set-panic-count!clj/s

(set-panic-count! this val)

get-deadline-usclj/s

(get-deadline-us this)

set-timed-out!clj/s

(set-timed-out! this val)

get-detected-at-usclj/s

(get-detected-at-us this)

get-stride-maxclj/s

(get-stride-max this)

set-last-sample-us!clj/s

(set-last-sample-us! this val)

get-var-usclj/s

(get-var-us this)

get-decayed-max-usclj/s

(get-decayed-max-us this)

set-check-count!clj/s

(set-check-count! this val)

set-sample-count!clj/s

(set-sample-count! this val)

set-detected-at-us!clj/s

(set-detected-at-us! this val)

set-stride-k!clj/s

(set-stride-k! this val)

get-burn-maxclj/s

(get-burn-max this)

get-sample-countclj/s

(get-sample-count this)

set-stride-min!clj/s

(set-stride-min! this val)

get-stride-kclj/s

(get-stride-k this)
source

make-timeoutclj/s

(make-timeout timeout-ms)

Create an adaptive timeout guard function for cooperative CPU-bound computation limits.

Creates a timeout? function that uses adaptive stride-based sampling to efficiently check if the specified timeout has been exceeded. The algorithm automatically adjusts its sampling frequency based on observed timing patterns to minimize overhead while ensuring responsive timeout detection.

Arguments:

  • timeout-ms: Timeout duration in milliseconds (converted to microseconds internally)

Returns: A timeout? function with two arities:

  • (timeout?) => boolean ; true means STOP NOW, false means continue
  • (timeout? :timeout/debug) => map ; debugging information and statistics

Usage Pattern:

(let [timeout? (make-timeout 200)]  ; 200ms timeout
  (reduce (fn [acc x]
            (if (timeout?)         ; Check at natural loop boundaries
              (reduced acc)        ; Return current result on timeout  
              (expensive-step acc x))) ; Continue processing
          initial-value
          large-dataset))

Performance Notes:

  • Fast path: ~1-2ns overhead (counter increment + comparison)
  • Sample path: ~100ns overhead (time read + statistics)
  • Zero allocation on fast path
  • Adaptive sampling reduces checks when far from deadline
  • Target p99 overshoot: max(step_time, min(5% × timeout, 10ms))

Thread Safety: Each timeout guard is designed for single-threaded use. Create one guard per compute thread - guards do not require synchronization between threads.

Create an adaptive timeout guard function for cooperative CPU-bound computation limits.

Creates a `timeout?` function that uses adaptive stride-based sampling to efficiently
check if the specified timeout has been exceeded. The algorithm automatically adjusts
its sampling frequency based on observed timing patterns to minimize overhead while
ensuring responsive timeout detection.

Arguments:
- timeout-ms: Timeout duration in milliseconds (converted to microseconds internally)

Returns:
A timeout? function with two arities:
- (timeout?)              => boolean  ; true means STOP NOW, false means continue
- (timeout? :timeout/debug) => map     ; debugging information and statistics

Usage Pattern:
```clojure
(let [timeout? (make-timeout 200)]  ; 200ms timeout
  (reduce (fn [acc x]
            (if (timeout?)         ; Check at natural loop boundaries
              (reduced acc)        ; Return current result on timeout  
              (expensive-step acc x))) ; Continue processing
          initial-value
          large-dataset))
```

Performance Notes:
- Fast path: ~1-2ns overhead (counter increment + comparison)
- Sample path: ~100ns overhead (time read + statistics)
- Zero allocation on fast path
- Adaptive sampling reduces checks when far from deadline
- Target p99 overshoot: max(step_time, min(5% × timeout, 10ms))

Thread Safety:
Each timeout guard is designed for single-threaded use. Create one guard per
compute thread - guards do not require synchronization between threads.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close