Skip to content

pyronova: bump to v2.3.1, unlock sub-interp DB bridge + size GIL pool#623

Merged
MDA2AV merged 5 commits intoMDA2AV:mainfrom
moomoo-tech:pyronova-v2.3.1
Apr 28, 2026
Merged

pyronova: bump to v2.3.1, unlock sub-interp DB bridge + size GIL pool#623
MDA2AV merged 5 commits intoMDA2AV:mainfrom
moomoo-tech:pyronova-v2.3.1

Conversation

@ddxd
Copy link
Copy Markdown
Contributor

@ddxd ddxd commented Apr 23, 2026

Updates Pyronova from v2.2.0 → v2.3.1 via PYRONOVA_REF + a small launcher tweak. No app.py changes.

What changed in Pyronova between v2.2.0 and v2.3.1

1. Sub-interpreter DB bridge now works under TPC (src/bridge/db_bridge.rs)

The bridge existed in v2.2 but panicked the moment a route without gil=True hit it under TPC mode:

thread 'pyronova-tpc-N' panicked at tokio ... multi_thread/mod.rs:88:
Cannot start a runtime from within a runtime.

Cause: the bridge's C-FFI entry points called rt.block_on(fut) on the dedicated DB runtime, from inside the TPC worker thread's own current_thread tokio runtime. tokio forbids nested block_on.

Fix: rt.spawn(fut) + std::sync::mpsc::sync_channel + rx.recv(). spawn has no runtime-context check — it just queues the task onto the DB runtime's worker pool. The sub-interp worker blocks on the channel with the GIL released (py.detach), so peer sub-interpreters keep running during the query. Parallelism ceiling is min(sub_interp_workers, DATABASE_MAX_CONN) instead of the single main-interp thread.

2. Main-interp gil=True bridge defaults to N-worker pool (src/bridge/main_bridge.rs)

Used by the crud routes (their cache-aside dict semantics require a single interpreter — sub-interp workers have independent heaps, so SO_REUSEPORT would route consecutive GET /crud/items/42 hits to different workers and the HttpArena cache-aside validator would never see MISSHIT).

Previously a single std::thread; v2.3 is a crossbeam::bounded MPMC queue served by N threads. This PR's launcher change sets PYRONOVA_GIL_BRIDGE_WORKERS=16 + PYRONOVA_GIL_BRIDGE_CAPACITY=8192 so the 1024–4096-conn profiles don't 503-storm on a 64-deep default channel.

3. TPC becomes the default dispatch mode

Flipped in 0ae579c upstream. The Arena leaderboard's current v2.2.0 numbers are from hybrid mode (N sub-interp pool + N io threads, with per-request crossbeam_channel dispatch across workers). TPC replaces that with per-thread sub-interpreter + same-thread handler call — zero cross-thread wake, zero cross-CCD atomic contention on the hot path. On the Arena 32-physical-core EPYC, this should lift baseline / short-lived / json proportionally, not just async-db.

Measured impact (local)

Linux 7840HS 16-thread, Postgres sidecar, wrk -t8:

Profile v2.2.0 (hybrid) v2.3.1 (TPC) Δ
/async-db @ c=64 3.7k 30k ≈8×
/async-db @ c=1024 3.7k 35k ≈9×
/async-db @ c=4096 3.7k 34k ≈9×

All profiles: 0 non-2xx across c=64..4096.

validate.sh pyronova: 49 passed, 0 failed (verified locally against the current scripts/validate.sh).

Compatibility

  • No app.py changes. The previously-submitted /async-db handler (without gil=True) and crud handlers (with gil=True) both work unchanged with the v2.3.1 engine.
  • Pyronova's gil=True contract is preserved: pydantic-core, numpy, and any other main-interp-only extension still works.
  • The engine is source-cloned from https://github.com/moomoo-tech/pyronova at tag v2.3.1 (existing Dockerfile pattern, just bumping the ref).

🤖 Generated with Claude Code

ddxd added 4 commits April 22, 2026 18:21
Changes:
- PYRONOVA_REF: v2.0.2 -> v2.1.5
- app.py: add warning-level logging on benchmark-path errors; fix CRUD
  endpoint paths/response shape to match aspnet-minimal reference;
  restore gil=True on async-db (PG_POOL lives on main interp); widen
  upload limit to 25MB
- launcher.py: NUMA-aware io_workers cap (avoid oversubscription on
  multi-socket boxes)
- meta.json: subscribe to crud + unary-grpc{,-tls} + api-{4,16}

v2.1.5 highlights (see moomoo-tech/pyronova CHANGELOG):
- Per-worker sharded channels replacing the single MPMC crossbeam queue
  (eliminates cross-CCD cache-line bouncing on AMD multi-CCD boxes)
- TCP_DEFER_ACCEPT, slowloris/HPP hardening, Py_SETREF correctness fix
- In-flight-aware P2C load balancing
- HOL body streaming + bounded WS channel
Arena's validate.sh reads `X-Cache` (MISS/HIT) via
`curl | grep ^x-cache:` under `set -o pipefail`. Without the header
the pipeline fails silently and terminates the whole script before
fail_with_link has a chance to print anything — exactly what we saw on
the last CI run ("PASS [GET /crud/items/1]" → cleanup → exit 1, no
diagnostic in between).

Fix: wrap every crud_get_one return path in Response with the
appropriate X-Cache header (MISS on first fetch / error / 404, HIT on
cache-aside return). Cache now stores a pre-serialized JSON string so
the HIT path skips json.dumps on every hit.

No behavior change for any other profile.
v2.2.0 adds a C-FFI DB bridge (4 functions injected into every
sub-interp's globals) that forwards sqlx calls onto the shared
process-global pool while releasing the calling interp's GIL. This
removes the single-GIL ceiling that was capping /async-db at 3.7k rps
on the previous v2.1.5 run.

/crud/* endpoints keep gil=True for now — their in-process dict cache
relies on main-interp GIL serialization for the MISS→HIT semantics the
validator checks. Moving that cache to a SharedState-backed DashMap
unblocks /crud too, tracked as a v2.3 follow-up.

Expected impact this run (64-core TR 3995WX):
- async-db: 3.7k → 30-50k rps (bridge ceiling ≈ min(cores, PG max_conn))
- api-4 / api-16: partial improvement (CRUD sub-profile still gil=True)
- Other profiles: unchanged

See docs/arena-async-db-and-static.md in pyronova repo for the full
design doc.
Updates Pyronova from v2.2.0 → v2.3.1 via PYRONOVA_REF. Two changes
in the Pyronova engine itself that affect Arena numbers:

1. Sub-interpreter DB bridge now works under TPC
   (src/bridge/db_bridge.rs). The bridge existed in v2.2 but panicked
   under TPC mode with "Cannot start a runtime from within a runtime":
   `rt.block_on(fut)` inside each sub-interp worker's tokio
   current_thread runtime is forbidden by tokio. Fixed by
   channel-dispatching to the DB runtime (`rt.spawn + std::mpsc::recv`)
   instead of nested block_on. The existing /async-db handler (no
   gil=True) now scales across all sub-interps with independent GILs
   instead of 503-ing on the single-thread main bridge.

2. Main-interp gil=True bridge defaults to N-worker pool
   (src/bridge/main_bridge.rs). Used by crud routes (their
   cache-aside dict semantics require a single interpreter). The
   launcher sets PYRONOVA_GIL_BRIDGE_WORKERS=16 + CAPACITY=8192 so
   the 1024+ concurrency profiles don't overflow the default 64-deep
   channel with a 503 storm. Verified locally at c=4096: 15k req/s
   steady, 0 drops.

Both fixes preserve Pyronova's gil=True contract — pydantic-core /
numpy / any other main-interp-only extension still works unchanged.

Measured locally (Linux 7840HS 16-thread, PG sidecar, wrk -t8):
  /async-db @ c=4096: 3.7k (v2.2.0) → 34k req/s (v2.3.1)   ≈9×
  /async-db @ c=64:   3.7k          → 30k req/s            ≈8×
  All profiles: 0 non-2xx responses across c=64..4096.

TPC also becomes the default dispatch mode in v2.3.x (flipped in
0ae579c upstream). The Arena leaderboard's current v2.2.0 numbers
were hybrid-mode; TPC's per-core pinning + leaked route tables
should give a proportional lift to baseline / short-lived / json
profiles too, not just async-db.

validate.sh pyronova locally: 49 passed, 0 failed.
@MDA2AV
Copy link
Copy Markdown
Owner

MDA2AV commented Apr 25, 2026

/benchmark -f pyronova --save

@github-actions
Copy link
Copy Markdown
Contributor

👋 /benchmark request received. A collaborator will review and approve the run.

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ /benchmark --save aborted: main has diverged and cannot be auto-merged into this branch. Please merge or rebase main manually, push, and re-run /benchmark --save.

@ddxd
Copy link
Copy Markdown
Contributor Author

ddxd commented Apr 28, 2026

/benchmark -f pyronova --save

@github-actions
Copy link
Copy Markdown
Contributor

👋 /benchmark request received. A collaborator will review and approve the run.

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark Results

Framework: pyronova | Test: all tests

Test Conn RPS CPU Mem Δ RPS Δ Mem
baseline 512 1,163,283 3454.3% 1.2GiB +247.7% +9.1%
baseline 4096 1,155,627 3467.8% 1.3GiB +267.3% +8.3%
pipelined 512 2,724,187 3239.7% 1.2GiB +5.2% +9.1%
pipelined 4096 2,817,805 3313.3% 1.3GiB +10.9% +8.3%
limited-conn 512 789,576 3161.4% 1.2GiB +197.1% +9.1%
limited-conn 4096 931,761 3415.0% 1.4GiB +212.7% +16.7%
json 4096 332,300 3235.9% 1.4GiB -1.6% +7.7%
json-comp 512 159,963 3107.8% 1.2GiB -16.2% +9.1%
json-comp 4096 166,344 3230.1% 1.4GiB -16.0% +7.7%
json-comp 16384 163,499 3120.5% 2.0GiB +0.6% +5.3%
json-tls 4096 319,566 3265.9% 1.5GiB -2.8% +7.1%
upload 32 2,686 1131.2% 1.7GiB +16.0% ~0%
upload 256 2,503 1258.9% 2.2GiB -8.4% +4.8%
api-4 256 9,803 164.6% 201MiB -20.4% -63.1%
api-16 1024 35,533 641.7% 462MiB +212.3% -59.0%
static 1024 274,943 3288.3% 2.1GiB +187.2% -8.7%
static 4096 239,891 3322.3% 4.5GiB +174.2% +15.4%
static 6800 240,757 3322.9% 6.1GiB +172.1% +7.0%
async-db 1024 27,020 785.7% 1.2GiB +627.3% -36.8%
crud 4096 12,352 296.4% 1.3GiB NEW NEW
baseline-h2 256 2,595,268 3208.4% 1.3GiB +760.9% ~0%
baseline-h2 1024 2,350,376 3211.5% 1.8GiB +648.0% +12.5%
static-h2 256 195,286 3230.2% 2.0GiB +137.2% +42.9%
static-h2 1024 189,429 3273.8% 2.4GiB +124.8% ~0%
unary-grpc 256 4,258,127 3160.7% 1.4GiB NEW NEW
unary-grpc 1024 3,269,151 3208.4% 2.0GiB NEW NEW
unary-grpc-tls 256 4,151,332 3210.6% 1.4GiB NEW NEW
unary-grpc-tls 1024 3,194,316 3207.4% 2.0GiB NEW NEW
Full log
Application protocol: h2
11. Stopping all clients.


finished in 5.03s, 4176240.00 req/s, 151.36MB/s
requests: 20881200 total, 20906800 started, 20881200 done, 20881200 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 20881200 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 756.79MB (793546694) total, 79.69MB (83566016) headers (space savings 95.18%), 139.40MB (146168400) data
                     min         max         mean         sd        +/- sd
time for request:      880us     14.48ms      6.21ms      2.84ms    62.83%
time for connect:     2.07ms     29.22ms     14.21ms      6.20ms    60.55%
time to 1st byte:    10.11ms     38.64ms     26.09ms      4.50ms    75.39%
req/s           :    7899.08    47276.88    16311.86     8022.21    83.20%
[info] CPU 3210.6% | Mem 1.4GiB

=== Best: 4151332 req/s (CPU: 3210.6%, Mem: 1.4GiB) ===
[info] saved results/unary-grpc-tls/256/pyronova.json
httparena-bench-pyronova
httparena-bench-pyronova

==============================================
=== pyronova / unary-grpc-tls / 1024c (tool=h2load) ===
==============================================
[info] waiting for server...
[info] gRPC server ready

[run 1/3]
starting benchmark...
.
21.
7.
50.
5552.
.
. Stopping all clients.29. Stopping all clients.

31Stopped all clients for thread #29
1Stopped all clients for thread #Main benchmark duration is over for thread #. Stopping all clients.. Stopping all clients.11. Stopping all clients.


. Stopping all clients.17Stopped all clients for thread #63Stopped all clients for thread #1131

39Main benchmark duration is over for thread #6. Stopping all clients.
. Stopping all clients.Stopped all clients for thread #6
34Main benchmark duration is over for thread #
37. Stopping all clients.
4. Stopping all clients.
1
42. Stopping all clients.Stopped all clients for thread #Stopped all clients for thread #

. Stopping all clients.Stopped all clients for thread #49
19



37Stopped all clients for thread #10

15. Stopping all clients.
5. Stopping all clients.
16. Stopping all clients.
61Stopped all clients for thread #36
. Stopping all clients.

2. Stopping all clients.
46. Stopping all clients.Stopped all clients for thread #51
5955
. Stopping all clients.. Stopping all clients.

5838. Stopping all clients.. Stopping all clients.Stopped all clients for thread #57


56Stopped all clients for thread #. Stopping all clients.Stopped all clients for thread #38

55


finished in 5.01s, 0.00 req/s, 0B/s
requests: 0 total, 0 started, 0 done, 0 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 0 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 0B (0) total, 0B (0) headers (space savings 0.00%), 0B (0) data
                     min         max         mean         sd        +/- sd
time for request:        0us         0us         0us         0us     0.00%
time for connect:        0us         0us         0us         0us     0.00%
time to 1st byte:        0us         0us         0us         0us     0.00%
req/s           :       0.00        0.00        0.00        0.00   100.00%
[info] CPU 144.7% | Mem 1.1GiB

[run 2/3]
starting benchmark...
37.
TLS Protocol: TLSv1.3
Cipher: TLS_AES_128_GCM_SHA256
Server Temp Key: X25519 253 bits
Application protocol: h2
.
60.

21
.
34.
17Main benchmark duration is started for thread #34.
.Warm-up phase is over for thread #Main benchmark duration is started for thread #58.
7.

.
13. Stopping all clients.Stopped all clients for thread #6

63. Stopping all clients.
22. Stopping all clients.


41. Stopping all clients.
. Stopping all clients.Stopped all clients for thread #9

27. Stopping all clients.

finished in 5.05s, 3199800.00 req/s, 116.00MB/s
requests: 15999000 total, 16101400 started, 15999000 done, 15999000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 15999000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 579.99MB (608167015) total, 61.17MB (64144695) headers (space savings 95.17%), 106.80MB (111993000) data
                     min         max         mean         sd        +/- sd
time for request:      871us    121.66ms     30.16ms      9.12ms    77.52%
time for connect:     5.03ms    154.65ms     80.09ms     41.58ms    57.13%
time to 1st byte:    41.27ms    211.69ms    150.07ms     37.23ms    59.47%
req/s           :    2119.28     6018.38     3124.10      633.31    73.24%
[info] CPU 3117.4% | Mem 1.9GiB

[run 3/3]
starting benchmark...
.

8.
TLS Protocol: TLSv1.3
Cipher: TLS_AES_128_GCM_SHA256
Server Temp Key: X25519 253 bits
Application protocol: h2
.
3.
62..

38.
4. Stopping all clients.

2. Stopping all clients.
39

finished in 5.05s, 3226260.00 req/s, 116.96MB/s
requests: 16131300 total, 16233700 started, 16131300 done, 16131300 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 16131300 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 584.79MB (613196853) total, 61.68MB (64676333) headers (space savings 95.17%), 107.69MB (112919100) data
                     min         max         mean         sd        +/- sd
time for request:     1.16ms     56.48ms     29.54ms      8.24ms    75.36%
time for connect:     4.53ms    127.58ms     55.43ms     32.73ms    58.20%
time to 1st byte:    51.54ms    169.25ms    118.98ms     32.78ms    70.21%
req/s           :    2438.89     5019.66     3149.99      518.21    68.07%
[info] CPU 3207.4% | Mem 2.0GiB

=== Best: 3194316 req/s (CPU: 3207.4%, Mem: 2.0GiB) ===
[info] saved results/unary-grpc-tls/1024/pyronova.json
httparena-bench-pyronova
httparena-bench-pyronova
[info] skip: pyronova does not subscribe to stream-grpc
[info] skip: pyronova does not subscribe to stream-grpc-tls
[info] skip: pyronova does not subscribe to echo-ws
[info] rebuilding site/data/*.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/frameworks.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/api-16-1024.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/api-4-256.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/async-db-1024.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/baseline-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/baseline-512.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/baseline-h2-1024.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/baseline-h2-256.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/crud-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/json-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/json-comp-16384.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/json-comp-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/json-comp-512.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/json-tls-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/limited-conn-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/limited-conn-512.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/pipelined-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/pipelined-512.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/static-1024.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/static-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/static-6800.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/static-h2-1024.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/static-h2-256.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/unary-grpc-1024.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/unary-grpc-256.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/unary-grpc-tls-1024.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/unary-grpc-tls-256.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/upload-256.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/upload-32.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/current.json
[info] done
httparena-postgres
httparena-redis
[info] restoring loopback MTU to 65536
[info] restoring CPU governor → powersave

@MDA2AV MDA2AV merged commit 0566d2d into MDA2AV:main Apr 28, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants