fastapi-loopguard 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. fastapi_loopguard-0.3.1/.github/dependabot.yml +13 -0
  2. fastapi_loopguard-0.3.1/.github/workflows/ci.yml +48 -0
  3. fastapi_loopguard-0.3.1/.github/workflows/publish.yml +32 -0
  4. fastapi_loopguard-0.3.1/.gitignore +115 -0
  5. fastapi_loopguard-0.3.1/CLAUDE.md +79 -0
  6. fastapi_loopguard-0.3.1/LICENSE +21 -0
  7. fastapi_loopguard-0.3.1/PKG-INFO +154 -0
  8. fastapi_loopguard-0.3.1/README.md +109 -0
  9. fastapi_loopguard-0.3.1/assets/loopguard-logo.webp +0 -0
  10. fastapi_loopguard-0.3.1/examples/demo_app.py +58 -0
  11. fastapi_loopguard-0.3.1/examples/locustfile.py +196 -0
  12. fastapi_loopguard-0.3.1/examples/run_stress_test.py +407 -0
  13. fastapi_loopguard-0.3.1/examples/stress_app.py +196 -0
  14. fastapi_loopguard-0.3.1/pyproject.toml +126 -0
  15. fastapi_loopguard-0.3.1/src/fastapi_loopguard/__init__.py +62 -0
  16. fastapi_loopguard-0.3.1/src/fastapi_loopguard/config.py +88 -0
  17. fastapi_loopguard-0.3.1/src/fastapi_loopguard/context.py +210 -0
  18. fastapi_loopguard-0.3.1/src/fastapi_loopguard/logging.py +100 -0
  19. fastapi_loopguard-0.3.1/src/fastapi_loopguard/metrics.py +204 -0
  20. fastapi_loopguard-0.3.1/src/fastapi_loopguard/middleware.py +236 -0
  21. fastapi_loopguard-0.3.1/src/fastapi_loopguard/monitor.py +412 -0
  22. fastapi_loopguard-0.3.1/src/fastapi_loopguard/py.typed +0 -0
  23. fastapi_loopguard-0.3.1/src/fastapi_loopguard/pytest_plugin.py +135 -0
  24. fastapi_loopguard-0.3.1/tests/__init__.py +1 -0
  25. fastapi_loopguard-0.3.1/tests/test_config.py +135 -0
  26. fastapi_loopguard-0.3.1/tests/test_context.py +506 -0
  27. fastapi_loopguard-0.3.1/tests/test_cumulative_blocking.py +99 -0
  28. fastapi_loopguard-0.3.1/tests/test_logging.py +242 -0
  29. fastapi_loopguard-0.3.1/tests/test_metrics.py +246 -0
  30. fastapi_loopguard-0.3.1/tests/test_middleware.py +617 -0
  31. fastapi_loopguard-0.3.1/tests/test_monitor.py +804 -0
  32. fastapi_loopguard-0.3.1/tests/test_pytest_plugin.py +280 -0
@@ -0,0 +1,13 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ open-pull-requests-limit: 5
8
+
9
+ - package-ecosystem: "github-actions"
10
+ directory: "/"
11
+ schedule:
12
+ interval: "weekly"
13
+ open-pull-requests-limit: 5
@@ -0,0 +1,48 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v6
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: |
26
+ python -m pip install --upgrade pip
27
+ pip install -e ".[dev]"
28
+
29
+ - name: Lint with ruff
30
+ run: |
31
+ ruff check src/ tests/
32
+ ruff format --check src/ tests/
33
+
34
+ - name: Type check with mypy
35
+ run: mypy src/
36
+
37
+ - name: Run tests with coverage
38
+ run: |
39
+ coverage run -m pytest tests/ -v
40
+ coverage report --fail-under=80
41
+ coverage xml
42
+
43
+ - name: Upload coverage to Codecov
44
+ uses: codecov/codecov-action@v5
45
+ if: matrix.python-version == '3.12'
46
+ with:
47
+ files: ./coverage.xml
48
+ fail_ci_if_error: false
@@ -0,0 +1,32 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ environment: release
12
+ permissions:
13
+ id-token: write # Required for trusted publishing
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.12"
22
+
23
+ - name: Install build tools
24
+ run: |
25
+ python -m pip install --upgrade pip
26
+ pip install build
27
+
28
+ - name: Build package
29
+ run: python -m build
30
+
31
+ - name: Publish to PyPI
32
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,115 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *.cover
47
+ *.py,cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+
51
+ # Translations
52
+ *.mo
53
+ *.pot
54
+
55
+ # Environments
56
+ .env
57
+ .venv
58
+ env/
59
+ venv/
60
+ ENV/
61
+ env.bak/
62
+ venv.bak/
63
+
64
+ # Spyder project settings
65
+ .spyderproject
66
+ .spyproject
67
+
68
+ # Rope project settings
69
+ .ropeproject
70
+
71
+ # mkdocs documentation
72
+ /site
73
+
74
+ # mypy
75
+ .mypy_cache/
76
+ .dmypy.json
77
+ dmypy.json
78
+
79
+ # Pyre type checker
80
+ .pyre/
81
+
82
+ # pytype static type analyzer
83
+ .pytype/
84
+
85
+ # Cython debug symbols
86
+ cython_debug/
87
+
88
+ # IDEs
89
+ .idea/
90
+ .vscode/
91
+ *.swp
92
+ *.swo
93
+ *~
94
+
95
+ # OS files
96
+ .DS_Store
97
+ Thumbs.db
98
+
99
+ # Project specific
100
+ *.log
101
+ .ruff_cache/
102
+
103
+ # Data files
104
+ *.csv
105
+ *.sqlite
106
+ *.db
107
+
108
+ # Performance profiling
109
+ perf.data
110
+ *.prof
111
+ *.pstat
112
+
113
+ # AI / Editors
114
+ .claude/
115
+ .cursor/
@@ -0,0 +1,79 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ fastapi-loopguard is a middleware library that detects event-loop blocking in FastAPI/Starlette applications with per-request attribution. It identifies when synchronous operations (like `time.sleep()`, blocking I/O, or CPU-bound code) block the async event loop and reports which request was responsible.
8
+
9
+ ## Development Commands
10
+
11
+ ```bash
12
+ # Install dependencies (including dev tools)
13
+ pip install -e ".[dev]"
14
+
15
+ # Run tests
16
+ pytest
17
+
18
+ # Run single test
19
+ pytest tests/test_middleware.py::TestLoopGuardMiddleware::test_dev_mode_headers
20
+
21
+ # Type checking (strict mode)
22
+ mypy src/
23
+
24
+ # Linting and formatting
25
+ ruff check src/ tests/
26
+ ruff format src/ tests/
27
+
28
+ # Coverage
29
+ coverage run -m pytest && coverage report
30
+ ```
31
+
32
+ ## Architecture
33
+
34
+ ### Core Components
35
+
36
+ **LoopGuardMiddleware** (`middleware.py`): Pure ASGI middleware (no BaseHTTPMiddleware) that:
37
+ - Handles ASGI lifespan events for proper startup/shutdown of the monitor
38
+ - Registers request contexts for attribution via `RequestRegistry`
39
+ - Injects debug headers (X-Request-Id, X-Blocking-Count, etc.) in dev mode via send wrapper
40
+
41
+ **SentinelMonitor** (`monitor.py`): Background async task that detects blocking by:
42
+ 1. Sleeping for short intervals (default 10ms)
43
+ 2. Measuring actual elapsed time vs expected time
44
+ 3. If lag exceeds threshold (baseline × multiplier), blocking occurred
45
+ 4. Attributes blocking to ALL active requests since we can't determine the specific cause
46
+
47
+ **RequestRegistry** (`context.py`): Thread-safe (via single-threaded asyncio) registry tracking concurrent requests. Uses dict keyed by request_id. When blocking is detected, the monitor iterates all active contexts.
48
+
49
+ **LoopGuardConfig** (`config.py`): Frozen dataclass with validation. Key settings:
50
+ - `monitor_interval_ms`: Check frequency (default 10ms)
51
+ - `threshold_multiplier`: Blocking = lag > baseline × multiplier (default 5.0)
52
+ - `dev_mode`: Enables X-Blocking-* response headers
53
+ - `adaptive_threshold`: Enable sliding-window based automatic threshold adjustment (v0.3.0+)
54
+
55
+ **AdaptiveThreshold** (`monitor.py`): Sliding window percentile-based threshold calculator:
56
+ - Maintains a bounded deque of recent lag samples (`adaptive_window_size`)
57
+ - Recalculates threshold as `P{adaptive_percentile} × multiplier`
58
+ - Only activates after `adaptive_min_samples` collected
59
+ - Reduces false positives in high-concurrency environments
60
+
61
+ ### Blocking Detection Flow
62
+
63
+ 1. Middleware registers `RequestContext` in global `RequestRegistry`
64
+ 2. `SentinelMonitor` runs continuous sleep-measure loop
65
+ 3. On startup: background calibration measures baseline latency (P75 of samples)
66
+ 4. When lag > threshold: `_handle_blocking()` iterates all active contexts via `get_active_requests()`
67
+ 5. Each context's `record_blocking()` appends event to `blocking_events` list
68
+ 6. On response: middleware reads context stats and adds headers
69
+
70
+ ### Optional Components
71
+
72
+ - **Prometheus metrics** (`metrics.py`): Optional `prometheus_client` integration
73
+ - **pytest plugin** (`pytest_plugin.py`): `@pytest.mark.no_blocking` marker fails tests that block
74
+
75
+ ## Test Configuration
76
+
77
+ - Uses `pytest-asyncio` with `asyncio_mode = "auto"` and `asyncio_default_fixture_loop_scope = "function"`
78
+ - Tests use `httpx.AsyncClient` with `ASGITransport` for testing ASGI apps
79
+ - Each test clears the global `RequestRegistry` via `clear_registry` fixture
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Parham
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-loopguard
3
+ Version: 0.3.1
4
+ Summary: Detect event-loop blocking in FastAPI/Starlette with per-request attribution
5
+ Project-URL: Homepage, https://github.com/parhamdavari/fastapi-loopguard
6
+ Project-URL: Documentation, https://github.com/parhamdavari/fastapi-loopguard#readme
7
+ Project-URL: Repository, https://github.com/parhamdavari/fastapi-loopguard
8
+ Project-URL: Issues, https://github.com/parhamdavari/fastapi-loopguard/issues
9
+ Author: Parham
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: asyncio,blocking,event-loop,fastapi,middleware,monitoring,performance,starlette
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Framework :: AsyncIO
15
+ Classifier: Framework :: FastAPI
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: System :: Monitoring
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.12
25
+ Requires-Dist: starlette<1.0,>=0.37.0
26
+ Provides-Extra: all
27
+ Requires-Dist: prometheus-client>=0.19.0; extra == 'all'
28
+ Requires-Dist: structlog>=24.1.0; extra == 'all'
29
+ Provides-Extra: dev
30
+ Requires-Dist: coverage>=7.4.0; extra == 'dev'
31
+ Requires-Dist: fastapi>=0.110.0; extra == 'dev'
32
+ Requires-Dist: httpx>=0.27.0; extra == 'dev'
33
+ Requires-Dist: mypy>=1.8.0; extra == 'dev'
34
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
35
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
36
+ Requires-Dist: ruff>=0.3.0; extra == 'dev'
37
+ Provides-Extra: prometheus
38
+ Requires-Dist: prometheus-client>=0.19.0; extra == 'prometheus'
39
+ Provides-Extra: stress
40
+ Requires-Dist: locust>=2.20.0; extra == 'stress'
41
+ Requires-Dist: uvicorn>=0.27.0; extra == 'stress'
42
+ Provides-Extra: structlog
43
+ Requires-Dist: structlog>=24.1.0; extra == 'structlog'
44
+ Description-Content-Type: text/markdown
45
+
46
+ <p align="center">
47
+ <img src="assets/loopguard-logo.webp" alt="LoopGuard logo" width="320" />
48
+ </p>
49
+
50
+ <p align="center">
51
+ Detect event-loop blocking in FastAPI/Starlette with <strong>per-request attribution</strong>.
52
+ </p>
53
+
54
+ ---
55
+
56
+ [![PyPI version](https://badge.fury.io/py/fastapi-loopguard.svg)](https://badge.fury.io/py/fastapi-loopguard)
57
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
58
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
59
+
60
+ `fastapi-loopguard` monitors your event loop and tells you exactly which request caused the blocking. It handles high-concurrency environments with adaptive thresholds and integrates seamlessly with Prometheus.
61
+
62
+ ###
63
+
64
+ ### Quick Start
65
+
66
+ ```bash
67
+ pip install fastapi-loopguard
68
+ ```
69
+
70
+ ```python
71
+ from fastapi import FastAPI
72
+ from fastapi_loopguard import LoopGuardMiddleware
73
+
74
+ app = FastAPI()
75
+ app.add_middleware(LoopGuardMiddleware)
76
+ ```
77
+
78
+ ### Features
79
+
80
+ * **Per-Request Attribution**: Pinpoint the exact endpoint causing latency.
81
+ * **Cumulative Blocking Detection**: Catch "death by a thousand cuts" where frequent small blocks freeze the loop.
82
+ * **Adaptive Thresholds**: Smart baselines that adjust to server load.
83
+ * **Zero-Overhead Sentinel**: Cooperative monitoring with negligible CPU usage.
84
+ * **Observability Ready**: Built-in support for Prometheus and structured logging.
85
+
86
+ ### Configuration
87
+
88
+ LoopGuard is highly configurable to suit your environment:
89
+
90
+ ```python
91
+ from fastapi import FastAPI
92
+ from fastapi_loopguard import LoopGuardMiddleware, LoopGuardConfig
93
+
94
+ config = LoopGuardConfig(
95
+ # Basic settings
96
+ monitor_interval_ms=10.0,
97
+ fallback_threshold_ms=50.0,
98
+
99
+ # Enable Cumulative Blocking Detection
100
+ # Detects frequent small blocks (e.g., 20ms) that sum up to a large freeze
101
+ cumulative_blocking_enabled=True,
102
+ cumulative_blocking_threshold_ms=200.0, # Alert if total block > 200ms...
103
+ cumulative_window_ms=1000.0, # ...within any 1-second window
104
+ )
105
+
106
+ app = FastAPI()
107
+ app.add_middleware(LoopGuardMiddleware, config=config)
108
+ ```
109
+
110
+ ### Request/Response flows
111
+
112
+ ```mermaid
113
+ sequenceDiagram
114
+ autonumber
115
+ actor Client
116
+ participant MW as Middleware<br/>(middleware.py)
117
+ participant Reg as Context Registry<br/>(context.py)
118
+ participant Mon as Sentinel Monitor<br/>(monitor.py)
119
+ participant App as FastAPI App<br/>(Your Code)
120
+
121
+ Note over MW, App: Request Flow
122
+
123
+ Client->>MW: HTTP Request
124
+ activate MW
125
+
126
+ MW->>Reg: Register Context
127
+ activate Reg
128
+ Reg-->>MW: Done
129
+ deactivate Reg
130
+
131
+ MW->>App: Forward Request
132
+ activate App
133
+
134
+ par Async Monitoring
135
+ Mon->>Reg: Detect Blocking &<br/>Update Contexts
136
+ activate Mon
137
+ activate Reg
138
+ Reg-->>Mon: Updated
139
+ deactivate Reg
140
+ deactivate Mon
141
+ and App Processing
142
+ App-->>MW: Process Complete
143
+ deactivate App
144
+ end
145
+
146
+ MW->>Reg: Unregister Context
147
+ activate Reg
148
+ Reg-->>MW: Done
149
+ deactivate Reg
150
+
151
+ MW->>Client: Response + Headers
152
+ deactivate MW
153
+ ```
154
+
@@ -0,0 +1,109 @@
1
+ <p align="center">
2
+ <img src="assets/loopguard-logo.webp" alt="LoopGuard logo" width="320" />
3
+ </p>
4
+
5
+ <p align="center">
6
+ Detect event-loop blocking in FastAPI/Starlette with <strong>per-request attribution</strong>.
7
+ </p>
8
+
9
+ ---
10
+
11
+ [![PyPI version](https://badge.fury.io/py/fastapi-loopguard.svg)](https://badge.fury.io/py/fastapi-loopguard)
12
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
13
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
14
+
15
+ `fastapi-loopguard` monitors your event loop and tells you exactly which request caused the blocking. It handles high-concurrency environments with adaptive thresholds and integrates seamlessly with Prometheus.
16
+
17
+ ###
18
+
19
+ ### Quick Start
20
+
21
+ ```bash
22
+ pip install fastapi-loopguard
23
+ ```
24
+
25
+ ```python
26
+ from fastapi import FastAPI
27
+ from fastapi_loopguard import LoopGuardMiddleware
28
+
29
+ app = FastAPI()
30
+ app.add_middleware(LoopGuardMiddleware)
31
+ ```
32
+
33
+ ### Features
34
+
35
+ * **Per-Request Attribution**: Pinpoint the exact endpoint causing latency.
36
+ * **Cumulative Blocking Detection**: Catch "death by a thousand cuts" where frequent small blocks freeze the loop.
37
+ * **Adaptive Thresholds**: Smart baselines that adjust to server load.
38
+ * **Zero-Overhead Sentinel**: Cooperative monitoring with negligible CPU usage.
39
+ * **Observability Ready**: Built-in support for Prometheus and structured logging.
40
+
41
+ ### Configuration
42
+
43
+ LoopGuard is highly configurable to suit your environment:
44
+
45
+ ```python
46
+ from fastapi import FastAPI
47
+ from fastapi_loopguard import LoopGuardMiddleware, LoopGuardConfig
48
+
49
+ config = LoopGuardConfig(
50
+ # Basic settings
51
+ monitor_interval_ms=10.0,
52
+ fallback_threshold_ms=50.0,
53
+
54
+ # Enable Cumulative Blocking Detection
55
+ # Detects frequent small blocks (e.g., 20ms) that sum up to a large freeze
56
+ cumulative_blocking_enabled=True,
57
+ cumulative_blocking_threshold_ms=200.0, # Alert if total block > 200ms...
58
+ cumulative_window_ms=1000.0, # ...within any 1-second window
59
+ )
60
+
61
+ app = FastAPI()
62
+ app.add_middleware(LoopGuardMiddleware, config=config)
63
+ ```
64
+
65
+ ### Request/Response flows
66
+
67
+ ```mermaid
68
+ sequenceDiagram
69
+ autonumber
70
+ actor Client
71
+ participant MW as Middleware<br/>(middleware.py)
72
+ participant Reg as Context Registry<br/>(context.py)
73
+ participant Mon as Sentinel Monitor<br/>(monitor.py)
74
+ participant App as FastAPI App<br/>(Your Code)
75
+
76
+ Note over MW, App: Request Flow
77
+
78
+ Client->>MW: HTTP Request
79
+ activate MW
80
+
81
+ MW->>Reg: Register Context
82
+ activate Reg
83
+ Reg-->>MW: Done
84
+ deactivate Reg
85
+
86
+ MW->>App: Forward Request
87
+ activate App
88
+
89
+ par Async Monitoring
90
+ Mon->>Reg: Detect Blocking &<br/>Update Contexts
91
+ activate Mon
92
+ activate Reg
93
+ Reg-->>Mon: Updated
94
+ deactivate Reg
95
+ deactivate Mon
96
+ and App Processing
97
+ App-->>MW: Process Complete
98
+ deactivate App
99
+ end
100
+
101
+ MW->>Reg: Unregister Context
102
+ activate Reg
103
+ Reg-->>MW: Done
104
+ deactivate Reg
105
+
106
+ MW->>Client: Response + Headers
107
+ deactivate MW
108
+ ```
109
+
@@ -0,0 +1,58 @@
1
+ """Demo app showing fastapi-loopguard blocking detection."""
2
+
3
+ import asyncio
4
+ import time
5
+
6
+ from fastapi import FastAPI
7
+
8
+ from fastapi_loopguard import LoopGuardConfig, LoopGuardMiddleware
9
+
10
+ app = FastAPI(title="LoopGuard Demo")
11
+
12
+ # Configure with dev_mode for headers and logging enabled
13
+ config = LoopGuardConfig(
14
+ dev_mode=True,
15
+ log_blocking_events=True,
16
+ fallback_threshold_ms=30.0, # Low threshold for demo
17
+ )
18
+ app.add_middleware(LoopGuardMiddleware, config=config)
19
+
20
+
21
+ @app.get("/")
22
+ async def root():
23
+ """Non-blocking endpoint."""
24
+ return {"message": "Hello, this is non-blocking!"}
25
+
26
+
27
+ @app.get("/api/users")
28
+ async def get_users():
29
+ """Endpoint that blocks the event loop - will trigger warning."""
30
+ # This blocks the event loop! BAD!
31
+ time.sleep(0.15) # 150ms blocking
32
+ # Small yield to let monitor detect the blocking while context is still active
33
+ await asyncio.sleep(0.02)
34
+ return {"users": ["alice", "bob", "charlie"]}
35
+
36
+
37
+ @app.get("/api/items")
38
+ async def get_items():
39
+ """Non-blocking endpoint using async sleep."""
40
+ # This is correct - doesn't block
41
+ await asyncio.sleep(0.1)
42
+ return {"items": ["item1", "item2", "item3"]}
43
+
44
+
45
+ if __name__ == "__main__":
46
+ import uvicorn
47
+
48
+ print("\n" + "=" * 60)
49
+ print("LoopGuard Demo App")
50
+ print("=" * 60)
51
+ print("\nEndpoints:")
52
+ print(" GET / - Non-blocking (no warning)")
53
+ print(" GET /api/users - BLOCKING (will trigger warning)")
54
+ print(" GET /api/items - Non-blocking async sleep (no warning)")
55
+ print("\nTry: curl http://localhost:8765/api/users")
56
+ print("=" * 60 + "\n")
57
+
58
+ uvicorn.run(app, host="0.0.0.0", port=8765, log_level="info")