or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mdconfig.mdindex.mdlogging.mdmiddleware.mdserver.mdsupervisors.mdtypes.md

supervisors.mddocs/

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