mq-bridge-py-basic 0.2.17__tar.gz → 0.2.19__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.
Files changed (188) hide show
  1. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/Cargo.lock +3 -2
  2. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/Cargo.toml +12 -3
  3. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/PKG-INFO +1 -1
  4. mq_bridge_py_basic-0.2.19/benches/router_bench.rs +275 -0
  5. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/analysis/http/mq_bridge_http_profile.rs +24 -4
  6. mq_bridge_py_basic-0.2.19/scripts/analysis/http/run_http_ladder.sh +87 -0
  7. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/httparena/README.md +5 -4
  8. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/httparena/frameworks/mq-bridge-py/Dockerfile +1 -1
  9. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/techempower/Python/mq-bridge-py/mq-bridge-py.dockerfile +1 -1
  10. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/the-benchmarker/README.md +1 -1
  11. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/the-benchmarker/python/mq-bridge-py/config.yaml +1 -1
  12. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/amqp.rs +189 -82
  13. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/http.rs +650 -84
  14. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/models.rs +24 -0
  15. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  16. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  17. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  18. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/.github/workflows/benchmark.yml +0 -0
  19. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/.github/workflows/ci.yml +0 -0
  20. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/.github/workflows/ibm-mq.yml +0 -0
  21. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/.github/workflows/publish-python.yml +0 -0
  22. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/.github/workflows/python.yml +0 -0
  23. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/.github/workflows/release.yml +0 -0
  24. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/.github/workflows/test-matrix.yml +0 -0
  25. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/.gitignore +0 -0
  26. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/ARCHITECTURE.md +0 -0
  27. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/CONFIGURATION.md +0 -0
  28. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/CONTRIBUTING.md +0 -0
  29. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/LICENSE +0 -0
  30. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/LICENSE.python +0 -0
  31. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/README.md +0 -0
  32. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/benches/performance_bench.rs +0 -0
  33. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/build.rs +0 -0
  34. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/claude.md +0 -0
  35. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/examples/ipc_worker_queue.yaml +0 -0
  36. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/mq-bridge.schema.json +0 -0
  37. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/mq_bridge/__init__.py +0 -0
  38. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/mq_bridge/__init__.pyi +0 -0
  39. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/mq_bridge/py.typed +0 -0
  40. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/pyproject.toml +0 -0
  41. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/Cargo.toml +0 -0
  42. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/LICENSE.python +0 -0
  43. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/README.md +0 -0
  44. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/analysis/bench_http_native.py +0 -0
  45. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/example.py +0 -0
  46. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/examples/bench_memory.py +0 -0
  47. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/examples/bench_memory.yaml +0 -0
  48. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/examples/json_route.py +0 -0
  49. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/examples/memory.yaml +0 -0
  50. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/examples/raw_route.py +0 -0
  51. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/pyproject-basic.toml +0 -0
  52. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/src/lib.rs +0 -0
  53. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/tests/test_performance_smoke.py +0 -0
  54. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/tests/test_public_api.py +0 -0
  55. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/python/mq-bridge-py/uv.lock +0 -0
  56. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/analysis/HTTP_STREAM_ENDPOINT_PLAN.md +0 -0
  57. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/analysis/benches/metadata_bench.rs +0 -0
  58. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/analysis/debug_ibm_mq_tls.rs +0 -0
  59. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/analysis/http/analyze_xctrace_time_profile.py +0 -0
  60. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/analysis/http/http_flamegraph.py +0 -0
  61. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/httparena/frameworks/mq-bridge-py/meta.json +0 -0
  62. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/httparena/frameworks/mq-bridge-py/server.py +0 -0
  63. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/techempower/Python/mq-bridge-py/README.md +0 -0
  64. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/techempower/Python/mq-bridge-py/benchmark_config.json +0 -0
  65. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/techempower/Python/mq-bridge-py/server.py +0 -0
  66. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/techempower/README.md +0 -0
  67. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/techempower/postgres.yml +0 -0
  68. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/techempower/seed.sql +0 -0
  69. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/techempower/verify.sh +0 -0
  70. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/the-benchmarker/python/mq-bridge-py/pyproject.toml +0 -0
  71. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/scripts/the-benchmarker/python/mq-bridge-py/server.py +0 -0
  72. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/canonical_message.rs +0 -0
  73. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/command_handler.rs +0 -0
  74. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/aws.rs +0 -0
  75. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/fanout.rs +0 -0
  76. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/file.rs +0 -0
  77. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/grpc.proto +0 -0
  78. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/grpc.rs +0 -0
  79. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/http_stream.rs +0 -0
  80. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/ibm_mq.rs +0 -0
  81. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/kafka.rs +0 -0
  82. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/memory/endpoint.rs +0 -0
  83. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/memory/ipc_unix.rs +0 -0
  84. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/memory/ipc_windows.rs +0 -0
  85. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/memory/memory_transport.rs +0 -0
  86. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/memory/mod.rs +0 -0
  87. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/memory/transport.rs +0 -0
  88. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/mod.rs +0 -0
  89. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/mongodb.rs +0 -0
  90. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/mqtt.rs +0 -0
  91. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/nats.rs +0 -0
  92. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/null.rs +0 -0
  93. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/reader.rs +0 -0
  94. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/response.rs +0 -0
  95. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/sled.rs +0 -0
  96. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/sqlx.rs +0 -0
  97. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/static_endpoint.rs +0 -0
  98. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/stream_buffer.rs +0 -0
  99. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/switch.rs +0 -0
  100. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/websocket.rs +0 -0
  101. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/endpoints/zeromq.rs +0 -0
  102. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/errors.rs +0 -0
  103. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/event_handler.rs +0 -0
  104. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/event_store.rs +0 -0
  105. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/extensions.rs +0 -0
  106. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/lib.rs +0 -0
  107. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/middleware/buffer.rs +0 -0
  108. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/middleware/cookie_jar.rs +0 -0
  109. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/middleware/deduplication.rs +0 -0
  110. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/middleware/delay.rs +0 -0
  111. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/middleware/dlq.rs +0 -0
  112. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/middleware/limiter.rs +0 -0
  113. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/middleware/metrics.rs +0 -0
  114. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/middleware/mod.rs +0 -0
  115. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/middleware/random_panic.rs +0 -0
  116. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/middleware/retry.rs +0 -0
  117. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/middleware/weak_join.rs +0 -0
  118. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/outcomes.rs +0 -0
  119. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/publisher.rs +0 -0
  120. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/response.rs +0 -0
  121. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/route.rs +0 -0
  122. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/test_utils.rs +0 -0
  123. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/traits.rs +0 -0
  124. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/src/type_handler.rs +0 -0
  125. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/README.md +0 -0
  126. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/armature_integration.rs +0 -0
  127. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/amqp.rs +0 -0
  128. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/aws.rs +0 -0
  129. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/config.toml +0 -0
  130. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/amqp.yml +0 -0
  131. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/aws.yml +0 -0
  132. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/ibm-mq-certs/client.crl +0 -0
  133. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/ibm-mq-certs/client.kdb +0 -0
  134. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/ibm-mq-certs/client.p12 +0 -0
  135. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/ibm-mq-certs/client.rdb +0 -0
  136. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/ibm-mq-certs/client.sth +0 -0
  137. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/ibm_mq.yml +0 -0
  138. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/ibm_mq_tls.yml +0 -0
  139. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/kafka-tls.yml +0 -0
  140. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/kafka.yml +0 -0
  141. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/mariadb.yml +0 -0
  142. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/mongodb-replica.yml +0 -0
  143. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/mongodb-tls.yml +0 -0
  144. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/mongodb.yml +0 -0
  145. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/mosquitto.conf +0 -0
  146. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/mosquitto_performance.conf +0 -0
  147. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/mosquitto_persistence.conf +0 -0
  148. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/mq_init.mqsc +0 -0
  149. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/mq_init_tls.mqsc +0 -0
  150. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/mqtt.yml +0 -0
  151. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/mqtt_performance.yml +0 -0
  152. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/mysql.yml +0 -0
  153. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/nats.yml +0 -0
  154. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/docker-compose/postgres.yml +0 -0
  155. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/file.rs +0 -0
  156. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/grpc.rs +0 -0
  157. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/grpc_tls.rs +0 -0
  158. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/http.rs +0 -0
  159. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/http_tls.rs +0 -0
  160. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/ibm_mq.rs +0 -0
  161. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/ibm_mq_tls.rs +0 -0
  162. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/ipc.rs +0 -0
  163. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/kafka.rs +0 -0
  164. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/kafka_tls.rs +0 -0
  165. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/logic_test.rs +0 -0
  166. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/mariadb.rs +0 -0
  167. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/memory.rs +0 -0
  168. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/mod.rs +0 -0
  169. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/mongodb.rs +0 -0
  170. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/mongodb_raw.rs +0 -0
  171. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/mongodb_tls.rs +0 -0
  172. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/mqtt.rs +0 -0
  173. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/mysql.rs +0 -0
  174. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/nats.rs +0 -0
  175. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/performance_static.rs +0 -0
  176. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/postgres.rs +0 -0
  177. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/route.rs +0 -0
  178. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/scripts/gen_certs.sh +0 -0
  179. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/sqlite.rs +0 -0
  180. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/tls_helpers.rs +0 -0
  181. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/websocket.rs +0 -0
  182. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration/zeromq.rs +0 -0
  183. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/integration_test.rs +0 -0
  184. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/ref_test.rs +0 -0
  185. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/sqlite_test.rs +0 -0
  186. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/tls_example.rs +0 -0
  187. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/unit_tests.md +0 -0
  188. {mq_bridge_py_basic-0.2.17 → mq_bridge_py_basic-0.2.19}/tests/websocket_test.rs +0 -0
@@ -3057,9 +3057,10 @@ dependencies = [
3057
3057
 
3058
3058
  [[package]]
3059
3059
  name = "mq-bridge"
3060
- version = "0.2.17"
3060
+ version = "0.2.19"
3061
3061
  dependencies = [
3062
3062
  "anyhow",
3063
+ "arc-swap",
3063
3064
  "async-channel",
3064
3065
  "async-nats",
3065
3066
  "async-trait",
@@ -3123,7 +3124,7 @@ dependencies = [
3123
3124
 
3124
3125
  [[package]]
3125
3126
  name = "mq-bridge-py"
3126
- version = "0.2.17"
3127
+ version = "0.2.19"
3127
3128
  dependencies = [
3128
3129
  "anyhow",
3129
3130
  "async-trait",
@@ -16,7 +16,7 @@ members = ["python/mq-bridge-py"]
16
16
  resolver = "2"
17
17
 
18
18
  [workspace.package]
19
- version = "0.2.17"
19
+ version = "0.2.19"
20
20
 
21
21
  [workspace.dependencies]
22
22
  anyhow = "1.0"
@@ -26,7 +26,7 @@ fast-uuid-v7 = "0.1.5"
26
26
  serde = "1.0"
27
27
  serde_json = "1.0"
28
28
  serde_yaml_ng = "0.10"
29
- tokio = "1"
29
+ tokio = "1.34" # `Receiver::recv_many` (HTTP source batch drain) needs 1.34+.
30
30
  tracing = "0.1"
31
31
 
32
32
  [profile.release]
@@ -109,6 +109,7 @@ rumqttc = { version = "0.25", default-features = false, features = ["use-rustls-
109
109
  mqi = { version = "0.3", optional = true }
110
110
 
111
111
  # HTTP
112
+ arc-swap = { version = "1", optional = true }
112
113
  hyper = { version = "1.8", optional = true, features = ["client", "server", "http1", "http2"] }
113
114
  hyper-util = { version = "0.1", optional = true, features = ["tokio", "client", "client-legacy", "server", "http1", "http2"] }
114
115
  hyper-rustls = { version = "0.27", optional = true, default-features = false, features = ["http1", "http2", "tls12", "logging", "native-tokio"] }
@@ -178,7 +179,7 @@ amqp = ["lapin", "rustls", "rustls-pemfile", "url"]
178
179
  nats = ["async-nats", "rustls", "rustls-pemfile"]
179
180
  mongodb = ["dep:mongodb"]
180
181
  mqtt = ["rumqttc", "rustls", "tokio-rustls", "rustls-pemfile", "url"]
181
- http = ["hyper", "hyper-util", "hyper-rustls", "http-body-util", "dep:http-body", "rustls", "tokio-rustls", "rustls-pemfile", "webpki-roots", "h2", "dep:flate2", "dep:base64"]
182
+ http = ["dep:arc-swap", "hyper", "hyper-util", "hyper-rustls", "http-body-util", "dep:http-body", "rustls", "tokio-rustls", "rustls-pemfile", "webpki-roots", "h2", "dep:flate2", "dep:base64"]
182
183
  websocket = ["dep:tokio-tungstenite"]
183
184
  aws = ["aws-config", "aws-sdk-sqs", "aws-sdk-sns", "dep:base64"]
184
185
  ibm-mq = ["mqi"]
@@ -194,6 +195,8 @@ tempfile = "3.10"
194
195
  chrono = "0.4"
195
196
  metrics-util = "0.20"
196
197
  criterion = { version = "0.8", features = ["async", "async_tokio"] }
198
+ # Used by the router_bench microbench to model the proposed lock-free route table.
199
+ arc-swap = "1"
197
200
  mq-bridge = { path = ".", features = ["test-utils", "rustls-ring"] }
198
201
  reqwest = { version = "0.13", features = ["stream"] }
199
202
 
@@ -207,6 +210,12 @@ name = "performance_bench"
207
210
  harness = false
208
211
  required-features = ["test-utils"]
209
212
 
213
+ # Isolates the per-request HTTP route lookup (Mutex<HashMap> vs ArcSwap).
214
+ # No external services or features required.
215
+ [[bench]]
216
+ name = "router_bench"
217
+ harness = false
218
+
210
219
  [[test]]
211
220
  name = "integration_test"
212
221
  required-features = ["test-utils"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mq-bridge-py-basic
3
- Version: 0.2.17
3
+ Version: 0.2.19
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Programming Language :: Python :: 3
@@ -0,0 +1,275 @@
1
+ //! Microbenchmark isolating HTTP route lookup — bottleneck #1 from the
2
+ //! throughput analysis.
3
+ //!
4
+ //! `SharedHttpRouter::match_route` (src/endpoints/http.rs) takes a blocking
5
+ //! `std::sync::Mutex` over a `HashMap` and linearly scans it on EVERY request.
6
+ //! This bench reproduces that exact access pattern in isolation and compares it
7
+ //! with two lock-free alternatives, so we can see — before touching production
8
+ //! code — whether the per-request router lock is what caps multicore scaling:
9
+ //!
10
+ //! * `mutex_hashmap` — current design: `Mutex<HashMap>` + scan + `Arc` clone
11
+ //! * `arcswap_vec` — proposed: `ArcSwap<Vec<..>>` snapshot + scan + `Arc` clone
12
+ //! * `arcswap_single` — proposed fast path for the common single-route server
13
+ //!
14
+ //! Each is measured single-threaded (latency) and under N-thread contention
15
+ //! (aggregate throughput). The hypothesis is confirmed if the mutex variant
16
+ //! collapses as threads are added while the ArcSwap variants stay flat.
17
+ //!
18
+ //! Run with: `cargo bench --bench router_bench`
19
+
20
+ use arc_swap::{ArcSwap, ArcSwapOption};
21
+ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
22
+ use std::collections::HashMap;
23
+ use std::hint::black_box;
24
+ use std::sync::{Arc, Barrier, Mutex};
25
+ use std::thread;
26
+ use std::time::{Duration, Instant};
27
+
28
+ /// Stand-in for `HttpConsumerState`: the two fields `match_route` actually reads,
29
+ /// plus padding so the `Arc` payload is a realistic size (the real struct holds a
30
+ /// channel sender, a semaphore, several strings, etc.). The padding keeps the
31
+ /// comparison honest — a trivially tiny `Arc` would understate clone/cache costs.
32
+ struct RouteState {
33
+ path: Option<String>,
34
+ method: Option<&'static str>,
35
+ id: u64,
36
+ _padding: [u64; 8],
37
+ }
38
+
39
+ impl RouteState {
40
+ fn new(id: u64, path: Option<&str>, method: Option<&'static str>) -> Arc<Self> {
41
+ Arc::new(Self {
42
+ path: path.map(String::from),
43
+ method,
44
+ id,
45
+ _padding: [0; 8],
46
+ })
47
+ }
48
+ }
49
+
50
+ fn route_matches_path(state: &RouteState, path: &str) -> bool {
51
+ match &state.path {
52
+ Some(route_path) => route_path == path,
53
+ None => true,
54
+ }
55
+ }
56
+
57
+ fn route_matches_method(state: &RouteState, method: &str) -> bool {
58
+ match state.method {
59
+ Some(route_method) => route_method == method,
60
+ None => true,
61
+ }
62
+ }
63
+
64
+ fn route_specificity(state: &RouteState) -> (u8, u8) {
65
+ (
66
+ u8::from(state.path.is_some()),
67
+ u8::from(state.method.is_some()),
68
+ )
69
+ }
70
+
71
+ /// Faithful copy of `match_route`'s best-specificity scan.
72
+ fn scan<'a, I>(routes: I, path: &str, method: &str) -> Option<Arc<RouteState>>
73
+ where
74
+ I: Iterator<Item = &'a Arc<RouteState>>,
75
+ {
76
+ let mut best: Option<Arc<RouteState>> = None;
77
+ let mut best_specificity = (0u8, 0u8);
78
+ for state in routes {
79
+ if !route_matches_path(state, path) {
80
+ continue;
81
+ }
82
+ if route_matches_method(state, method) {
83
+ let specificity = route_specificity(state);
84
+ if best.is_none() || specificity > best_specificity {
85
+ best_specificity = specificity;
86
+ best = Some(Arc::clone(state));
87
+ }
88
+ }
89
+ }
90
+ best
91
+ }
92
+
93
+ /// Current production design: blocking lock + `HashMap` scan per lookup.
94
+ struct MutexRouter {
95
+ routes: Mutex<HashMap<u64, Arc<RouteState>>>,
96
+ }
97
+ impl MutexRouter {
98
+ fn lookup(&self, path: &str, method: &str) -> Option<Arc<RouteState>> {
99
+ let routes = self.routes.lock().unwrap();
100
+ scan(routes.values(), path, method)
101
+ }
102
+ }
103
+
104
+ /// Proposed design: lock-free snapshot + scan (handles multi-route servers).
105
+ struct ArcSwapRouter {
106
+ routes: ArcSwap<Vec<Arc<RouteState>>>,
107
+ }
108
+ impl ArcSwapRouter {
109
+ fn lookup(&self, path: &str, method: &str) -> Option<Arc<RouteState>> {
110
+ let routes = self.routes.load();
111
+ scan(routes.iter(), path, method)
112
+ }
113
+ }
114
+
115
+ /// Proposed fast path for the overwhelmingly common single-route server: a
116
+ /// lock-free pointer load with no scan at all.
117
+ struct ArcSwapSingleRouter {
118
+ only: ArcSwapOption<RouteState>,
119
+ }
120
+ impl ArcSwapSingleRouter {
121
+ fn lookup(&self, path: &str, method: &str) -> Option<Arc<RouteState>> {
122
+ let only = self.only.load_full()?;
123
+ if route_matches_path(&only, path) && route_matches_method(&only, method) {
124
+ Some(only)
125
+ } else {
126
+ None
127
+ }
128
+ }
129
+ }
130
+
131
+ fn build_routes(n: usize) -> Vec<Arc<RouteState>> {
132
+ (0..n)
133
+ .map(|i| RouteState::new(i as u64, Some(&format!("/r{i}")), Some("POST")))
134
+ .collect()
135
+ }
136
+
137
+ /// Run `work` on `threads` OS threads, each performing `iters` lookups, started
138
+ /// together via a barrier. Returns wall-clock elapsed for the whole batch.
139
+ ///
140
+ /// criterion divides this by `iters`, so with `Throughput::Elements(threads)`
141
+ /// the reported rate is aggregate lookups/sec across all threads — exactly the
142
+ /// number that should plateau if a shared lock is the bottleneck.
143
+ fn contended(threads: usize, iters: u64, work: Arc<dyn Fn() + Send + Sync>) -> Duration {
144
+ let barrier = Arc::new(Barrier::new(threads + 1));
145
+ let mut handles = Vec::with_capacity(threads);
146
+ for _ in 0..threads {
147
+ let work = Arc::clone(&work);
148
+ let barrier = Arc::clone(&barrier);
149
+ handles.push(thread::spawn(move || {
150
+ barrier.wait();
151
+ for _ in 0..iters {
152
+ work();
153
+ }
154
+ }));
155
+ }
156
+ let start = Instant::now();
157
+ barrier.wait();
158
+ for handle in handles {
159
+ handle.join().unwrap();
160
+ }
161
+ start.elapsed()
162
+ }
163
+
164
+ fn bench_router(c: &mut Criterion) {
165
+ // A handful of routes so the scan has real work; lookups target the last one
166
+ // to force a full scan (worst case for the linear-scan designs).
167
+ let n_routes = 4usize;
168
+ let target_path = format!("/r{}", n_routes - 1);
169
+ let single_path = "/only";
170
+
171
+ let mutex = Arc::new(MutexRouter {
172
+ routes: Mutex::new(
173
+ build_routes(n_routes)
174
+ .into_iter()
175
+ .map(|state| (state.id, state))
176
+ .collect(),
177
+ ),
178
+ });
179
+ let arcswap = Arc::new(ArcSwapRouter {
180
+ routes: ArcSwap::from_pointee(build_routes(n_routes)),
181
+ });
182
+ let single = Arc::new(ArcSwapSingleRouter {
183
+ only: ArcSwapOption::new(Some(RouteState::new(0, Some(single_path), Some("POST")))),
184
+ });
185
+
186
+ // ---- single-threaded latency ----
187
+ {
188
+ let mut group = c.benchmark_group("router_lookup/single_thread");
189
+ group.throughput(Throughput::Elements(1));
190
+ group.bench_function("mutex_hashmap", |b| {
191
+ b.iter(|| black_box(mutex.lookup(black_box(&target_path), black_box("POST"))))
192
+ });
193
+ group.bench_function("arcswap_vec", |b| {
194
+ b.iter(|| black_box(arcswap.lookup(black_box(&target_path), black_box("POST"))))
195
+ });
196
+ group.bench_function("arcswap_single", |b| {
197
+ b.iter(|| black_box(single.lookup(black_box(single_path), black_box("POST"))))
198
+ });
199
+ group.finish();
200
+ }
201
+
202
+ // ---- contended aggregate throughput ----
203
+ let max_threads = thread::available_parallelism()
204
+ .map(|n| n.get())
205
+ .unwrap_or(4);
206
+ let thread_counts: Vec<usize> = {
207
+ let mut counts = vec![1usize, max_threads / 2, max_threads];
208
+ counts.retain(|&t| t >= 1);
209
+ counts.sort_unstable();
210
+ counts.dedup();
211
+ counts
212
+ };
213
+
214
+ let mut group = c.benchmark_group("router_lookup/contended");
215
+ for &threads in &thread_counts {
216
+ group.throughput(Throughput::Elements(threads as u64));
217
+
218
+ group.bench_with_input(
219
+ BenchmarkId::new("mutex_hashmap", threads),
220
+ &threads,
221
+ |b, &t| {
222
+ b.iter_custom(|iters| {
223
+ let router = Arc::clone(&mutex);
224
+ let path = target_path.clone();
225
+ contended(
226
+ t,
227
+ iters,
228
+ Arc::new(move || {
229
+ black_box(router.lookup(&path, "POST"));
230
+ }),
231
+ )
232
+ })
233
+ },
234
+ );
235
+
236
+ group.bench_with_input(
237
+ BenchmarkId::new("arcswap_vec", threads),
238
+ &threads,
239
+ |b, &t| {
240
+ b.iter_custom(|iters| {
241
+ let router = Arc::clone(&arcswap);
242
+ let path = target_path.clone();
243
+ contended(
244
+ t,
245
+ iters,
246
+ Arc::new(move || {
247
+ black_box(router.lookup(&path, "POST"));
248
+ }),
249
+ )
250
+ })
251
+ },
252
+ );
253
+
254
+ group.bench_with_input(
255
+ BenchmarkId::new("arcswap_single", threads),
256
+ &threads,
257
+ |b, &t| {
258
+ b.iter_custom(|iters| {
259
+ let router = Arc::clone(&single);
260
+ contended(
261
+ t,
262
+ iters,
263
+ Arc::new(move || {
264
+ black_box(router.lookup(single_path, "POST"));
265
+ }),
266
+ )
267
+ })
268
+ },
269
+ );
270
+ }
271
+ group.finish();
272
+ }
273
+
274
+ criterion_group!(benches, bench_router);
275
+ criterion_main!(benches);
@@ -136,6 +136,7 @@ enum ProfileMode {
136
136
  Immediate,
137
137
  Metadata,
138
138
  ChannelAck,
139
+ WorkerLocalAck,
139
140
  DirectConsumer,
140
141
  DirectConsumerAck,
141
142
  DirectConsumerFireForget,
@@ -242,6 +243,7 @@ fn parse_mode(value: &str) -> ProfileMode {
242
243
  "immediate" => ProfileMode::Immediate,
243
244
  "metadata" => ProfileMode::Metadata,
244
245
  "channel-ack" | "channel_ack" => ProfileMode::ChannelAck,
246
+ "worker-local-ack" | "worker_local_ack" => ProfileMode::WorkerLocalAck,
245
247
  "direct-consumer" | "direct_consumer" => ProfileMode::DirectConsumer,
246
248
  "direct-consumer-ack" | "direct_consumer_ack" => ProfileMode::DirectConsumerAck,
247
249
  "direct-consumer-fire-forget" | "direct_consumer_fire_forget" => {
@@ -283,7 +285,7 @@ fn print_help() {
283
285
  eprintln!(
284
286
  "Usage: mq_bridge_http_profile [--mode MODE] [--port PORT] [--duration-s SECONDS] [--path PATH]\n\
285
287
  Options:\n\
286
- --mode MODE route | route-fire-forget | route-handler | immediate | metadata | channel-ack | direct-consumer | direct-consumer-ack | direct-consumer-fire-forget | inline-response | inline-body-only (default route)\n\
288
+ --mode MODE route | route-fire-forget | route-handler | immediate | metadata | channel-ack | worker-local-ack | direct-consumer | direct-consumer-ack | direct-consumer-fire-forget | inline-response | inline-body-only (default route)\n\
287
289
  --client-url URL run Rust load client instead of server\n\
288
290
  --clients N Rust load client concurrency (default 8)\n\
289
291
  --header-count N Add N synthetic request headers in Rust load client (default 0)\n\
@@ -335,6 +337,7 @@ async fn run_standalone_server(args: ProfileArgs) -> anyhow::Result<()> {
335
337
  let mode = args.mode;
336
338
  let path = Arc::new(args.path.clone());
337
339
  let duration_s = args.duration_s;
340
+ let buffer_size = args.internal_buffer_size;
338
341
 
339
342
  let shutdown = tokio::time::sleep(std::time::Duration::from_secs(duration_s));
340
343
  tokio::pin!(shutdown);
@@ -350,7 +353,21 @@ async fn run_standalone_server(args: ProfileArgs) -> anyhow::Result<()> {
350
353
  accepted = listener.accept() => {
351
354
  let (stream, _) = accepted?;
352
355
  let io = TokioIo::new(stream);
353
- let tx = channel_tx.clone();
356
+ // WorkerLocalAck gives every connection its own channel + drain task, so
357
+ // there is no shared single-receiver contention (variant B in the ladder).
358
+ // ChannelAck reuses the one process-wide channel (variant C). The
359
+ // per-request work is otherwise identical, isolating the global funnel.
360
+ let tx = if mode == ProfileMode::WorkerLocalAck {
361
+ let (tx, mut rx) = tokio::sync::mpsc::channel::<AckMessage>(buffer_size);
362
+ tokio::spawn(async move {
363
+ while let Some((_payload, _metadata, ack)) = rx.recv().await {
364
+ let _ = ack.send(());
365
+ }
366
+ });
367
+ Some(tx)
368
+ } else {
369
+ channel_tx.clone()
370
+ };
354
371
  let path = path.clone();
355
372
  tokio::spawn(async move {
356
373
  let service = service_fn(move |req| {
@@ -479,7 +496,10 @@ async fn profile_request(
479
496
  .unwrap());
480
497
  }
481
498
 
482
- let metadata = if matches!(mode, ProfileMode::Metadata | ProfileMode::ChannelAck) {
499
+ let metadata = if matches!(
500
+ mode,
501
+ ProfileMode::Metadata | ProfileMode::ChannelAck | ProfileMode::WorkerLocalAck
502
+ ) {
483
503
  build_metadata(&req)
484
504
  } else {
485
505
  HashMap::new()
@@ -495,7 +515,7 @@ async fn profile_request(
495
515
  }
496
516
  };
497
517
 
498
- if mode == ProfileMode::ChannelAck {
518
+ if matches!(mode, ProfileMode::ChannelAck | ProfileMode::WorkerLocalAck) {
499
519
  let Some(tx) = tx else {
500
520
  return Ok(Response::builder()
501
521
  .status(500)
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # HTTP throughput ladder — runs the four benchmark targets from the throughput
4
+ # analysis back-to-back with the same in-tree Rust load client, so each rung adds
5
+ # exactly one layer and the RPS deltas localize the bottleneck.
6
+ #
7
+ # A raw-hyper (immediate) : Hyper + body read + fixed "ok". Ceiling.
8
+ # B worker-local-chan (worker-local-ack) : + per-connection mpsc + oneshot ack (no shared receiver)
9
+ # C global-channel (channel-ack) : + ONE process-wide mpsc + single drain task
10
+ # Dr router+consumer (direct-consumer-ack): real HttpConsumer — router Mutex + per-route mpsc
11
+ # D full-route (route-handler) : full mq-bridge route executor
12
+ #
13
+ # Read the deltas:
14
+ # A -> B : cost of the per-request channel+ack machinery itself (should be small)
15
+ # B -> C : cost of funneling every connection through ONE shared receiver (bottleneck #2)
16
+ # C -> Dr: cost of the router Mutex + real consumer path (bottleneck #1)
17
+ # Dr -> D: cost of the route executor / worker pool / commit sequencer
18
+ #
19
+ # Caveat: A/B/C use a single-acceptor standalone server; Dr/D use the production
20
+ # SO_REUSEPORT multi-worker accept. Compare within {A,B,C} and within {Dr,D}; the
21
+ # router lock itself is isolated cleanly (no accept-model confound) by
22
+ # `cargo bench --bench router_bench`.
23
+ #
24
+ # Usage: scripts/analysis/http/run_http_ladder.sh
25
+ # Env: WORKERS, CLIENTS, DURATION (s), PORT, FEATURES
26
+ set -euo pipefail
27
+
28
+ cd "$(dirname "$0")/../../.."
29
+
30
+ FEATURES="${FEATURES:-http,rustls-ring}"
31
+ if command -v nproc >/dev/null 2>&1; then
32
+ CORES="$(nproc)"
33
+ else
34
+ CORES="$(sysctl -n hw.ncpu 2>/dev/null || echo 4)"
35
+ fi
36
+ WORKERS="${WORKERS:-$CORES}"
37
+ CLIENTS="${CLIENTS:-$CORES}"
38
+ DURATION="${DURATION:-15}"
39
+ PORT="${PORT:-18080}"
40
+ REQ_PATH="/bench"
41
+ BIN=target/release/mq_bridge_http_profile
42
+
43
+ echo ">> building release binary (features: $FEATURES)"
44
+ cargo build --release --bin mq_bridge_http_profile --features "$FEATURES" >/dev/null
45
+
46
+ run_rung() {
47
+ local label="$1" mode="$2" expect="$3"
48
+ local log; log="$(mktemp)"
49
+
50
+ "$BIN" --mode "$mode" --port "$PORT" --path "$REQ_PATH" \
51
+ --workers "$WORKERS" --duration-s "$((DURATION + 8))" >"$log" 2>&1 &
52
+ local server_pid=$!
53
+
54
+ # Wait for the server to announce READY (up to ~10s).
55
+ local ready=0
56
+ for _ in $(seq 1 100); do
57
+ if grep -q READY "$log"; then ready=1; break; fi
58
+ if ! kill -0 "$server_pid" 2>/dev/null; then break; fi
59
+ sleep 0.1
60
+ done
61
+ if [ "$ready" -ne 1 ]; then
62
+ echo " [$label] server failed to start:"; sed 's/^/ /' "$log"
63
+ kill "$server_pid" 2>/dev/null || true; wait "$server_pid" 2>/dev/null || true
64
+ rm -f "$log"; return 1
65
+ fi
66
+
67
+ local out
68
+ out="$("$BIN" --client-url "http://127.0.0.1:${PORT}${REQ_PATH}" \
69
+ --clients "$CLIENTS" --duration-s "$DURATION" --expected-body "$expect" 2>&1 || true)"
70
+ local rps; rps="$(printf '%s' "$out" | sed -n 's/.*(\([0-9]*\) req\/s).*/\1/p')"
71
+ printf ' %-22s %12s req/s\n' "$label" "${rps:-FAILED}"
72
+ if [ -z "$rps" ]; then printf '%s\n' "$out" | sed 's/^/ /'; fi
73
+
74
+ kill "$server_pid" 2>/dev/null || true
75
+ wait "$server_pid" 2>/dev/null || true
76
+ rm -f "$log"
77
+
78
+ if [ -z "$rps" ]; then return 1; fi
79
+ }
80
+
81
+ echo ">> HTTP throughput ladder (workers=$WORKERS clients=$CLIENTS duration=${DURATION}s)"
82
+ run_rung "A raw-hyper" immediate ok
83
+ run_rung "B worker-local-chan" worker-local-ack ok
84
+ run_rung "C global-channel" channel-ack ok
85
+ run_rung "Dr router+consumer" direct-consumer-ack message-processed
86
+ run_rung "D full-route" route-handler payload
87
+ echo ">> done"
@@ -38,12 +38,13 @@ profiles still run.
38
38
 
39
39
  ## How it works
40
40
 
41
- - **One AutoBuilder, every HTTP flavour.** mq-bridge's HTTP server uses
41
+ - **Auto for core, explicit H2 for h2c.** mq-bridge's default HTTP server uses
42
42
  hyper-util's `AutoBuilder`, which negotiates HTTP/1.1 **and** HTTP/2
43
43
  prior-knowledge (h2c) on the same plaintext port. The core `mq-bridge` entry
44
44
  binds **8080** (HTTP/1.1 + h2c) and **8443** (HTTP/2 over TLS) in one process,
45
- sharing the same handler; `mq-bridge-h2c` is the same dispatch on **8082** for
46
- the explicit h2c profiles.
45
+ sharing the same handler; `mq-bridge-h2c` uses the library's
46
+ `server_protocol = http2_only` setting on **8082** so HttpArena's explicit
47
+ h2c profiles cannot fall back to HTTP/1.1.
47
48
  - **HTTP/2 over TLS (`baseline-h2`, `static-h2`).** The library advertises ALPN
48
49
  `h2` on the TLS listener, so conformant clients negotiate HTTP/2; the TLS route
49
50
  reads its cert/key from `/certs/server.crt` + `/certs/server.key` (overridable
@@ -97,7 +98,7 @@ scripts/httparena/frameworks/
97
98
  ## Submitting upstream
98
99
 
99
100
  1. Pin the version: each Rust `Cargo.toml` and the Python `Dockerfile` reference
100
- this repo at tag `v0.2.16` — bump to the release you want to benchmark.
101
+ this repo at tag `v0.2.18` — bump to the release you want to benchmark.
101
102
  2. Fork `MDA2AV/HttpArena` and copy each `scripts/httparena/frameworks/<name>/`
102
103
  into the fork's `frameworks/<name>/`.
103
104
  3. On the PR, validate and benchmark per framework:
@@ -5,7 +5,7 @@
5
5
  # the compiled extension ABI matches. Pin MQB_REF to a released tag.
6
6
  FROM python:3.12-slim AS build
7
7
 
8
- ARG MQB_REF=v0.2.16
8
+ ARG MQB_REF=v0.2.18
9
9
  RUN apt-get update \
10
10
  && apt-get install -y --no-install-recommends curl build-essential git ca-certificates \
11
11
  && rm -rf /var/lib/apt/lists/*
@@ -5,7 +5,7 @@
5
5
  # matches. For the upstream PR, pin MQB_REF to a released tag.
6
6
  FROM python:3.12-slim AS build
7
7
 
8
- ARG MQB_REF=v0.2.16
8
+ ARG MQB_REF=v0.2.18
9
9
  RUN apt-get update \
10
10
  && apt-get install -y --no-install-recommends curl build-essential git ca-certificates \
11
11
  && rm -rf /var/lib/apt/lists/*
@@ -45,7 +45,7 @@ installs Rust + maturin and builds `mq_bridge_py` from this repo at the pinned
45
45
  ## Submitting upstream
46
46
 
47
47
  1. Pin the version: the Rust `Cargo.toml` and Python `config.yaml` reference this
48
- repo at tag `v0.2.16` — bump to the release you want to benchmark.
48
+ repo at tag `v0.2.18` — bump to the release you want to benchmark.
49
49
  2. Fork `the-benchmarker/web-frameworks` and copy:
50
50
  - `scripts/the-benchmarker/rust/mq-bridge/` → `rust/mq-bridge/`
51
51
  - `scripts/the-benchmarker/python/mq-bridge-py/` → `python/mq-bridge-py/`
@@ -24,7 +24,7 @@ framework:
24
24
  bootstrap:
25
25
  - curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal
26
26
  - . "$HOME/.cargo/env" && pip install maturin
27
- - git clone --depth 1 -b v0.2.16 https://github.com/marcomq/mq-bridge /tmp/mqb
27
+ - git clone --depth 1 -b v0.2.18 https://github.com/marcomq/mq-bridge /tmp/mqb
28
28
  - . "$HOME/.cargo/env" && cd /tmp/mqb/python/mq-bridge-py && maturin build --release --no-default-features -F http -F pyo3/extension-module -o /tmp/wheels
29
29
  - pip install /tmp/wheels/*.whl
30
30