Skip to content

Commit 0dd0bbf

Browse files
committed
feat(etl-uvicorn): ignore SIGTERM in plugin uvicorn Server
uvicorn's default SIGTERM behavior load-sheds new requests with 504 while draining in-flight work. Plugin webservers are expected to outlive their controller container during pod shutdown so the controller can finish dispatching in-flight work before the pod is SIGKILLed; the default behavior orphans that work. Monkey-patch uvicorn.Server.install_signal_handlers at module import time to install only a SIGINT handler. The plugin process now ignores SIGTERM and exits only on SIGKILL (or SIGINT for local dev / Ctrl-C). Bumps version to 0.0.44.
1 parent 094fc3d commit 0dd0bbf

4 files changed

Lines changed: 64 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.0.44
2+
3+
* **Ignore SIGTERM in plugin uvicorn Servers**: plugin webservers now keep
4+
serving on SIGTERM so their controller container can finish dispatching
5+
in-flight work before the pod is SIGKILLed. SIGINT still terminates for
6+
local-dev Ctrl-C.
7+
18
## 0.0.43
29

310
* **Deprecate `wrap_in_fastapi`** - Mark `wrap_in_fastapi` (and the `etl-uvicorn` CLI it backs) as deprecated via PEP 702 `@deprecated`. New plugins should build a FastAPI app directly with explicit handlers for the plugin contract routes.

test/test_signal_handlers.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""Verify the etl_uvicorn module installs a SIGTERM-ignoring handler on uvicorn.Server."""
2+
3+
import asyncio
4+
import signal
5+
6+
import uvicorn
7+
8+
# Importing the module applies the monkey-patch as a side effect.
9+
from unstructured_platform_plugins.etl_uvicorn import main # noqa: F401
10+
11+
12+
def test_uvicorn_server_install_signal_handlers_is_patched():
13+
assert (
14+
uvicorn.Server.install_signal_handlers.__name__
15+
== "_install_signal_handlers_ignoring_sigterm"
16+
)
17+
18+
19+
def test_install_signal_handlers_registers_sigint_only():
20+
async def _run() -> None:
21+
config = uvicorn.Config(app="fake:app", lifespan="off")
22+
server = uvicorn.Server(config=config)
23+
server.install_signal_handlers()
24+
loop = asyncio.get_running_loop()
25+
try:
26+
assert loop.remove_signal_handler(signal.SIGINT) is True
27+
assert loop.remove_signal_handler(signal.SIGTERM) is False
28+
finally:
29+
try:
30+
loop.remove_signal_handler(signal.SIGINT)
31+
loop.remove_signal_handler(signal.SIGTERM)
32+
except Exception:
33+
pass
34+
35+
asyncio.run(_run())
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.43" # pragma: no cover
1+
__version__ = "0.0.44" # pragma: no cover

unstructured_platform_plugins/etl_uvicorn/main.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
1+
import asyncio
2+
import signal
3+
import threading
14
from dataclasses import dataclass, field
25
from typing import IO, Any, Optional
36

47
import click
8+
import uvicorn
59
from uvicorn.config import LOGGING_CONFIG, Config, RawConfigParser
610
from uvicorn.main import main, run
711

812
from unstructured_platform_plugins.etl_uvicorn.api_generator import generate_fast_api
913

1014

15+
def _install_signal_handlers_ignoring_sigterm(self: uvicorn.Server) -> None:
16+
# uvicorn's default load-sheds 504 on SIGTERM, which races with controllers
17+
# trying to drain in-flight work during pod shutdown. Plugin webservers are
18+
# expected to outlive their controller container so SIGKILL — not SIGTERM —
19+
# ends the process. SIGINT is preserved so local Ctrl-C still works.
20+
if threading.current_thread() is not threading.main_thread():
21+
return
22+
try:
23+
loop = asyncio.get_event_loop()
24+
loop.add_signal_handler(signal.SIGINT, self.handle_exit, signal.SIGINT, None)
25+
except NotImplementedError:
26+
signal.signal(signal.SIGINT, self.handle_exit)
27+
28+
29+
uvicorn.Server.install_signal_handlers = _install_signal_handlers_ignoring_sigterm
30+
31+
1132
@dataclass
1233
class CustomConfig:
1334
log_config: dict[str, Any] | str | RawConfigParser | IO[Any] | None = field(

0 commit comments

Comments
 (0)