0
# Process Supervisors
1
2
Process management supervisors for multiprocess workers and automatic code reloading during development.
3
4
## Imports
5
6
```python
7
from uvicorn.supervisors import Multiprocess, ChangeReload
8
from uvicorn.supervisors.multiprocess import Process
9
from uvicorn.supervisors.basereload import BaseReload
10
from uvicorn.supervisors.statreload import StatReload
11
from uvicorn.supervisors.watchfilesreload import WatchFilesReload
12
```
13
14
## Capabilities
15
16
### Multiprocess Supervisor
17
18
Supervisor for managing multiple worker processes for production deployments.
19
20
```python { .api }
21
class Multiprocess:
22
"""
23
Supervisor for managing multiple worker processes.
24
25
Spawns worker processes, monitors their health, handles signals,
26
and restarts failed workers automatically.
27
"""
28
29
def __init__(
30
self,
31
config: Config,
32
target: Callable,
33
sockets: list[socket.socket],
34
) -> None:
35
"""
36
Initialize multiprocess supervisor.
37
38
Args:
39
config: Server configuration (should have workers > 1)
40
target: Target function to run in each worker process
41
sockets: Pre-bound sockets to share across all workers
42
43
Attributes:
44
config: Server configuration
45
target: Worker target function
46
sockets: Shared sockets
47
processes_num: Number of worker processes to spawn
48
processes: List of worker process wrappers
49
should_exit: Threading event for shutdown signal
50
signal_queue: Queue of received signals
51
"""
52
53
def run(self) -> None:
54
"""
55
Run the multiprocess supervisor.
56
57
Main supervisor loop that:
58
1. Initializes worker processes
59
2. Monitors worker health
60
3. Handles signals (SIGINT, SIGTERM, SIGHUP, SIGTTIN, SIGTTOU)
61
4. Restarts failed workers
62
5. Performs graceful shutdown
63
64
This method blocks until shutdown is complete.
65
"""
66
67
def init_processes(self) -> None:
68
"""
69
Initialize and start all worker processes.
70
71
Creates Process instances and starts each worker.
72
"""
73
74
def terminate_all(self) -> None:
75
"""
76
Terminate all worker processes.
77
78
Sends SIGTERM to all workers and waits briefly for graceful shutdown.
79
"""
80
81
def join_all(self) -> None:
82
"""
83
Wait for all worker processes to exit.
84
85
Blocks until all workers have terminated.
86
"""
87
88
def restart_all(self) -> None:
89
"""
90
Restart all worker processes.
91
92
Terminates existing workers and spawns new ones.
93
Used for SIGHUP (reload without code changes).
94
"""
95
96
def keep_subprocess_alive(self) -> None:
97
"""
98
Monitor worker processes and restart failed workers.
99
100
Runs in a loop checking worker health and restarting any
101
that have died unexpectedly.
102
"""
103
104
def handle_signals(self) -> None:
105
"""
106
Process queued signals.
107
108
Handles signals received by workers and forwards them
109
to appropriate handlers.
110
"""
111
```
112
113
### Process Wrapper
114
115
Wrapper for individual worker processes with health monitoring.
116
117
```python { .api }
118
class Process:
119
"""
120
Wrapper for worker processes with health check support.
121
122
Manages individual worker process lifecycle and provides
123
ping/pong health checking.
124
"""
125
126
def __init__(
127
self,
128
config: Config,
129
target: Callable,
130
sockets: list[socket.socket],
131
) -> None:
132
"""
133
Initialize process wrapper.
134
135
Args:
136
config: Server configuration
137
target: Function to run in worker process
138
sockets: Sockets to pass to worker
139
"""
140
141
@property
142
def pid(self) -> int | None:
143
"""
144
Get process ID.
145
146
Returns:
147
Process ID or None if process hasn't started
148
"""
149
150
def is_alive(self, timeout: float = 5) -> bool:
151
"""
152
Check if process is alive and responsive.
153
154
Args:
155
timeout: Health check timeout in seconds (default: 5)
156
157
Returns:
158
True if process responds to ping within timeout
159
"""
160
161
def start(self) -> None:
162
"""
163
Start the worker process.
164
165
Spawns the process and begins its execution.
166
"""
167
168
def terminate(self) -> None:
169
"""
170
Terminate the worker process gracefully.
171
172
Sends SIGTERM to request graceful shutdown.
173
"""
174
175
def kill(self) -> None:
176
"""
177
Kill the worker process forcefully.
178
179
Sends SIGKILL to immediately terminate process.
180
"""
181
182
def join(self) -> None:
183
"""
184
Wait for worker process to exit.
185
186
Blocks until the process has terminated.
187
"""
188
```
189
190
### Base Reload Supervisor
191
192
Abstract base class for file-watching reload supervisors.
193
194
```python { .api }
195
class BaseReload:
196
"""
197
Base class for reload supervisors.
198
199
Monitors files for changes and restarts the worker process
200
when changes are detected. Used during development.
201
"""
202
203
def __init__(
204
self,
205
config: Config,
206
target: Callable,
207
sockets: list[socket.socket],
208
) -> None:
209
"""
210
Initialize reload supervisor.
211
212
Args:
213
config: Server configuration (should have reload=True)
214
target: Function to run in worker process
215
sockets: Sockets to pass to worker
216
217
Attributes:
218
config: Server configuration
219
target: Worker target function
220
sockets: Shared sockets
221
should_exit: Threading event for shutdown signal
222
pid: Supervisor process ID
223
is_restarting: Flag indicating restart in progress
224
reloader_name: Name of reload implementation
225
"""
226
227
def run(self) -> None:
228
"""
229
Run the reload supervisor.
230
231
Main loop that:
232
1. Starts worker process
233
2. Monitors files for changes
234
3. Restarts worker when changes detected
235
4. Handles shutdown signals
236
237
This method blocks until shutdown is complete.
238
"""
239
240
def startup(self) -> None:
241
"""
242
Start the worker process.
243
244
Spawns worker in subprocess and waits for it to initialize.
245
"""
246
247
def restart(self) -> None:
248
"""
249
Restart the worker process.
250
251
Terminates existing worker and spawns a new one with
252
reloaded code.
253
"""
254
255
def shutdown(self) -> None:
256
"""
257
Shutdown the worker process.
258
259
Terminates worker and cleans up resources.
260
"""
261
262
def pause(self) -> None:
263
"""
264
Pause between file system checks.
265
266
Sleeps for reload_delay seconds to avoid excessive checking.
267
"""
268
269
def should_restart(self) -> list[pathlib.Path] | None:
270
"""
271
Check if worker should be restarted.
272
273
Returns:
274
List of changed file paths if restart needed, None otherwise
275
276
This is an abstract method that must be implemented by subclasses.
277
"""
278
```
279
280
### Stat Reload Supervisor
281
282
File watching using stat-based polling (fallback implementation).
283
284
```python { .api }
285
class StatReload(BaseReload):
286
"""
287
Reload supervisor using stat-based file watching.
288
289
Polls files periodically using os.stat() to detect changes.
290
This is a fallback implementation when watchfiles is not available.
291
"""
292
293
def __init__(
294
self,
295
config: Config,
296
target: Callable,
297
sockets: list[socket.socket],
298
) -> None:
299
"""
300
Initialize stat reload supervisor.
301
302
Args:
303
config: Server configuration
304
target: Worker target function
305
sockets: Shared sockets
306
307
Attributes:
308
mtimes: Dictionary mapping file paths to modification times
309
"""
310
311
def should_restart(self) -> list[pathlib.Path] | None:
312
"""
313
Check for modified Python files.
314
315
Iterates over Python files in reload directories and checks
316
modification times against cached values.
317
318
Returns:
319
List of changed files if any modifications detected, None otherwise
320
"""
321
322
def restart(self) -> None:
323
"""
324
Clear modification time cache and restart worker.
325
326
Clears the mtimes cache so new files are detected after restart.
327
"""
328
329
def iter_py_files(self) -> Iterator[pathlib.Path]:
330
"""
331
Iterate over Python files in reload directories.
332
333
Yields:
334
Path objects for each .py file in watched directories
335
"""
336
```
337
338
### WatchFiles Reload Supervisor
339
340
File watching using the watchfiles library (preferred implementation).
341
342
```python { .api }
343
class WatchFilesReload(BaseReload):
344
"""
345
Reload supervisor using watchfiles library.
346
347
Uses efficient file system notifications (inotify on Linux, FSEvents
348
on macOS, etc.) for fast, low-overhead change detection.
349
350
This is the preferred implementation when watchfiles is installed.
351
"""
352
353
def __init__(
354
self,
355
config: Config,
356
target: Callable,
357
sockets: list[socket.socket],
358
) -> None:
359
"""
360
Initialize watchfiles reload supervisor.
361
362
Args:
363
config: Server configuration
364
target: Worker target function
365
sockets: Shared sockets
366
367
Attributes:
368
reload_dirs: Directories to watch
369
watch_filter: File filter for determining what to watch
370
watcher: watchfiles watcher generator
371
"""
372
373
def should_restart(self) -> list[pathlib.Path] | None:
374
"""
375
Check for file changes using watchfiles.
376
377
Uses watchfiles to efficiently detect file system changes.
378
379
Returns:
380
List of changed files if any detected, None otherwise
381
"""
382
```
383
384
### File Filter
385
386
Filter for determining which files to watch.
387
388
```python { .api }
389
class FileFilter:
390
"""
391
Filter for file watching.
392
393
Determines which files should trigger reloads based on
394
include/exclude patterns.
395
"""
396
397
def __init__(self, config: Config) -> None:
398
"""
399
Initialize file filter.
400
401
Args:
402
config: Server configuration with reload_includes/reload_excludes
403
404
Attributes:
405
includes: List of glob patterns to include
406
excludes: List of glob patterns to exclude
407
exclude_dirs: List of directories to exclude
408
"""
409
410
def __call__(self, path: pathlib.Path) -> bool:
411
"""
412
Check if path should be watched.
413
414
Args:
415
path: File path to check
416
417
Returns:
418
True if file should trigger reload, False otherwise
419
420
The filter:
421
1. Excludes directories in exclude_dirs
422
2. Excludes files matching exclude patterns
423
3. Includes files matching include patterns
424
"""
425
```
426
427
### ChangeReload Alias
428
429
```python { .api }
430
# Auto-selected reload supervisor based on availability
431
ChangeReload: type[BaseReload]
432
"""
433
Reload supervisor automatically selected based on available dependencies.
434
435
- WatchFilesReload if watchfiles package is installed (preferred)
436
- StatReload otherwise (fallback using stat-based polling)
437
"""
438
```
439
440
## Usage Examples
441
442
### Run with Multiple Workers
443
444
```python
445
from uvicorn import Config, Server
446
from uvicorn.supervisors import Multiprocess
447
import socket
448
449
async def app(scope, receive, send):
450
# Your ASGI application
451
...
452
453
# Create configuration
454
config = Config(
455
app=app,
456
host="0.0.0.0",
457
port=8000,
458
workers=4, # Run 4 worker processes
459
)
460
461
# Bind socket
462
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
463
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
464
sock.bind((config.host, config.port))
465
466
# Create and run multiprocess supervisor
467
supervisor = Multiprocess(
468
config=config,
469
target=Server(config).run,
470
sockets=[sock],
471
)
472
supervisor.run()
473
```
474
475
### Automatic Multiprocess with uvicorn.run
476
477
```python
478
import uvicorn
479
480
# Multiprocess is automatically used when workers > 1
481
uvicorn.run(
482
"myapp:app",
483
host="0.0.0.0",
484
port=8000,
485
workers=4,
486
)
487
```
488
489
### Enable Auto-Reload
490
491
```python
492
import uvicorn
493
494
# Auto-reload is enabled with reload=True
495
uvicorn.run(
496
"myapp:app",
497
host="127.0.0.1",
498
port=8000,
499
reload=True, # Enable auto-reload
500
reload_dirs=["./src", "./lib"], # Watch these directories
501
)
502
```
503
504
### Custom Reload Configuration
505
506
```python
507
from uvicorn import Config
508
from uvicorn.supervisors import ChangeReload
509
import socket
510
511
config = Config(
512
app="myapp:app",
513
host="127.0.0.1",
514
port=8000,
515
reload=True,
516
reload_dirs=["./src"],
517
reload_includes=["*.py", "*.yaml", "*.json"],
518
reload_excludes=["*.pyc", "*.pyo", "__pycache__/*"],
519
reload_delay=0.5, # Check every 0.5 seconds
520
)
521
522
# Bind socket
523
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
524
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
525
sock.bind((config.host, config.port))
526
527
# Create and run reload supervisor
528
from uvicorn import Server
529
530
supervisor = ChangeReload(
531
config=config,
532
target=Server(config).run,
533
sockets=[sock],
534
)
535
supervisor.run()
536
```
537
538
### Force Specific Reload Implementation
539
540
```python
541
from uvicorn import Config, Server
542
from uvicorn.supervisors.statreload import StatReload
543
import socket
544
545
config = Config(
546
app="myapp:app",
547
reload=True,
548
)
549
550
sock = socket.socket()
551
sock.bind((config.host, config.port))
552
553
# Force stat-based reload (e.g., when watchfiles not available)
554
supervisor = StatReload(
555
config=config,
556
target=Server(config).run,
557
sockets=[sock],
558
)
559
supervisor.run()
560
```
561
562
### Custom Worker Target
563
564
```python
565
from uvicorn import Config
566
from uvicorn.supervisors import Multiprocess
567
import socket
568
569
def custom_worker_target(sockets):
570
"""Custom worker function."""
571
config = Config(app="myapp:app")
572
from uvicorn import Server
573
server = Server(config)
574
server.run(sockets=sockets)
575
576
config = Config(app="myapp:app", workers=4)
577
578
sock = socket.socket()
579
sock.bind((config.host, config.port))
580
581
supervisor = Multiprocess(
582
config=config,
583
target=custom_worker_target,
584
sockets=[sock],
585
)
586
supervisor.run()
587
```
588
589
### Monitor Worker Health
590
591
```python
592
from uvicorn import Config
593
from uvicorn.supervisors.multiprocess import Process
594
import socket
595
596
config = Config(app="myapp:app")
597
598
sock = socket.socket()
599
sock.bind((config.host, config.port))
600
601
# Create worker process
602
from uvicorn import Server
603
604
process = Process(
605
config=config,
606
target=Server(config).run,
607
sockets=[sock],
608
)
609
610
# Start worker
611
process.start()
612
613
# Check health
614
import time
615
while True:
616
if process.is_alive(timeout=5):
617
print(f"Worker {process.pid} is healthy")
618
else:
619
print(f"Worker {process.pid} is not responding")
620
process.terminate()
621
break
622
time.sleep(10)
623
```
624
625
### Handle Reload Events
626
627
```python
628
from uvicorn import Config, Server
629
from uvicorn.supervisors import ChangeReload
630
import socket
631
632
class CustomReloadSupervisor(ChangeReload):
633
"""Custom reload supervisor with logging."""
634
635
def restart(self):
636
"""Override restart to add custom logging."""
637
changed_files = self.should_restart()
638
if changed_files:
639
print(f"Files changed: {changed_files}")
640
print("Restarting worker...")
641
super().restart()
642
643
config = Config(
644
app="myapp:app",
645
reload=True,
646
)
647
648
sock = socket.socket()
649
sock.bind((config.host, config.port))
650
651
supervisor = CustomReloadSupervisor(
652
config=config,
653
target=Server(config).run,
654
sockets=[sock],
655
)
656
supervisor.run()
657
```
658
659
### Graceful Shutdown Handling
660
661
```python
662
import signal
663
from uvicorn import Config
664
from uvicorn.supervisors import Multiprocess
665
import socket
666
667
config = Config(app="myapp:app", workers=4)
668
669
sock = socket.socket()
670
sock.bind((config.host, config.port))
671
672
supervisor = Multiprocess(
673
config=config,
674
target=lambda sockets: None, # Placeholder
675
sockets=[sock],
676
)
677
678
# Signal handlers are automatically set up
679
# SIGINT/SIGTERM trigger graceful shutdown
680
# SIGHUP triggers worker restart without supervisor exit
681
# SIGTTIN/SIGTTOU adjust worker count (Unix only)
682
683
supervisor.run()
684
```
685
686
### Custom File Filter
687
688
```python
689
from uvicorn import Config
690
from uvicorn.supervisors.watchfilesreload import FileFilter
691
import pathlib
692
693
config = Config(
694
app="myapp:app",
695
reload=True,
696
reload_includes=["*.py", "*.yaml"],
697
reload_excludes=["tests/*", "*.pyc"],
698
)
699
700
# Create file filter
701
file_filter = FileFilter(config)
702
703
# Test filter
704
test_files = [
705
pathlib.Path("myapp/main.py"), # Should watch
706
pathlib.Path("myapp/config.yaml"), # Should watch
707
pathlib.Path("tests/test_app.py"), # Should not watch
708
pathlib.Path("myapp/__pycache__/main.cpython-39.pyc"), # Should not watch
709
]
710
711
for path in test_files:
712
should_watch = file_filter(path)
713
print(f"{path}: {'watch' if should_watch else 'ignore'}")
714
```
715
716
### Development vs Production
717
718
```python
719
import uvicorn
720
import os
721
722
# Determine environment
723
is_production = os.getenv("ENVIRONMENT") == "production"
724
725
if is_production:
726
# Production: Multiple workers, no reload
727
uvicorn.run(
728
"myapp:app",
729
host="0.0.0.0",
730
port=8000,
731
workers=4,
732
reload=False,
733
access_log=True,
734
)
735
else:
736
# Development: Single worker with reload
737
uvicorn.run(
738
"myapp:app",
739
host="127.0.0.1",
740
port=8000,
741
reload=True,
742
reload_dirs=["./src"],
743
log_level="debug",
744
)
745
```
746
747
### Worker Count Adjustment
748
749
```python
750
# In production with Multiprocess supervisor:
751
# - Send SIGTTIN to increase worker count by 1
752
# - Send SIGTTOU to decrease worker count by 1
753
# - Send SIGHUP to restart all workers
754
755
# Example from shell:
756
# kill -TTIN <supervisor_pid> # Add 1 worker
757
# kill -TTOU <supervisor_pid> # Remove 1 worker
758
# kill -HUP <supervisor_pid> # Restart all workers
759
```
760