amfs-http-server 0.3.40__tar.gz → 0.3.41__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: amfs-http-server
3
- Version: 0.3.40
3
+ Version: 0.3.41
4
4
  Summary: AMFS HTTP/REST API server with SSE support
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
@@ -9,6 +9,6 @@ Requires-Dist: amfs>=0.5.27
9
9
  Requires-Dist: fastapi>=0.115
10
10
  Requires-Dist: httpx>=0.27
11
11
  Requires-Dist: sse-starlette>=2.0
12
- Requires-Dist: uvicorn[standard]>=0.32
12
+ Requires-Dist: uvicorn[standard]>=0.37
13
13
  Provides-Extra: pro
14
14
  Requires-Dist: amfs-patterns; extra == 'pro'
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "amfs-http-server"
3
- version = "0.3.40"
3
+ version = "0.3.41"
4
4
  description = "AMFS HTTP/REST API server with SSE support"
5
5
  requires-python = ">=3.11"
6
6
  license = "Apache-2.0"
@@ -29,7 +29,10 @@ dependencies = [
29
29
  # agent's private keys. Either way the floor is what makes the rule real.
30
30
  "amfs-core>=0.3.37",
31
31
  "fastapi>=0.115",
32
- "uvicorn[standard]>=0.32",
32
+ # 0.37 is where uvicorn.run() grew timeout_worker_healthcheck, which
33
+ # main() passes unconditionally; on anything older that is a TypeError
34
+ # before the first socket is bound, one worker or twenty.
35
+ "uvicorn[standard]>=0.37",
33
36
  "sse-starlette>=2.0",
34
37
  "httpx>=0.27",
35
38
  ]
@@ -8826,6 +8826,23 @@ async def ingest_event(
8826
8826
  # CLI Entry Point
8827
8827
  # ──────────────────────────────────────────────────────────────────────
8828
8828
 
8829
+ # With more than one worker, uvicorn runs a supervisor that pings every worker
8830
+ # twice a second and SIGKILLs any that has not answered within
8831
+ # ``timeout_worker_healthcheck`` — 5 seconds by default — then spawns a fresh
8832
+ # one and logs the same "Child process [pid] died" it would for a real crash.
8833
+ # The pong comes from a thread the worker starts on entry, so it needs the GIL,
8834
+ # and a fresh worker does not have one to give: spawn re-imports this module
8835
+ # and its C extensions and the lifespan loads the embedding models, all of it
8836
+ # CPU-bound, all of it competing with the worker next door that is serving
8837
+ # traffic. On a 2-vCPU Cloud Run instance that reliably took longer than 5
8838
+ # seconds, so one slot per instance was killed every 8 seconds forever and
8839
+ # never once logged "Started server process" (2026-09-21, 36 kills a minute
8840
+ # across the fleet, every request that landed on the dying slot truncated).
8841
+ # Two minutes is generous room to start and rides out any GIL hold a request
8842
+ # could plausibly cause, while a worker that is genuinely wedged still gets
8843
+ # replaced. A single worker has no supervisor and is unaffected either way.
8844
+ DEFAULT_WORKER_HEALTHCHECK_S = 120
8845
+
8829
8846
 
8830
8847
  def _parse_args() -> argparse.Namespace:
8831
8848
  parser = argparse.ArgumentParser(
@@ -8862,6 +8879,16 @@ def _parse_args() -> argparse.Namespace:
8862
8879
  default=int(os.environ.get("AMFS_HTTP_WORKERS", "1")),
8863
8880
  help="Number of uvicorn worker processes (default: 1, env: AMFS_HTTP_WORKERS)",
8864
8881
  )
8882
+ parser.add_argument(
8883
+ "--worker-healthcheck-timeout",
8884
+ type=int,
8885
+ default=int(os.environ.get("AMFS_HTTP_WORKER_HEALTHCHECK_S", str(DEFAULT_WORKER_HEALTHCHECK_S))),
8886
+ help=(
8887
+ "Seconds a worker may go without answering the supervisor's ping before "
8888
+ "it is killed and replaced; only meaningful with --workers > 1 "
8889
+ f"(default: {DEFAULT_WORKER_HEALTHCHECK_S}, env: AMFS_HTTP_WORKER_HEALTHCHECK_S)"
8890
+ ),
8891
+ )
8865
8892
  return parser.parse_args()
8866
8893
 
8867
8894
 
@@ -8998,7 +9025,8 @@ def main() -> None:
8998
9025
  workers = 1
8999
9026
 
9000
9027
  logger.info(
9001
- "Starting AMFS HTTP server on %s:%d (workers=%d)", args.host, args.port, workers
9028
+ "Starting AMFS HTTP server on %s:%d (workers=%d, worker healthcheck=%ds)",
9029
+ args.host, args.port, workers, args.worker_healthcheck_timeout,
9002
9030
  )
9003
9031
  uvicorn.run(
9004
9032
  "amfs_http.server:app",
@@ -9006,6 +9034,7 @@ def main() -> None:
9006
9034
  port=args.port,
9007
9035
  reload=args.reload,
9008
9036
  workers=workers,
9037
+ timeout_worker_healthcheck=args.worker_healthcheck_timeout,
9009
9038
  )
9010
9039
 
9011
9040