Преглед изворни кода

Merge pull request #28 from TechEmpower/master

aa
三刀 пре 2 година
родитељ
комит
8433e23947
100 измењених фајлова са 1459 додато и 690 уклоњено
  1. 1 1
      .github/PULL_REQUEST_TEMPLATE.md
  2. 22 17
      .github/workflows/build.yml
  3. 1 1
      .github/workflows/label-failing-pr.yml
  4. 5 3
      .gitignore
  5. 41 9
      Dockerfile
  6. 6 0
      entrypoint.sh
  7. 2 1
      frameworks/C++/cpoll_cppsp/benchmark_config.json
  8. 1 1
      frameworks/C++/ffead-cpp/benchmark_config.json
  9. 1 1
      frameworks/C++/treefrog/treefrog-epoll.dockerfile
  10. 1 1
      frameworks/C++/treefrog/treefrog-mongodb.dockerfile
  11. 1 1
      frameworks/C++/treefrog/treefrog-mysql.dockerfile
  12. 1 1
      frameworks/C++/treefrog/treefrog.dockerfile
  13. 2 0
      frameworks/C++/userver/benchmark_config.json
  14. 2 0
      frameworks/C++/userver/config.toml
  15. 4 2
      frameworks/C++/userver/userver-bare.dockerfile
  16. 4 2
      frameworks/C++/userver/userver.dockerfile
  17. 3 0
      frameworks/C++/userver/userver_benchmark/.clang-format
  18. 0 1
      frameworks/C++/userver/userver_benchmark/bare/simple_connection.hpp
  19. 0 1
      frameworks/C++/userver/userver_benchmark/bare/simple_response.hpp
  20. 13 4
      frameworks/C++/userver/userver_benchmark/bare/simple_router.cpp
  21. 4 1
      frameworks/C++/userver/userver_benchmark/bare/simple_router.hpp
  22. 0 1
      frameworks/C++/userver/userver_benchmark/bare/simple_server.cpp
  23. 5 4
      frameworks/C++/userver/userver_benchmark/common/db_helpers.cpp
  24. 161 0
      frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.cpp
  25. 28 0
      frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.hpp
  26. 21 10
      frameworks/C++/userver/userver_benchmark/controllers/multiple_queries/handler.cpp
  27. 6 4
      frameworks/C++/userver/userver_benchmark/controllers/plaintext/handler.cpp
  28. 28 17
      frameworks/C++/userver/userver_benchmark/controllers/updates/handler.cpp
  29. 36 1
      frameworks/C++/userver/userver_benchmark/userver_techempower.cpp
  30. 7 7
      frameworks/C++/userver/userver_configs/dynamic_config_fallback.json
  31. 18 4
      frameworks/C++/userver/userver_configs/static_config.yaml
  32. 2 2
      frameworks/C/h2o/CMakeLists.txt
  33. 4 4
      frameworks/C/h2o/README.md
  34. 83 37
      frameworks/C/h2o/h2o.dockerfile
  35. 0 88
      frameworks/C/h2o/h2o.sh
  36. 47 35
      frameworks/C/h2o/src/database.c
  37. 0 1
      frameworks/C/h2o/src/database.h
  38. 1 0
      frameworks/C/h2o/src/global_data.h
  39. 14 62
      frameworks/C/h2o/src/handlers/fortune.c
  40. 11 1
      frameworks/C/h2o/src/main.c
  41. 1 0
      frameworks/C/h2o/src/request_handler.c
  42. 18 16
      frameworks/C/h2o/src/thread.c
  43. 1 1
      frameworks/CFML/CFML/cfml-adobe.dockerfile
  44. 1 1
      frameworks/CFML/CFML/cfml.dockerfile
  45. 1 1
      frameworks/CFML/coldbox/coldbox-adobe.dockerfile
  46. 1 1
      frameworks/CFML/coldbox/coldbox.dockerfile
  47. 1 1
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.Caching.cs
  48. 41 31
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs
  49. 12 1
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs
  50. 1 1
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.Json.cs
  51. 3 3
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.MultipleQueries.cs
  52. 2 2
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.SingleQuery.cs
  53. 3 3
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.Updates.cs
  54. 62 42
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.cs
  55. 1 1
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkConfigurationHelpers.cs
  56. 2 2
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/BufferWriter.cs
  57. 241 0
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/ChunkedBufferWriter.cs
  58. 13 9
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/BatchUpdateString.cs
  59. 3 3
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/FortuneUtf16.cs
  60. 22 0
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/FortuneUtf8.cs
  61. 4 4
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/Providers/RawDbMySqlConnector.cs
  62. 112 108
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/Providers/RawDbNpgsql.cs
  63. 1 4
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/DateHeader.cs
  64. 2 1
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/PlatformBenchmarks.csproj
  65. 33 15
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/Program.cs
  66. 2 0
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/Templates/FortunesUtf16.cshtml
  67. 2 0
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/Templates/FortunesUtf8.cshtml
  68. 9 0
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/Templates/_ViewImports.cshtml
  69. 2 1
      frameworks/CSharp/aspnetcore/PlatformBenchmarks/appsettings.json
  70. 2 1
      frameworks/CSharp/evhttp-sharp/benchmark_config.json
  71. 2 1
      frameworks/CSharp/watson/benchmark_config.json
  72. 3 3
      frameworks/Clojure/aleph/README.md
  73. 3 3
      frameworks/Clojure/aleph/aleph.dockerfile
  74. 5 6
      frameworks/Clojure/aleph/project.clj
  75. 4 2
      frameworks/Clojure/duct/benchmark_config.json
  76. 1 1
      frameworks/Crystal/spider-gazelle/config.toml
  77. 5 0
      frameworks/Crystal/spider-gazelle/run.sh
  78. 70 0
      frameworks/Crystal/spider-gazelle/shard.lock
  79. 2 14
      frameworks/Crystal/spider-gazelle/shard.yml
  80. 9 3
      frameworks/Crystal/spider-gazelle/spider-gazelle.dockerfile
  81. 4 4
      frameworks/Crystal/spider-gazelle/src/config.cr
  82. 1 1
      frameworks/Crystal/spider-gazelle/src/controllers/application.cr
  83. 15 21
      frameworks/Crystal/spider-gazelle/src/controllers/benchmark.cr
  84. 4 7
      frameworks/Crystal/spider-gazelle/src/models/fortune.cr
  85. 4 7
      frameworks/Crystal/spider-gazelle/src/models/world.cr
  86. 4 2
      frameworks/D/vibed/benchmark_config.json
  87. 1 1
      frameworks/Dart/angel3/angel3-mysql.dockerfile
  88. 1 1
      frameworks/Dart/angel3/angel3.dockerfile
  89. 2 2
      frameworks/Dart/angel3/benchmark_config.json
  90. 2 1
      frameworks/Dart/start/benchmark_config.json
  91. 2 1
      frameworks/Dart/stream/benchmark_config.json
  92. 1 1
      frameworks/Elixir/phoenix/config/bandit.exs
  93. 65 26
      frameworks/Elixir/phoenix/lib/hello_web.ex
  94. 2 2
      frameworks/Elixir/phoenix/lib/hello_web/controllers/error_html.ex
  95. 19 0
      frameworks/Elixir/phoenix/lib/hello_web/controllers/error_json.ex
  96. 2 1
      frameworks/Elixir/phoenix/lib/hello_web/controllers/page_controller.ex
  97. 9 0
      frameworks/Elixir/phoenix/lib/hello_web/controllers/page_html.ex
  98. 0 0
      frameworks/Elixir/phoenix/lib/hello_web/controllers/page_html/fortunes.html.eex
  99. 24 0
      frameworks/Elixir/phoenix/lib/hello_web/gettext.ex
  100. 7 0
      frameworks/Elixir/phoenix/lib/hello_web/layouts.ex

+ 1 - 1
.github/PULL_REQUEST_TEMPLATE.md

@@ -1,7 +1,7 @@
 <!--
 Thank you for submitting to the TechEmpower Framework Benchmarks!
 
-If you are submitting a new framework, please make sure that you add the appropriate line in the `.github/workflows/build.yml` file for proper integration testing. Also please make sure that an appropriate `README.md` is added in your framework directory with information about the framework and a link to its homepage and documentation.
+If you are submitting a new framework, please make sure that an appropriate `README.md` is added in your framework directory with information about the framework and a link to its homepage and documentation.
 
 For new frameworks, please do not include source code that isn't required for the benchmarks.
 

+ 22 - 17
.github/workflows/build.yml

@@ -2,7 +2,7 @@ name: build
 on: [ push, pull_request ]
 jobs:
   setup:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
       # Required for workflow triggers like the auto-label for failing PRs
       - name: Save PR number
@@ -20,7 +20,7 @@ jobs:
       #
       # We need to fetch more than one commit to be able to access HEAD^2 in case
       # of a pull request
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           fetch-depth: 10
       # In case of a push event, the commit we care about is simply HEAD.
@@ -46,7 +46,7 @@ jobs:
           echo "$(git log --format=%B -n 1 HEAD^2)" >> $GITHUB_ENV
           echo "EOF" >> $GITHUB_ENV
           echo "PREVIOUS_COMMIT=$(git log --format=%H -n 1 HEAD^2~1)" >> $GITHUB_ENV
-      - uses: actions/setup-python@v2
+      - uses: actions/setup-python@v4
         with:
           python-version: '2.7'
           architecture: 'x64'
@@ -65,14 +65,16 @@ jobs:
       - id: event_out
         name: Write event outputs
         run: |
-          # Escape the multiline string for Github Actions, see https://github.community/t/set-output-truncates-multiline-strings/16852/3
-          COMMIT_MESSAGE="${COMMIT_MESSAGE//'%'/'%25'}"
-          COMMIT_MESSAGE="${COMMIT_MESSAGE//$'\n'/'%0A'}"
-          COMMIT_MESSAGE="${COMMIT_MESSAGE//$'\r'/'%0D'}"
-          echo "::set-output name=commit_message::$COMMIT_MESSAGE"
-          echo "::set-output name=branch_name::$BRANCH_NAME"
-          echo "::set-output name=target_branch_name::$TARGET_BRANCH_NAME"
-          echo "::set-output name=previous_commit::$PREVIOUS_COMMIT"
+          # Escape the multiline string for Github Actions, see https://lab.amalitsky.com/posts/2022/github-actions-set-output-migration/#migration-from-set-output-to-github-output
+          delimiter="$(openssl rand -hex 8)"
+              {
+                echo "commit_message<<${delimiter}"
+                echo $COMMIT_MESSAGE
+                echo "${delimiter}"
+              } >> $GITHUB_OUTPUT
+          echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
+          echo "target_branch_name=$TARGET_BRANCH_NAME" >> $GITHUB_OUTPUT
+          echo "previous_commit=$PREVIOUS_COMMIT" >> $GITHUB_OUTPUT
       - id: verify_out
         name: Write verify job matrix
         run: |
@@ -82,7 +84,7 @@ jobs:
           #   - TESTLANG: {lang}
           # with a TESTLANG object in the include array for each language under frameworks
           VERIFY_MATRIX=$(ls -1 frameworks | jq -Rc '.+"/" | select(inside(env.RUN_TESTS))  | rtrimstr("/")' | jq -sc '{include: map({TESTLANG: .})}')
-          echo "::set-output name=verify_matrix::$VERIFY_MATRIX"
+          echo "verify_matrix=$VERIFY_MATRIX" >> $GITHUB_OUTPUT
     outputs:
       commit_message: ${{ steps.event_out.outputs.commit_message }}
       branch_name: ${{ steps.event_out.outputs.branch_name }}
@@ -93,7 +95,7 @@ jobs:
     needs: setup
     # The matrix check is necessary because an empty job matrix is otherwise considered a workflow failure
     if: ${{ !contains(needs.setup.outputs.commit_message, '[ci skip]') && contains(needs.setup.outputs.verify_matrix, 'TESTLANG') }}
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-22.04
     strategy:
       matrix: ${{ fromJSON(needs.setup.outputs.verify_matrix) }}
       # Disable fail-fast to allow all failing frameworks/etc to fail in a
@@ -108,10 +110,10 @@ jobs:
       PREVIOUS_COMMIT: ${{ needs.setup.outputs.previous_commit }}
       PR_NUMBER: ${{ github.event.pull_request.number }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           fetch-depth: 10
-      - uses: actions/setup-python@v2
+      - uses: actions/setup-python@v4
         with:
           python-version: '2.7'
           architecture: 'x64'
@@ -138,7 +140,10 @@ jobs:
           if [ "$RUN_TESTS" ]; then echo "Proceeding to run tests."; else echo 'Skipping test verification.'; fi
       - name: Build tfb dockerfile
         if: ${{ env.RUN_TESTS }}
-        run: docker build -t techempower/tfb - < ./Dockerfile;
+        uses: mattes/cached-docker-build-action@v1
+        with:
+          args: " --file ./Dockerfile --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g) --tag techempower/tfb ."
+          cache_key: "${{ hashFiles('./Dockerfile') }}"
       - name: Stop services
         # Stop services that would claim ports we may need
         run: |
@@ -150,7 +155,7 @@ jobs:
           # run-ci.py runs the diffing to see if github actions needs to test this framework. Ideally/eventually,
           # we'd like to try and do the diffing before github_actions_clean & setup.
           # This will run the tests exactly as you would in your own vm:
-          docker network create tfb > /dev/null 2>&1 && docker run --network=tfb -v /var/run/docker.sock:/var/run/docker.sock --mount type=bind,source=`pwd`,target=/FrameworkBenchmarks techempower/tfb --mode verify --test-dir $RUN_TESTS --results-environment Github-Actions;
+          docker network create tfb > /dev/null 2>&1 && docker run --network=tfb -e USER_ID=$(id -u) -v /var/run/docker.sock:/var/run/docker.sock --mount type=bind,source=`pwd`,target=/FrameworkBenchmarks techempower/tfb --mode verify --test-dir $RUN_TESTS --results-environment Github-Actions;
   dependabot:
     needs: verify
     runs-on: ubuntu-latest

+ 1 - 1
.github/workflows/label-failing-pr.yml

@@ -7,7 +7,7 @@ on:
 jobs:
   apply_label:
     if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure' }}
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
       - name: 'Download artifact'
         uses: actions/[email protected]

+ 5 - 3
.gitignore

@@ -1,6 +1,4 @@
-.DS_Store
 installs
-*.log
 node_modules/
 .travis.bak
 
@@ -59,9 +57,13 @@ benchmark.cfg
 
 # Visual Studio Code
 .vscode
+.history
 
 # vim
 .*.sw[a-p]
 
 # merge tooling
-*.orig
+*.orig
+
+# python
+.venv/

+ 41 - 9
Dockerfile

@@ -1,26 +1,58 @@
-FROM buildpack-deps:bionic
+FROM ubuntu:22.04
 
+ARG USER_ID
+ARG GROUP_ID
 ARG DEBIAN_FRONTEND=noninteractive
+
+#RUN add-apt-repository universe
 # WARNING: DON'T PUT A SPACE AFTER ANY BACKSLASH OR APT WILL BREAK
 # One -q produces output suitable for logging (mostly hides
 # progress indicators)
 RUN apt-get -yqq update && apt-get -yqq install \
       -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" \
       cloc \
+      curl \
       dstat                       `# Collect resource usage statistics` \
+      gcc \
       git-core \
+      gosu \
       libmysqlclient-dev          `# Needed for MySQL-python` \
-      python-dev \
-      python-pip \
+      libpq-dev \
+      python2 \
+      python2.7-dev \
       siege \
       software-properties-common
 
-RUN pip install colorama==0.3.1 docker==4.0.2 MySQL-python psutil psycopg2-binary pymongo requests
+RUN curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
+RUN python2 get-pip.py
+
+RUN curl https://raw.githubusercontent.com/paulfitz/mysql-connector-c/master/include/my_config.h --output /usr/include/mysql/my_config.h
+
+RUN pip install \
+      colorama==0.3.1 \
+      docker==4.0.2 \
+      MySQL-python \
+      psutil \
+      psycopg2-binary \
+      pymongo \
+      requests
+    # Fix for docker-py trying to import one package from the wrong location
+    #cp -r /usr/local/lib/python2.7/dist-packages/backports/ssl_match_hostname \
+    #  /usr/lib/python2.7/dist-packages/backports
+
+ENV FWROOT=/FrameworkBenchmarks PYTHONPATH=/FrameworkBenchmarks
+
+# Check if Group is already created
+RUN if ! getent group $GROUP_ID; then \
+      addgroup --gid $GROUP_ID user; \
+    fi
 
-# Fix for docker-py trying to import one package from the wrong location
-RUN cp -r /usr/local/lib/python2.7/dist-packages/backports/ssl_match_hostname /usr/lib/python2.7/dist-packages/backports
+# Drop permissions of user to match those of the host system
+# Check if the User ID is already created
+RUN if ! getent passwd $USER_ID; then \
+      adduser --disabled-password --gecos '' --uid $USER_ID --gid $GROUP_ID user; \
+    fi
 
-ENV PYTHONPATH=/FrameworkBenchmarks
-ENV FWROOT=/FrameworkBenchmarks
+ENV USER_ID=$USER_ID
 
-ENTRYPOINT ["python", "/FrameworkBenchmarks/toolset/run-tests.py"]
+ENTRYPOINT ["/bin/bash", "FrameworkBenchmarks/entrypoint.sh" ]

+ 6 - 0
entrypoint.sh

@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+set -euox pipefail
+
+chown -R $USER_ID /var/run/
+
+gosu $USER_ID python2 /FrameworkBenchmarks/toolset/run-tests.py "$@"

+ 2 - 1
frameworks/C++/cpoll_cppsp/benchmark_config.json

@@ -38,7 +38,8 @@
       "database_os": "Linux",
       "display_name": "cpoll-cppsp-raw",
       "notes": "",
-      "versus": "cpoll_cppsp"
+      "versus": "cpoll_cppsp",
+      "tags": ["broken"]
     },
     "postgres-raw": {
       "db_url": "/db_pg_async",

+ 1 - 1
frameworks/C++/ffead-cpp/benchmark_config.json

@@ -23,7 +23,7 @@
 			"display_name": "ffead-cpp-mongo",
 			"notes": "mongodb redis",
 			"versus": "",
-			"tags": []
+			"tags": ["broken"]
 		},
 		"v-picov": {
 			"json_url": "/t3/j",

+ 1 - 1
frameworks/C++/treefrog/treefrog-epoll.dockerfile

@@ -2,7 +2,7 @@ FROM buildpack-deps:jammy
 
 ENV DEBIAN_FRONTEND noninteractive
 ENV DEBCONF_NOWARNINGS yes
-ENV TFVER=2.6.0
+ENV TFVER=2.7.1
 
 RUN apt-get update -yqq && apt-get upgrade -yq && \
     apt-get install -yqq --no-install-recommends software-properties-common unzip wget libjemalloc-dev \

+ 1 - 1
frameworks/C++/treefrog/treefrog-mongodb.dockerfile

@@ -2,7 +2,7 @@ FROM buildpack-deps:jammy
 
 ENV DEBIAN_FRONTEND noninteractive
 ENV DEBCONF_NOWARNINGS yes
-ENV TFVER=2.6.0
+ENV TFVER=2.7.1
 
 RUN apt-get update -yqq && apt-get upgrade -yq && \
     apt-get install -yqq --no-install-recommends software-properties-common unzip wget libjemalloc-dev \

+ 1 - 1
frameworks/C++/treefrog/treefrog-mysql.dockerfile

@@ -2,7 +2,7 @@ FROM buildpack-deps:jammy
 
 ENV DEBIAN_FRONTEND noninteractive
 ENV DEBCONF_NOWARNINGS yes
-ENV TFVER=2.6.0
+ENV TFVER=2.7.1
 
 RUN apt-get update -yqq && apt-get upgrade -yq && \
     apt-get install -yqq --no-install-recommends software-properties-common unzip wget libjemalloc-dev \

+ 1 - 1
frameworks/C++/treefrog/treefrog.dockerfile

@@ -2,7 +2,7 @@ FROM buildpack-deps:jammy
 
 ENV DEBIAN_FRONTEND noninteractive
 ENV DEBCONF_NOWARNINGS yes
-ENV TFVER=2.6.0
+ENV TFVER=2.7.1
 
 RUN apt-get update -yqq && apt-get upgrade -yq && \
     apt-get install -yqq --no-install-recommends software-properties-common unzip wget libjemalloc-dev \

+ 2 - 0
frameworks/C++/userver/benchmark_config.json

@@ -9,6 +9,7 @@
         "query_url": "/queries?queries=",
         "update_url": "/updates?queries=",
         "cached_query_url": "/cached-queries?count=",
+	"fortune_url": "/fortunes",
         "port": 8080,
         "approach": "Realistic",
         "classification": "Fullstack",
@@ -32,6 +33,7 @@
         "query_url": "/queries?queries=",
         "update_url": "/updates?queries=",
         "cached_query_url": "/cached-queries?count=",
+	"fortune_url": "/fortunes",
         "port": 8081,
         "approach": "Realistic",
         "classification": "Micro",

+ 2 - 0
frameworks/C++/userver/config.toml

@@ -8,6 +8,7 @@ urls.db = "/db"
 urls.query = "/queries?queries="
 urls.update = "/updates?queries="
 urls.cached_query = "/cached-queries?count="
+urls.fortune = "/fortunes"
 approach = "Realistic"
 classification = "Fullstack"
 database = "Postgres"
@@ -25,6 +26,7 @@ urls.db = "/db"
 urls.query = "/queries?queries="
 urls.update = "/updates?queries="
 urls.cached_query = "/cached-queries?count="
+urls.fortune = "/fortunes"
 approach = "Realistic"
 classification = "Micro"
 database = "Postgres"

+ 4 - 2
frameworks/C++/userver/userver-bare.dockerfile

@@ -1,10 +1,12 @@
 FROM ghcr.io/userver-framework/docker-userver-build-base:v1a AS builder
+RUN apt install -y libnghttp2-dev
+
 WORKDIR /src
 RUN git clone https://github.com/userver-framework/userver.git && \
-    cd userver && git checkout b69a8db23844d3abbb68e40a502eae0ecd2e4b62
+    cd userver && git checkout 040b0bd001a078ee023aabe987e5ec7cc8ed5b6c
 COPY userver_benchmark/ ./
 RUN mkdir build && cd build && \
-    cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -DUSERVER_OPEN_SOURCE_BUILD=1 -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 \
+    cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 \
           -DUSERVER_FEATURE_REDIS=0 -DUSERVER_FEATURE_CLICKHOUSE=0 -DUSERVER_FEATURE_MONGODB=0 -DUSERVER_FEATURE_RABBITMQ=0 -DUSERVER_FEATURE_GRPC=0 \
           -DUSERVER_FEATURE_UTEST=0 \
           -DUSERVER_FEATURE_POSTGRESQL=1 \

+ 4 - 2
frameworks/C++/userver/userver.dockerfile

@@ -1,10 +1,12 @@
 FROM ghcr.io/userver-framework/docker-userver-build-base:v1a AS builder
+RUN apt install -y libnghttp2-dev
+
 WORKDIR /src
 RUN git clone https://github.com/userver-framework/userver.git && \
-    cd userver && git checkout b69a8db23844d3abbb68e40a502eae0ecd2e4b62
+    cd userver && git checkout 040b0bd001a078ee023aabe987e5ec7cc8ed5b6c
 COPY userver_benchmark/ ./
 RUN mkdir build && cd build && \
-    cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -DUSERVER_OPEN_SOURCE_BUILD=1 -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 \
+    cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 \
           -DUSERVER_FEATURE_REDIS=0 -DUSERVER_FEATURE_CLICKHOUSE=0 -DUSERVER_FEATURE_MONGODB=0 -DUSERVER_FEATURE_RABBITMQ=0 -DUSERVER_FEATURE_GRPC=0 \
           -DUSERVER_FEATURE_UTEST=0 \
           -DUSERVER_FEATURE_POSTGRESQL=1 \

+ 3 - 0
frameworks/C++/userver/userver_benchmark/.clang-format

@@ -0,0 +1,3 @@
+BasedOnStyle: google
+DerivePointerAlignment: false
+IncludeBlocks: Preserve

+ 0 - 1
frameworks/C++/userver/userver_benchmark/bare/simple_connection.hpp

@@ -24,4 +24,3 @@ class SimpleConnection final {
 };
 
 }  // namespace userver_techempower::bare
-

+ 0 - 1
frameworks/C++/userver/userver_benchmark/bare/simple_response.hpp

@@ -10,4 +10,3 @@ struct SimpleResponse final {
 };
 
 }  // namespace userver_techempower::bare
-

+ 13 - 4
frameworks/C++/userver/userver_benchmark/bare/simple_router.cpp

@@ -3,6 +3,7 @@
 #include <userver/components/component_context.hpp>
 
 #include "../controllers/cached_queries/handler.hpp"
+#include "../controllers/fortunes/handler.hpp"
 #include "../controllers/json/handler.hpp"
 #include "../controllers/multiple_queries/handler.hpp"
 #include "../controllers/plaintext/handler.hpp"
@@ -14,16 +15,19 @@ namespace userver_techempower::bare {
 namespace {
 
 constexpr std::string_view kPlainTextUrlPrefix{"/plaintext"};
-constexpr std::string_view kJsontUrlPrefix{"/json"};
+constexpr std::string_view kJsonUrlPrefix{"/json"};
 constexpr std::string_view kSingleQueryUrlPrefix{"/db"};
 constexpr std::string_view kMultipleQueriesUrlPrefix{"/queries"};
 constexpr std::string_view kUpdatesUrlPrefix{"/updates"};
 constexpr std::string_view kCachedQueriesUrlPrefix{"/cached-queries"};
+constexpr std::string_view kFortunesUrlPrefix{"/fortunes"};
 
 // NOLINTNEXTLINE
 const std::string kContentTypePlain{"text/plain"};
 // NOLINTNEXTLINE
 const std::string kContentTypeJson{"application/json"};
+// NOLINTNEXTLINE
+const std::string kContentTypeTextHtml{"text/html; charset=utf-8"};
 
 bool StartsWith(std::string_view source, std::string_view pattern) {
   return source.substr(0, pattern.length()) == pattern;
@@ -37,7 +41,8 @@ SimpleRouter::SimpleRouter(const userver::components::ComponentConfig& config,
       single_query_{context.FindComponent<single_query::Handler>()},
       multiple_queries_{context.FindComponent<multiple_queries::Handler>()},
       updates_{context.FindComponent<updates::Handler>()},
-      cached_queries_{context.FindComponent<cached_queries::Handler>()} {}
+      cached_queries_{context.FindComponent<cached_queries::Handler>()},
+      fortunes_{context.FindComponent<fortunes::Handler>()} {}
 
 SimpleRouter::~SimpleRouter() = default;
 
@@ -46,7 +51,7 @@ SimpleResponse SimpleRouter::RouteRequest(std::string_view url) const {
     return {plaintext::Handler::GetResponse(), kContentTypePlain};
   }
 
-  if (StartsWith(url, kJsontUrlPrefix)) {
+  if (StartsWith(url, kJsonUrlPrefix)) {
     return {ToString(json::Handler::GetResponse()), kContentTypeJson};
   }
 
@@ -72,7 +77,11 @@ SimpleResponse SimpleRouter::RouteRequest(std::string_view url) const {
     const auto count = db_helpers::ParseParamFromQuery(
         url.substr(kCachedQueriesUrlPrefix.size()), "count");
 
-    return {ToString(cached_queries_.GetResponse(count)), "application/json"};
+    return {ToString(cached_queries_.GetResponse(count)), kContentTypeJson};
+  }
+
+  if (StartsWith(url, kFortunesUrlPrefix)) {
+    return {fortunes_.GetResponse(), kContentTypeTextHtml};
   }
 
   throw std::runtime_error{"No handler found for url"};

+ 4 - 1
frameworks/C++/userver/userver_benchmark/bare/simple_router.hpp

@@ -18,6 +18,9 @@ class Handler;
 namespace cached_queries {
 class Handler;
 }
+namespace fortunes {
+class Handler;
+}
 
 namespace bare {
 
@@ -36,8 +39,8 @@ class SimpleRouter final : public userver::components::LoggableComponentBase {
   const multiple_queries::Handler& multiple_queries_;
   const updates::Handler& updates_;
   const cached_queries::Handler& cached_queries_;
+  const fortunes::Handler& fortunes_;
 };
 
 }  // namespace bare
 }  // namespace userver_techempower
-

+ 0 - 1
frameworks/C++/userver/userver_benchmark/bare/simple_server.cpp

@@ -26,4 +26,3 @@ SimpleResponse SimpleServer::HandleRequest(std::string_view url) const {
 }
 
 }  // namespace userver_techempower::bare
-

+ 5 - 4
frameworks/C++/userver/userver_benchmark/common/db_helpers.cpp

@@ -1,7 +1,7 @@
 #include "db_helpers.hpp"
 
-#include <charconv>
 #include <cctype>
+#include <charconv>
 
 #include <userver/formats/json/inline.hpp>
 #include <userver/utils/rand.hpp>
@@ -16,7 +16,8 @@ int ParseFromQueryVal(std::string_view query_val) {
   }
 
   int parse_result{};
-  const auto [ptr, err] = std::from_chars(query_val.data(), query_val.data() + query_val.size(), parse_result);
+  const auto [ptr, err] = std::from_chars(
+      query_val.data(), query_val.data() + query_val.size(), parse_result);
   if (err != std::errc{} || ptr != query_val.data() + query_val.size()) {
     return 1;
   }
@@ -24,7 +25,7 @@ int ParseFromQueryVal(std::string_view query_val) {
   return std::min(500, std::max(1, parse_result));
 }
 
-}
+}  // namespace
 
 int GenerateRandomId() {
   return userver::utils::RandRange(1, kMaxWorldRows + 1);
@@ -52,7 +53,7 @@ int ParseParamFromQuery(std::string_view url, std::string_view name) {
   if (pos == std::string_view::npos) {
     return 1;
   }
-  pos += name.size() + 1; // +1 for '='
+  pos += name.size() + 1;  // +1 for '='
 
   std::size_t len = 0;
   while (pos + len < url.size() && std::isdigit(url[pos + len])) {

+ 161 - 0
frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.cpp

@@ -0,0 +1,161 @@
+#include "handler.hpp"
+
+#include <vector>
+
+#include "../../common/db_helpers.hpp"
+
+#include <userver/components/component_context.hpp>
+#include <userver/storages/postgres/postgres.hpp>
+
+namespace userver_techempower::fortunes {
+
+namespace {
+
+const std::string kContentTypeHeader{"Content-Type"};
+const std::string kContentTypeTextHtml{"text/html; charset=utf-8"};
+
+struct Fortune final {
+  int id;
+  std::string message;
+};
+
+constexpr std::string_view kResultingHtmlHeader{
+    "<!DOCTYPE "
+    "html><html><head><title>Fortunes</title></head><body><table><tr><th>id</"
+    "th><th>message</th></tr>"};
+constexpr std::string_view kResultingHtmlFooter{"</table></body></html>"};
+
+constexpr std::string_view kNewRowStart{"<tr><td>"};
+constexpr std::string_view kColumnsSeparator{"</td><td>"};
+constexpr std::string_view kNewRowEnd{"</td></tr>"};
+
+constexpr std::string_view kEscapedQuote{"&quot;"};
+constexpr std::string_view kEscapedAmpersand{"&amp;"};
+constexpr std::string_view kEscapedLessThanSign{"&lt;"};
+constexpr std::string_view kEscapedMoreThanSign{"&gt;"};
+
+void AppendFortune(std::string& result, const Fortune& fortune) {
+  {
+    auto old_size = result.size();
+    const auto fortune_id = std::to_string(fortune.id);
+
+    const auto first_step_size =
+        kNewRowStart.size() + fortune_id.size() + kColumnsSeparator.size();
+
+    result.resize(old_size + first_step_size);
+    char* append_position = result.data() + old_size;
+
+    // this is just faster than std::string::append if we know the resulting
+    // size upfront, because there are a lot of not inlined calls otherwise
+    const auto append = [&append_position](std::string_view what) {
+      std::memcpy(append_position, what.data(), what.size());
+      append_position += what.size();
+    };
+    append(kNewRowStart);
+    append(fortune_id);
+    append(kColumnsSeparator);
+  }
+
+  {
+    std::string_view message{fortune.message};
+
+    const auto do_append = [&result](std::string_view unescaped,
+                                     std::string_view escaped) {
+      const auto old_size = result.size();
+      const auto added_size = unescaped.size() + escaped.size();
+
+      result.resize(result.size() + added_size);
+      char* append_position = result.data() + old_size;
+      if (!unescaped.empty()) {
+        std::memcpy(append_position, unescaped.data(), unescaped.size());
+        append_position += unescaped.size();
+      }
+      std::memcpy(append_position, escaped.data(), escaped.size());
+    };
+
+    std::size_t unescaped_len = 0;
+    const auto append = [&unescaped_len, &message,
+                         &do_append](std::string_view escaped) {
+      do_append(message.substr(0, unescaped_len), escaped);
+      message = message.substr(std::exchange(unescaped_len, 0) + 1);
+    };
+
+    while (unescaped_len < message.size()) {
+      const auto c = message[unescaped_len];
+      switch (c) {
+        case '"': {
+          append(kEscapedQuote);
+          break;
+        }
+        case '&': {
+          append(kEscapedAmpersand);
+          break;
+        }
+        case '<': {
+          append(kEscapedLessThanSign);
+          break;
+        }
+        case '>': {
+          append(kEscapedMoreThanSign);
+          break;
+        }
+        default:
+          ++unescaped_len;
+      }
+    }
+    result.append(message);
+  }
+
+  { result.append(kNewRowEnd); }
+}
+
+std::string FormatFortunes(const std::vector<Fortune>& fortunes) {
+  std::string result{};
+  // Wild guess, seems reasonable. Could be the exact value needed, but that
+  // looks kinda cheating.
+  result.reserve(2048);
+
+  result.append(kResultingHtmlHeader);
+  for (const auto& fortune : fortunes) {
+    AppendFortune(result, fortune);
+  }
+  result.append(kResultingHtmlFooter);
+
+  return result;
+}
+
+}  // namespace
+
+Handler::Handler(const userver::components::ComponentConfig& config,
+                 const userver::components::ComponentContext& context)
+    : userver::server::handlers::HttpHandlerBase{config, context},
+      pg_{context
+              .FindComponent<userver::components::Postgres>(
+                  db_helpers::kDbComponentName)
+              .GetCluster()},
+      select_all_fortunes_query_{"SELECT id, message FROM Fortune"} {}
+
+std::string Handler::HandleRequestThrow(
+    const userver::server::http::HttpRequest& request,
+    userver::server::request::RequestContext&) const {
+  request.GetHttpResponse().SetHeader(kContentTypeHeader, kContentTypeTextHtml);
+  return GetResponse();
+}
+
+std::string Handler::GetResponse() const {
+  auto fortunes =
+      pg_->Execute(db_helpers::kClusterHostType, select_all_fortunes_query_)
+          .AsContainer<std::vector<Fortune>>(
+              userver::storages::postgres::kRowTag);
+
+  fortunes.push_back({0, "Additional fortune added at request time."});
+
+  std::sort(fortunes.begin(), fortunes.end(),
+            [](const auto& lhs, const auto& rhs) {
+              return lhs.message < rhs.message;
+            });
+
+  return FormatFortunes(fortunes);
+}
+
+}  // namespace userver_techempower::fortunes

+ 28 - 0
frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.hpp

@@ -0,0 +1,28 @@
+#pragma once
+
+#include <userver/server/handlers/http_handler_base.hpp>
+
+#include <userver/storages/postgres/postgres_fwd.hpp>
+#include <userver/storages/postgres/query.hpp>
+
+namespace userver_techempower::fortunes {
+
+class Handler final : public userver::server::handlers::HttpHandlerBase {
+ public:
+  static constexpr std::string_view kName = "fortunes-handler";
+
+  Handler(const userver::components::ComponentConfig& config,
+          const userver::components::ComponentContext& context);
+
+  std::string HandleRequestThrow(
+      const userver::server::http::HttpRequest& request,
+      userver::server::request::RequestContext&) const final;
+
+  std::string GetResponse() const;
+
+ private:
+  const userver::storages::postgres::ClusterPtr pg_;
+  const userver::storages::postgres::Query select_all_fortunes_query_;
+};
+
+}  // namespace userver_techempower::fortunes

+ 21 - 10
frameworks/C++/userver/userver_benchmark/controllers/multiple_queries/handler.cpp

@@ -30,18 +30,29 @@ userver::formats::json::Value Handler::HandleRequestJsonThrow(
 }
 
 userver::formats::json::Value Handler::GetResponse(int queries) const {
-  boost::container::small_vector<int, 500> random_ids(queries);
-  std::generate(random_ids.begin(), random_ids.end(),
-                db_helpers::GenerateRandomId);
-
-  boost::container::small_vector<db_helpers::WorldTableRow, 500> result{};
-  for (auto id : random_ids) {
-    result.push_back(pg_->Execute(db_helpers::kClusterHostType,
-                                  db_helpers::kSelectRowQuery, id)
-                         .AsSingleRow<db_helpers::WorldTableRow>(
-                             userver::storages::postgres::kRowTag));
+  boost::container::small_vector<db_helpers::WorldTableRow, 500> result(
+      queries);
+  for (auto& value : result) {
+    value.id = db_helpers::GenerateRandomId();
   }
 
+  // even though this adds a round-trip for Begin/Commit we expect this to be
+  // faster due to the pool semaphore contention reduction - now we have a
+  // connection for ourselves until we are done with it, otherwise we would
+  // likely wait on the semaphore with every new query.
+  auto transaction = pg_->Begin(
+      db_helpers::kClusterHostType,
+      userver::storages::postgres::TransactionOptions{
+          userver::storages::postgres::TransactionOptions::Mode::kReadOnly});
+  for (auto& value : result) {
+    value.random_number =
+        transaction.Execute(db_helpers::kSelectRowQuery, value.id)
+            .AsSingleRow<db_helpers::WorldTableRow>(
+                userver::storages::postgres::kRowTag)
+            .random_number;
+  }
+  transaction.Commit();
+
   return userver::formats::json::ValueBuilder{result}.ExtractValue();
 }
 

+ 6 - 4
frameworks/C++/userver/userver_benchmark/controllers/plaintext/handler.cpp

@@ -2,15 +2,17 @@
 
 namespace userver_techempower::plaintext {
 
+const std::string kContentTypeHeader{"Content-Type"};
+const std::string kContentTypeTextPlain{"text/plain"};
+
 std::string Handler::HandleRequestThrow(
     const userver::server::http::HttpRequest& request,
     userver::server::request::RequestContext&) const {
-  request.GetHttpResponse().SetContentType("text/plain");
+  request.GetHttpResponse().SetHeader(kContentTypeHeader,
+                                      kContentTypeTextPlain);
   return GetResponse();
 }
 
-std::string Handler::GetResponse() {
-  return "Hello, World!";
-}
+std::string Handler::GetResponse() { return "Hello, World!"; }
 
 }  // namespace userver_techempower::plaintext

+ 28 - 17
frameworks/C++/userver/userver_benchmark/controllers/updates/handler.cpp

@@ -43,27 +43,38 @@ userver::formats::json::Value Handler::HandleRequestJsonThrow(
 }
 
 userver::formats::json::Value Handler::GetResponse(int queries) const {
-  std::vector<int> random_ids(queries);
-  std::generate(random_ids.begin(), random_ids.end(),
-                db_helpers::GenerateRandomId);
-  std::sort(random_ids.begin(), random_ids.end());
-
-  boost::container::small_vector<db_helpers::WorldTableRow, 500> result{};
-  for (auto id : random_ids) {
-    result.push_back(pg_->Execute(db_helpers::kClusterHostType,
-                                  db_helpers::kSelectRowQuery, id)
-                         .AsSingleRow<db_helpers::WorldTableRow>(
-                             userver::storages::postgres::kRowTag));
+  // userver's PG doesn't accept boost::small_vector as an input, sadly
+  std::vector<db_helpers::WorldTableRow> values(queries);
+  for (auto& value : values) {
+    value.id = db_helpers::GenerateRandomId();
+  }
+  // we have to sort ids to not deadlock in update
+  std::sort(values.begin(), values.end(),
+            [](const auto& lhs, const auto& rhs) { return lhs.id < rhs.id; });
+
+  // even though this adds a round-trip for Begin/Commit we expect this to be
+  // faster due to the pool semaphore contention reduction - now we have a
+  // connection for ourselves until we are done with it, otherwise we would
+  // likely wait on the semaphore with every new query.
+  auto transaction = pg_->Begin(db_helpers::kClusterHostType, {});
+  for (auto& value : values) {
+    value.random_number =
+        transaction.Execute(db_helpers::kSelectRowQuery, value.id)
+            .AsSingleRow<db_helpers::WorldTableRow>(
+                userver::storages::postgres::kRowTag)
+            .random_number;
   }
 
-  std::vector<int> random_numbers(queries);
-  std::generate(random_numbers.begin(), random_numbers.end(),
-                db_helpers::GenerateRandomValue);
+  auto json_result =
+      userver::formats::json::ValueBuilder{values}.ExtractValue();
 
-  pg_->Execute(db_helpers::kClusterHostType, update_query_, random_ids,
-               random_numbers);
+  for (auto& value : values) {
+    value.random_number = db_helpers::GenerateRandomValue();
+  }
+  transaction.ExecuteDecomposeBulk(update_query_, values, values.size());
+  transaction.Commit();
 
-  return userver::formats::json::ValueBuilder{result}.ExtractValue();
+  return json_result;
 }
 
 }  // namespace userver_techempower::updates

+ 36 - 1
frameworks/C++/userver/userver_benchmark/userver_techempower.cpp

@@ -2,10 +2,15 @@
 #include <userver/testsuite/testsuite_support.hpp>
 #include <userver/utils/daemon_run.hpp>
 
+#include <userver/clients/dns/component.hpp>
+
 #include <userver/storages/postgres/component.hpp>
 #include <userver/storages/secdist/component.hpp>
+#include <userver/storages/secdist/provider_component.hpp>
+#include <userver/tracing/manager_component.hpp>
 
 #include "controllers/cached_queries/handler.hpp"
+#include "controllers/fortunes/handler.hpp"
 #include "controllers/json/handler.hpp"
 #include "controllers/multiple_queries/handler.hpp"
 #include "controllers/plaintext/handler.hpp"
@@ -17,19 +22,49 @@
 
 namespace userver_techempower {
 
+class NoopTracingManager final
+    : public userver::tracing::TracingManagerComponentBase {
+ public:
+  static constexpr std::string_view kName{"noop-tracing-manager"};
+  using userver::tracing::TracingManagerComponentBase::
+      TracingManagerComponentBase;
+
+ protected:
+  bool TryFillSpanBuilderFromRequest(
+      const userver::server::http::HttpRequest&,
+      userver::tracing::SpanBuilder&) const final {
+    return true;
+  }
+
+  void FillRequestWithTracingContext(
+      const userver::tracing::Span&,
+      userver::clients::http::RequestTracingEditor) const final {}
+
+  void FillResponseWithTracingContext(
+      const userver::tracing::Span&,
+      userver::server::http::HttpResponse&) const final {}
+};
+
 int Main(int argc, char* argv[]) {
   auto component_list =
       userver::components::MinimalServerComponentList()
+          // some required infra
+          .Append<userver::clients::dns::Component>()
           .Append<userver::components::Secdist>()
+          .Append<userver::components::DefaultSecdistProvider>()
           .Append<userver::components::TestsuiteSupport>()
           .Append<userver::components::Postgres>("hello-world-db")
+          // actual handlers
           .Append<plaintext::Handler>()
           .Append<json::Handler>()
           .Append<single_query::Handler>()
           .Append<multiple_queries::Handler>()
           .Append<updates::Handler>()
-          .Append<cached_queries::WorldCacheComponent>()
+          .Append<cached_queries::WorldCacheComponent>()  // cache component
           .Append<cached_queries::Handler>()
+          .Append<fortunes::Handler>()
+          // tracing tweaks
+          .Append<NoopTracingManager>()
           // bare
           .Append<bare::SimpleRouter>()
           .Append<bare::SimpleServer>();

+ 7 - 7
frameworks/C++/userver/userver_configs/dynamic_config_fallback.json

@@ -9,6 +9,7 @@
   "USERVER_LRU_CACHES": {},
   "USERVER_RPS_CCONTROL_CUSTOM_STATUS": {},
   "USERVER_TASK_PROCESSOR_PROFILER_DEBUG": {},
+  "USERVER_HANDLER_STREAM_API_ENABLED": false,
   "HTTP_CLIENT_CONNECTION_POOL_SIZE": 1000,
   "HTTP_CLIENT_CONNECT_THROTTLE": {
     "http-limit": 6000,
@@ -22,6 +23,10 @@
     "cancel-request": false,
     "update-timeout": false
   },
+  "BAGGAGE_SETTINGS": {
+    "allowed_keys": []
+  },
+  "USERVER_BAGGAGE_ENABLED": false,
   "USERVER_TASK_PROCESSOR_QOS": {
     "default-service": {
       "default-task-processor": {
@@ -35,14 +40,9 @@
   },
 
   "POSTGRES_STATEMENT_METRICS_SETTINGS": {},
+  "POSTGRES_CONNLIMIT_MODE_AUTO_ENABLED": false,
   "POSTGRES_CONNECTION_PIPELINE_ENABLED": false,
-  "POSTGRES_CONNECTION_POOL_SETTINGS": {
-    "hello_world": {
-      "min_pool_size": 28,
-      "max_pool_size": 28,
-      "max_queue_size": 512
-    }
-  },
+  "POSTGRES_CONNECTION_POOL_SETTINGS": {},
   "POSTGRES_CONNECTION_SETTINGS": {},
   "POSTGRES_DEFAULT_COMMAND_CONTROL": {
     "network_timeout_ms": 7000,

+ 18 - 4
frameworks/C++/userver/userver_configs/static_config.yaml

@@ -43,6 +43,9 @@ components_manager:
         tracer:                                 # Component that helps to trace execution times and requests in logs.
             service-name: userver-techempower   # "You know. You all know exactly who I am. Say my name. " (c)
 
+        dns-client:
+            fs-task-processor: fs-task-processor
+
         dynamic-config:                      # Dynamic config storage options, do nothing
             fs-cache-path: ''
         dynamic-config-fallbacks:            # Load options from file and push them into the dynamic config storage.
@@ -50,9 +53,14 @@ components_manager:
 
         testsuite-support:
 
-        secdist: # Component that stores configuration of hosts and passwords
+        secdist: {} # Component that stores configuration of hosts and passwords
+        default-secdist-provider:
             config: /app/secure_data.json  # Values are supposed to be stored in this file
-            missing-ok: false                             # ... but if the file is missing it is still ok
+            missing-ok: false
+
+        noop-tracing-manager:
+        tracing-manager-locator:
+            component-name: noop-tracing-manager
 
         plaintext-handler:
             path: /plaintext
@@ -67,9 +75,10 @@ components_manager:
         hello-world-db:
             dbalias: hello_world
             blocking_task_processor: fs-task-processor
-            min_pool_size: 28
-            max_pool_size: 28
+            min_pool_size: 75
+            max_pool_size: 125
             max_queue_size: 512
+            connecting_limit: 15
 
         single-query-handler:
             path: /db
@@ -97,3 +106,8 @@ components_manager:
             method: GET
             task_processor: main-task-processor
 
+        fortunes-handler:
+            path: /fortunes
+            method: GET
+            task_processor: main-task-processor
+

+ 2 - 2
frameworks/C/h2o/CMakeLists.txt

@@ -27,8 +27,8 @@ set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -O3")
 file(GLOB_RECURSE SOURCES "src/*.c")
 add_executable(${PROJECT_NAME} ${SOURCES})
 target_link_libraries(${PROJECT_NAME} ${COMMON_OPTIONS})
-target_link_libraries(${PROJECT_NAME} ${H2O_LIB} ${MUSTACHE_C_LIB} ${NUMA_LIB} ${PQ_LIB} ${SSL_LIB})
-target_link_libraries(${PROJECT_NAME} ${CRYPTO_LIB} ${YAJL_LIB} ${Z_LIB})
+target_link_libraries(${PROJECT_NAME} ${H2O_LIB} m ${MUSTACHE_C_LIB} ${NUMA_LIB} ${PQ_LIB})
+target_link_libraries(${PROJECT_NAME} ${SSL_LIB} ${CRYPTO_LIB} ${YAJL_LIB} ${Z_LIB})
 install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
 file(GLOB TEMPLATES "template/*")
 install(FILES ${TEMPLATES} DESTINATION share/${PROJECT_NAME}/template)

+ 4 - 4
frameworks/C/h2o/README.md

@@ -11,16 +11,16 @@ builds directly on top of `libh2o` instead of running the standalone server.
 
 ## Test implementations
 
-The test implementations are located into the `src/handlers` directory - refer to the
-`initialize_*_handler*()` functions.
+The test implementations are located into the [src/handlers](src/handlers) directory - refer to
+the `initialize_*_handler*()` functions.
 
 ## Performance tuning
 
 If the test environment changes, it will probably be necessary to tune some of the framework
 settings in order to achieve the best performance possible. The most significant parameters are the
 maximum number of database connections per thread and the maximum number of pipelined database
-queries per database connection, which are controlled by the `DB_CONN` and the `DB_PIPELINE`
-variables respectively in the `h2o.sh` script.
+queries per database connection, which are controlled by the `-m` and the `-e` command-line
+options respectively.
 
 ## Performance issues
 

+ 83 - 37
frameworks/C/h2o/h2o.dockerfile

@@ -1,50 +1,96 @@
-FROM ubuntu:22.04
+ARG UBUNTU_VERSION=22.04
 
-WORKDIR /h2o_app_src
-COPY ./ ./
+ARG H2O_APP_PREFIX=/opt/h2o_app
+
+FROM "ubuntu:${UBUNTU_VERSION}" AS compile
 
 ARG DEBIAN_FRONTEND=noninteractive
 RUN apt-get -yqq update && \
-    apt-get -yqq install autoconf bison cmake curl file flex g++ git libnuma-dev libpq-dev \
-                         libssl-dev libtool libyajl-dev libz-dev make ninja-build wget
-
-### Install mustache-c
-
-ARG MUSTACHE_C_REVISION=c1948c599edfe48c6099ed70ab1d5911d8c3ddc8
-
-ARG MUSTACHE_C_BUILD_DIR=mustache-c-build
-ENV MUSTACHE_C_PREFIX=/opt/mustache-c
+    apt-get -yqq install \
+      autoconf \
+      bison \
+      cmake \
+      curl \
+      flex \
+      g++ \
+      libbrotli-dev \
+      libcap-dev \
+      libnuma-dev \
+      libpq-dev \
+      libssl-dev \
+      libtool \
+      libuv1-dev \
+      libwslay-dev \
+      libyajl-dev \
+      libz-dev \
+      make \
+      ninja-build \
+      pkg-config \
+      systemtap-sdt-dev
 
-RUN mkdir -p "$MUSTACHE_C_BUILD_DIR" && \
-    cd "$MUSTACHE_C_BUILD_DIR" && \
-    wget -qO - "https://github.com/x86-64/mustache-c/archive/${MUSTACHE_C_REVISION}.tar.gz" | \
-    tar xz --strip-components=1 && \
-    CFLAGS="-O3 -flto -march=native -mtune=native" ./autogen.sh --prefix="$MUSTACHE_C_PREFIX" && \
-    make -j "$(nproc)" install && \
-    cd .. && \
-    rm -rf "$MUSTACHE_C_BUILD_DIR"
+ARG H2O_VERSION=13ba727ad12dfb2338165d2bcfb2136457e33c8a
 
-### Install h2o
+WORKDIR /tmp/h2o-build
+RUN curl -LSs "https://github.com/h2o/h2o/archive/${H2O_VERSION}.tar.gz" | \
+      tar --strip-components=1 -xz && \
+    cmake \
+      -B build \
+      -DCMAKE_AR=/usr/bin/gcc-ar \
+      -DCMAKE_C_FLAGS="-flto -march=native -mtune=native" \
+      -DCMAKE_RANLIB=/usr/bin/gcc-ranlib \
+      -G Ninja \
+      -S . && \
+    cmake --build build -j && \
+    cmake --install build && \
+    cp -a deps/picotls/include/picotls* deps/quicly/include/quicly* /usr/local/include
 
-ARG H2O_VERSION=v2.2.6
+ARG MUSTACHE_C_REVISION=c1948c599edfe48c6099ed70ab1d5911d8c3ddc8
 
-ARG H2O_BUILD_DIR=h2o-build
-ENV H2O_PREFIX=/opt/h2o
+WORKDIR /tmp/mustache-c-build
+RUN curl -LSs "https://github.com/x86-64/mustache-c/archive/${MUSTACHE_C_REVISION}.tar.gz" | \
+      tar --strip-components=1 -xz && \
+    CFLAGS="-flto -march=native -mtune=native -O3" ./autogen.sh && \
+    make -j "$(nproc)" install
 
-RUN mkdir -p "${H2O_BUILD_DIR}/build" && \
-    cd "$H2O_BUILD_DIR" && \
-    wget -qO - "https://github.com/h2o/h2o/archive/${H2O_VERSION}.tar.gz" | \
-    tar xz --strip-components=1 && \
-    cd build && \
-    cmake -DCMAKE_INSTALL_PREFIX="$H2O_PREFIX" -DCMAKE_C_FLAGS="-flto -march=native -mtune=native" \
-          -DCMAKE_AR=/usr/bin/gcc-ar -DCMAKE_RANLIB=/usr/bin/gcc-ranlib -G Ninja .. && \
+ARG H2O_APP_PREFIX
+WORKDIR /tmp/build
+COPY CMakeLists.txt ../
+COPY src ../src/
+COPY template ../template/
+RUN cmake \
+      -DCMAKE_BUILD_TYPE=Release \
+      -DCMAKE_C_FLAGS="-march=native -mtune=native" \
+      -DCMAKE_INSTALL_PREFIX="${H2O_APP_PREFIX}" \
+      -G Ninja \
+      -S .. && \
     cmake --build . -j && \
-    cmake --install . && \
-    cd ../.. && \
-    rm -rf "$H2O_BUILD_DIR"
+    cmake --install .
 
-ARG BENCHMARK_ENV
-ENV BENCHMARK_ENV=$BENCHMARK_ENV
+FROM "ubuntu:${UBUNTU_VERSION}"
+
+ARG DEBIAN_FRONTEND=noninteractive
+RUN apt-get -yqq update && \
+    apt-get -yqq install \
+      libnuma1 \
+      libpq5 \
+      libyajl2
+ARG H2O_APP_PREFIX
+COPY --from=compile "${H2O_APP_PREFIX}" "${H2O_APP_PREFIX}/"
+COPY --from=compile /usr/local/lib/libmustache_c.so "${H2O_APP_PREFIX}/lib/"
+ENV LD_LIBRARY_PATH="${H2O_APP_PREFIX}/lib"
 EXPOSE 8080
+ARG BENCHMARK_ENV
+ARG TFB_TEST_DATABASE
+ARG TFB_TEST_NAME
 
-CMD ["./h2o.sh"]
+CMD ["taskset", \
+     "-c", \
+     "0", \
+     "/opt/h2o_app/bin/h2o_app", \
+     "-a20", \
+     "-d", \
+     "dbname=hello_world host=tfb-database password=benchmarkdbpass sslmode=disable user=benchmarkdbuser", \
+     "-e64", \
+     "-f", \
+     "/opt/h2o_app/share/h2o_app/template", \
+     "-m1"]

+ 0 - 88
frameworks/C/h2o/h2o.sh

@@ -1,88 +0,0 @@
-#!/bin/bash
-
-set -e
-
-H2O_APP_PROFILE_PORT=54321
-H2O_APP_PROFILE_URL="http://127.0.0.1:$H2O_APP_PROFILE_PORT"
-SCRIPT_PATH=$(realpath "$0")
-H2O_APP_SRC_ROOT=$(dirname "$SCRIPT_PATH")
-H2O_APP_BUILD_DIR="${H2O_APP_SRC_ROOT}/build"
-
-if [[ -z "$DBHOST" ]]; then
-	DBHOST=tfb-database
-fi
-
-if [[ -z "$H2O_APP_PREFIX" ]]; then
-	H2O_APP_PREFIX=/opt/h2o_app
-fi
-
-if [[ -z "$H2O_PREFIX" ]]; then
-	H2O_PREFIX=/usr
-fi
-
-if [[ -z "$MUSTACHE_C_PREFIX" ]]; then
-	MUSTACHE_C_PREFIX=/opt/mustache-c
-fi
-
-if [[ "$BENCHMARK_ENV" = "Azure" ]]; then
-	DB_CONN=1
-	DB_PIPELINE=64
-else
-	DB_CONN=1
-	DB_PIPELINE=64
-fi
-
-build_h2o_app()
-{
-	cmake -DCMAKE_INSTALL_PREFIX="$H2O_APP_PREFIX" -DCMAKE_BUILD_TYPE=Release \
-	      -DCMAKE_PREFIX_PATH="${H2O_PREFIX};${MUSTACHE_C_PREFIX}" \
-	      -DCMAKE_C_FLAGS="-march=native -mtune=native $1" -G Ninja \
-	      "$H2O_APP_SRC_ROOT"
-	cmake --build . --clean-first -j
-}
-
-run_curl()
-{
-	for ((i = 0; i < 10; i++)); do
-		curl "${H2O_APP_PROFILE_URL}/$1" > /dev/null 2>&1
-	done
-}
-
-run_h2o_app()
-{
-	LD_LIBRARY_PATH="${MUSTACHE_C_PREFIX}/lib:$LD_LIBRARY_PATH" \
-	taskset -c "$1" "$2/h2o_app" -a20 -e "$DB_PIPELINE" -f "$3/template" -m "$DB_CONN" "$4" "$5" \
-	        -d "host=$DBHOST dbname=hello_world user=benchmarkdbuser sslmode=disable \
-	            password=benchmarkdbpass" &
-}
-
-generate_profile_data()
-{
-	run_h2o_app 0 . "$H2O_APP_SRC_ROOT" "-p$H2O_APP_PROFILE_PORT" -t1
-	local -r H2O_APP_PROFILE_PID=$!
-	while ! curl "$H2O_APP_PROFILE_URL" > /dev/null 2>&1; do sleep 1; done
-	run_curl json
-	run_curl db
-	run_curl queries?queries=20
-	run_curl fortunes
-	run_curl updates?queries=20
-	run_curl plaintext
-	run_curl cached-worlds?queries=20
-	kill -s SIGTERM "$H2O_APP_PROFILE_PID"
-	wait "$H2O_APP_PROFILE_PID"
-}
-
-install -d "$H2O_APP_BUILD_DIR"
-pushd "$H2O_APP_BUILD_DIR"
-build_h2o_app "-fprofile-generate"
-generate_profile_data
-rm -f CMakeCache.txt
-build_h2o_app "-fprofile-use"
-cmake --install .
-popd
-rm -rf "$H2O_APP_BUILD_DIR"
-echo "Running h2o_app in the $BENCHMARK_ENV environment."
-echo "Maximum database connections per thread: $DB_CONN"
-echo "Maximum pipelined database queries per database connection: $DB_PIPELINE"
-run_h2o_app 0 "${H2O_APP_PREFIX}/bin" "${H2O_APP_PREFIX}/share/h2o_app"
-wait

+ 47 - 35
frameworks/C/h2o/src/database.c

@@ -33,11 +33,14 @@
 #include "global_data.h"
 #include "list.h"
 
-#define EXPECT_SYNC 1
-#define IGNORE_RESULT 2
-#define IS_RESETTING 4
 #define MS_IN_S 1000
 
+// Database connection state
+#define EXPECT_SYNC 1
+#define IDLE 2
+#define IGNORE_RESULT 4
+#define RESET 8
+
 typedef struct {
 	list_t l;
 	PGconn *conn;
@@ -48,7 +51,7 @@ typedef struct {
 	size_t query_num;
 	uint_fast32_t flags;
 	int sd;
-	h2o_timeout_entry_t timeout;
+	h2o_timer_t timer;
 } db_conn_t;
 
 typedef struct {
@@ -62,11 +65,11 @@ static int do_execute_query(db_conn_t *conn, db_query_param_t *param);
 static void error_notification(db_conn_pool_t *pool, bool timeout, const char *error_string);
 static void on_database_connect_error(db_conn_t *conn, bool timeout, const char *error_string);
 static void on_database_connect_read_ready(h2o_socket_t *sock, const char *err);
-static void on_database_connect_timeout(h2o_timeout_entry_t *entry);
+static void on_database_connect_timeout(h2o_timer_t *timer);
 static void on_database_connect_write_ready(h2o_socket_t *sock, const char *err);
 static void on_database_error(db_conn_t *conn, const char *error_string);
 static void on_database_read_ready(h2o_socket_t *sock, const char *err);
-static void on_database_timeout(h2o_timeout_entry_t *timeout);
+static void on_database_timeout(h2o_timer_t *timer);
 static void on_database_write_ready(h2o_socket_t *sock, const char *err);
 static void poll_database_connection(h2o_socket_t *sock, const char *err);
 static void prepare_statements(db_conn_t *conn);
@@ -144,16 +147,16 @@ static int do_execute_query(db_conn_t *conn, db_query_param_t *param)
 		h2o_socket_notify_write(conn->sock, on_database_write_ready);
 
 	if (!conn->queries.head && !(conn->flags & (EXPECT_SYNC | IGNORE_RESULT))) {
-		assert(!h2o_timeout_is_linked(&conn->timeout));
-		conn->timeout.cb = on_database_timeout;
-		h2o_timeout_link(conn->pool->loop, &conn->pool->timeout, &conn->timeout);
-		h2o_socket_read_start(conn->sock, on_database_read_ready);
+		assert(!h2o_timer_is_linked(&conn->timer));
+		conn->timer.cb = on_database_timeout;
+		h2o_timer_link(conn->pool->loop, conn->pool->config->db_timeout * MS_IN_S, &conn->timer);
 	}
 
 	param->l.next = NULL;
 	*conn->queries.tail = &param->l;
 	conn->queries.tail = &param->l.next;
 	conn->query_num--;
+	conn->flags &= ~IDLE;
 	return 0;
 }
 
@@ -188,7 +191,7 @@ static void on_database_connect_error(db_conn_t *conn, bool timeout, const char
 {
 	db_conn_pool_t * const pool = conn->pool;
 
-	h2o_timeout_unlink(&conn->timeout);
+	h2o_timer_unlink(&conn->timer);
 	h2o_socket_read_stop(conn->sock);
 	h2o_socket_close(conn->sock);
 	PQfinish(conn->conn);
@@ -233,8 +236,9 @@ static void on_database_connect_read_ready(h2o_socket_t *sock, const char *err)
 					break;
 				case PGRES_PIPELINE_SYNC:
 					PQclear(result);
-					h2o_timeout_unlink(&conn->timeout);
+					h2o_timer_unlink(&conn->timer);
 					h2o_socket_read_stop(conn->sock);
+					h2o_socket_read_start(conn->sock, on_database_read_ready);
 					process_queries(conn);
 					return;
 				default:
@@ -249,9 +253,9 @@ static void on_database_connect_read_ready(h2o_socket_t *sock, const char *err)
 	}
 }
 
-static void on_database_connect_timeout(h2o_timeout_entry_t *entry)
+static void on_database_connect_timeout(h2o_timer_t *timer)
 {
-	db_conn_t * const conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, timeout, entry);
+	db_conn_t * const conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, timer, timer);
 
 	ERROR(DB_TIMEOUT_ERROR);
 	on_database_connect_error(conn, true, DB_TIMEOUT_ERROR);
@@ -352,9 +356,11 @@ static void on_database_read_ready(h2o_socket_t *sock, const char *err)
 
 			if (param->on_result(param, result) == DONE) {
 				conn->query_num++;
-				h2o_timeout_unlink(&conn->timeout);
-				conn->timeout.cb = on_database_timeout;
-				h2o_timeout_link(conn->pool->loop, &conn->pool->timeout, &conn->timeout);
+				h2o_timer_unlink(&conn->timer);
+				conn->timer.cb = on_database_timeout;
+				h2o_timer_link(conn->pool->loop,
+				               conn->pool->config->db_timeout * MS_IN_S,
+				               &conn->timer);
 				conn->flags |= EXPECT_SYNC;
 				conn->queries.head = next;
 
@@ -369,18 +375,20 @@ static void on_database_read_ready(h2o_socket_t *sock, const char *err)
 		}
 		else {
 			assert(!result);
-			h2o_timeout_unlink(&conn->timeout);
-			h2o_socket_read_stop(conn->sock);
+			h2o_timer_unlink(&conn->timer);
 			break;
 		}
 	}
 
+	for (PGnotify *notify = PQnotifies(conn->conn); notify; notify = PQnotifies(conn->conn))
+		PQfreemem(notify);
+
 	process_queries(conn);
 }
 
-static void on_database_timeout(h2o_timeout_entry_t *timeout)
+static void on_database_timeout(h2o_timer_t *timer)
 {
-	db_conn_t * const conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, timeout, timeout);
+	db_conn_t * const conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, timer, timer);
 
 	ERROR(DB_TIMEOUT_ERROR);
 
@@ -425,7 +433,7 @@ static void poll_database_connection(h2o_socket_t *sock, const char *err)
 	if (err)
 		ERROR(err);
 	else {
-		const PostgresPollingStatusType status = conn->flags & IS_RESETTING ?
+		const PostgresPollingStatusType status = conn->flags & RESET ?
 		                                         PQresetPoll(conn->conn) :
 		                                         PQconnectPoll(conn->conn);
 		const int sd = PQsocket(conn->conn);
@@ -450,7 +458,7 @@ static void poll_database_connection(h2o_socket_t *sock, const char *err)
 
 				return;
 			case PGRES_POLLING_OK:
-				h2o_timeout_unlink(&conn->timeout);
+				h2o_timer_unlink(&conn->timer);
 				h2o_socket_read_stop(conn->sock);
 
 				if (PQsetnonblocking(conn->conn, 1)) {
@@ -474,6 +482,7 @@ static void poll_database_connection(h2o_socket_t *sock, const char *err)
 					conn->sock = sock;
 				}
 
+				conn->flags &= ~RESET;
 				prepare_statements(conn);
 				return;
 			case PGRES_POLLING_READING:
@@ -525,13 +534,15 @@ static void prepare_statements(db_conn_t *conn)
 		}
 
 		conn->prepared_statement = NULL;
-		conn->timeout.cb = on_database_connect_timeout;
-		h2o_timeout_link(conn->pool->loop, &conn->pool->timeout, &conn->timeout);
+		conn->timer.cb = on_database_connect_timeout;
+		h2o_timer_link(conn->pool->loop, conn->pool->config->db_timeout * MS_IN_S, &conn->timer);
 		h2o_socket_read_start(conn->sock, on_database_connect_read_ready);
 		on_database_connect_write_ready(conn->sock, NULL);
 	}
-	else
+	else {
+		h2o_socket_read_start(conn->sock, on_database_read_ready);
 		process_queries(conn);
+	}
 }
 
 static void process_queries(db_conn_t *conn)
@@ -558,9 +569,10 @@ static void process_queries(db_conn_t *conn)
 		}
 	}
 
-	if (!conn->queries.head && !(conn->flags & (EXPECT_SYNC | IGNORE_RESULT))) {
+	if (!conn->queries.head && !(conn->flags & (EXPECT_SYNC | IDLE | IGNORE_RESULT))) {
 		conn->l.next = conn->pool->conn;
 		conn->pool->conn = &conn->l;
+		conn->flags |= IDLE;
 	}
 }
 
@@ -569,7 +581,7 @@ static void start_database_connect(db_conn_pool_t *pool, db_conn_t *conn)
 	if (conn) {
 		PGconn * const c = conn->conn;
 
-		h2o_timeout_unlink(&conn->timeout);
+		h2o_timer_unlink(&conn->timer);
 		h2o_socket_read_stop(conn->sock);
 		h2o_socket_close(conn->sock);
 
@@ -580,7 +592,7 @@ static void start_database_connect(db_conn_pool_t *pool, db_conn_t *conn)
 
 		memset(conn, 0, sizeof(*conn));
 		conn->conn = c;
-		conn->flags = IS_RESETTING;
+		conn->flags = RESET;
 	}
 	else {
 		assert(pool->conn_num);
@@ -610,8 +622,8 @@ static void start_database_connect(db_conn_pool_t *pool, db_conn_t *conn)
 		conn->prepared_statement = pool->prepared_statements;
 		conn->queries.tail = &conn->queries.head;
 		conn->query_num = pool->config->max_pipeline_query_num;
-		conn->timeout.cb = on_database_connect_timeout;
-		h2o_timeout_link(pool->loop, &pool->timeout, &conn->timeout);
+		conn->timer.cb = on_database_connect_timeout;
+		h2o_timer_link(pool->loop, pool->config->db_timeout * MS_IN_S, &conn->timer);
 		h2o_socket_notify_write(conn->sock, poll_database_connection);
 		return;
 	}
@@ -642,6 +654,7 @@ int execute_database_query(db_conn_pool_t *pool, db_query_param_t *param)
 		db_conn_t * const conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, l, pool->conn);
 
 		assert(!conn->queries.head);
+		assert(conn->flags & IDLE);
 		assert(!(conn->flags & (EXPECT_SYNC | IGNORE_RESULT)));
 		pool->conn = conn->l.next;
 		ret = do_execute_query(conn, param);
@@ -684,8 +697,9 @@ void free_database_connection_pool(db_conn_pool_t *pool)
 
 			assert(!conn->queries.head);
 			assert(conn->query_num == pool->config->max_pipeline_query_num);
-			assert(!(conn->flags & (EXPECT_SYNC | IGNORE_RESULT)));
-			assert(!h2o_timeout_is_linked(&conn->timeout));
+			assert(conn->flags & IDLE);
+			assert(!(conn->flags & (EXPECT_SYNC | IGNORE_RESULT | RESET)));
+			assert(!h2o_timer_is_linked(&conn->timer));
 			h2o_socket_read_stop(conn->sock);
 			h2o_socket_close(conn->sock);
 			PQfinish(conn->conn);
@@ -695,7 +709,6 @@ void free_database_connection_pool(db_conn_pool_t *pool)
 		} while (pool->conn);
 
 	assert(num + pool->conn_num == pool->config->max_db_conn_num);
-	h2o_timeout_dispose(pool->loop, &pool->timeout);
 }
 
 void initialize_database_connection_pool(const char *conninfo,
@@ -712,7 +725,6 @@ void initialize_database_connection_pool(const char *conninfo,
 	pool->queries.tail = &pool->queries.head;
 	pool->conn_num = config->max_db_conn_num;
 	pool->query_num = config->max_query_num;
-	h2o_timeout_init(loop, &pool->timeout, config->db_timeout * MS_IN_S);
 }
 
 void remove_prepared_statements(list_t *prepared_statements)

+ 0 - 1
frameworks/C/h2o/src/database.h

@@ -68,7 +68,6 @@ typedef struct {
 	queue_t queries;
 	size_t conn_num;
 	size_t query_num;
-	h2o_timeout_t timeout;
 } db_conn_pool_t;
 
 void add_prepared_statement(const char *name, const char *query, list_t **prepared_statements);

+ 1 - 0
frameworks/C/h2o/src/global_data.h

@@ -59,6 +59,7 @@ typedef struct {
 	SSL_CTX *ssl_ctx;
 	size_t memory_alignment;
 	int signal_fd;
+	h2o_buffer_prototype_t buffer_prototype;
 	h2o_globalconf_t h2o_config;
 	request_handler_data_t request_handler_data;
 } global_data_t;

+ 14 - 62
frameworks/C/h2o/src/handlers/fortune.c

@@ -41,7 +41,6 @@
 #define FORTUNE_TABLE_NAME "Fortune"
 #define FORTUNE_QUERY "SELECT * FROM " FORTUNE_TABLE_NAME ";"
 #define ID_FIELD_NAME "id"
-#define MAX_IOVEC 128
 #define MESSAGE_FIELD_NAME "message"
 #define NEW_FORTUNE_ID "0"
 #define NEW_FORTUNE_MESSAGE "Additional fortune added at request time."
@@ -54,20 +53,10 @@ typedef struct {
 } fortune_t;
 
 typedef struct {
-	list_t l;
-	size_t iovcnt;
-	size_t max_iovcnt;
-	h2o_iovec_t iov[];
-} iovec_list_t;
-
-typedef struct {
-	PGresult *data;
+	h2o_buffer_t *buffer;
 	const fortune_t *fortune_iter;
-	list_t *iovec_list;
-	iovec_list_t *iovec_list_iter;
 	h2o_req_t *req;
 	list_t *result;
-	size_t content_length;
 	size_t num_result;
 	bool cleanup;
 	db_query_param_t param;
@@ -86,7 +75,6 @@ static uintmax_t add_iovec(mustache_api_t *api,
 static void cleanup_fortunes(fortune_ctx_t *fortune_ctx);
 static void cleanup_request(void *data);
 static int compare_fortunes(const list_t *x, const list_t *y);
-static void complete_fortunes(struct st_h2o_generator_t *self, h2o_req_t *req);
 static int fortunes(struct st_h2o_handler_t *self, h2o_req_t *req);
 static void on_fortune_error(db_query_param_t *param, const char *error_string);
 static result_return_t on_fortune_result(db_query_param_t *param, PGresult *result);
@@ -120,29 +108,15 @@ static uintmax_t add_iovec(mustache_api_t *api,
 	IGNORE_FUNCTION_PARAMETER(api);
 
 	fortune_ctx_t * const fortune_ctx = userdata;
-	iovec_list_t *iovec_list = fortune_ctx->iovec_list_iter;
-
-	if (iovec_list->iovcnt >= iovec_list->max_iovcnt) {
-		const size_t sz = offsetof(iovec_list_t, iov) + MAX_IOVEC * sizeof(h2o_iovec_t);
-
-		iovec_list = h2o_mem_alloc_pool(&fortune_ctx->req->pool, sz);
-		memset(iovec_list, 0, offsetof(iovec_list_t, iov));
-		iovec_list->max_iovcnt = MAX_IOVEC;
-		fortune_ctx->iovec_list_iter->l.next = &iovec_list->l;
-		fortune_ctx->iovec_list_iter = iovec_list;
-	}
 
-	memset(iovec_list->iov + iovec_list->iovcnt, 0, sizeof(*iovec_list->iov));
-	iovec_list->iov[iovec_list->iovcnt].base = (char *) buffer;
-	iovec_list->iov[iovec_list->iovcnt++].len = buffer_size;
-	fortune_ctx->content_length += buffer_size;
+	h2o_buffer_append(&fortune_ctx->buffer, buffer, buffer_size);
 	return 1;
 }
 
 static void cleanup_fortunes(fortune_ctx_t *fortune_ctx)
 {
-	if (fortune_ctx->data)
-		PQclear(fortune_ctx->data);
+	if (fortune_ctx->buffer)
+		h2o_buffer_dispose(&fortune_ctx->buffer);
 
 	free(fortune_ctx);
 }
@@ -170,22 +144,6 @@ static int compare_fortunes(const list_t *x, const list_t *y)
 	return ret;
 }
 
-static void complete_fortunes(struct st_h2o_generator_t *self, h2o_req_t *req)
-{
-	fortune_ctx_t * const fortune_ctx = H2O_STRUCT_FROM_MEMBER(fortune_ctx_t, generator, self);
-	iovec_list_t * const iovec_list = H2O_STRUCT_FROM_MEMBER(iovec_list_t,
-	                                                         l,
-	                                                         fortune_ctx->iovec_list);
-
-	fortune_ctx->iovec_list = iovec_list->l.next;
-
-	const h2o_send_state_t state = fortune_ctx->iovec_list ?
-	                               H2O_SEND_STATE_IN_PROGRESS :
-	                               H2O_SEND_STATE_FINAL;
-
-	h2o_send(req, iovec_list->iov, iovec_list->iovcnt, state);
-}
-
 static int fortunes(struct st_h2o_handler_t *self, h2o_req_t *req)
 {
 	IGNORE_FUNCTION_PARAMETER(self);
@@ -194,7 +152,7 @@ static int fortunes(struct st_h2o_handler_t *self, h2o_req_t *req)
 	                                                      event_loop.h2o_ctx,
 	                                                      req->conn->ctx);
 	fortune_ctx_t * const fortune_ctx = h2o_mem_alloc(sizeof(*fortune_ctx));
-	fortune_t * const fortune = h2o_mem_alloc_pool(&req->pool, sizeof(*fortune));
+	fortune_t * const fortune = h2o_mem_alloc_pool(&req->pool, fortune_t, 1);
 	fortune_ctx_t ** const p = h2o_mem_alloc_shared(&req->pool, sizeof(*p), cleanup_request);
 
 	*p = fortune_ctx;
@@ -204,7 +162,6 @@ static int fortunes(struct st_h2o_handler_t *self, h2o_req_t *req)
 	fortune->message.base = NEW_FORTUNE_MESSAGE;
 	fortune->message.len = sizeof(NEW_FORTUNE_MESSAGE) - 1;
 	memset(fortune_ctx, 0, sizeof(*fortune_ctx));
-	fortune_ctx->generator.proceed = complete_fortunes;
 	fortune_ctx->num_result = 1;
 	fortune_ctx->param.command = FORTUNE_TABLE_NAME;
 	fortune_ctx->param.on_error = on_fortune_error;
@@ -240,7 +197,6 @@ static result_return_t on_fortune_result(db_query_param_t *param, PGresult *resu
 	const bool cleanup = fortune_ctx->cleanup;
 
 	fortune_ctx->cleanup = true;
-	fortune_ctx->data = result;
 
 	if (cleanup)
 		cleanup_fortunes(fortune_ctx);
@@ -251,7 +207,8 @@ static result_return_t on_fortune_result(db_query_param_t *param, PGresult *resu
 
 		for (size_t i = 0; i < num_rows; i++) {
 			fortune_t * const fortune = h2o_mem_alloc_pool(&fortune_ctx->req->pool,
-			                                               sizeof(*fortune));
+			                                               fortune_t,
+			                                               1);
 			char * const id = PQgetvalue(result, i, 0);
 			char * const message = PQgetvalue(result, i, 1);
 			const size_t id_len = PQgetlength(result, i, 0);
@@ -275,28 +232,22 @@ static result_return_t on_fortune_result(db_query_param_t *param, PGresult *resu
 		thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
 		                                                      event_loop.h2o_ctx,
 		                                                      fortune_ctx->req->conn->ctx);
-		const size_t iovcnt = MIN(MAX_IOVEC, fortune_ctx->num_result * 5 + 2);
-		const size_t sz = offsetof(iovec_list_t, iov) + iovcnt * sizeof(h2o_iovec_t);
-		iovec_list_t * const iovec_list = h2o_mem_alloc_pool(&fortune_ctx->req->pool, sz);
 
-		memset(iovec_list, 0, offsetof(iovec_list_t, iov));
-		iovec_list->max_iovcnt = iovcnt;
-		fortune_ctx->iovec_list_iter = iovec_list;
+		h2o_buffer_init(&fortune_ctx->buffer,
+		                &ctx->global_thread_data->global_data->buffer_prototype);
 		fortune_ctx->result = sort_list(fortune_ctx->result, compare_fortunes);
 
 		struct mustache_token_t * const fortunes_template =
 			ctx->global_thread_data->global_data->request_handler_data.fortunes_template;
 
 		if (mustache_render(&api, fortune_ctx, fortunes_template)) {
-			fortune_ctx->iovec_list = iovec_list->l.next;
-			set_default_response_param(HTML, fortune_ctx->content_length, fortune_ctx->req);
+			set_default_response_param(HTML, fortune_ctx->buffer->size, fortune_ctx->req);
 			h2o_start_response(fortune_ctx->req, &fortune_ctx->generator);
 
-			const h2o_send_state_t state = fortune_ctx->iovec_list ?
-			                               H2O_SEND_STATE_IN_PROGRESS :
-			                               H2O_SEND_STATE_FINAL;
+			h2o_iovec_t body = {.base = fortune_ctx->buffer->bytes,
+			                    .len = fortune_ctx->buffer->size};
 
-			h2o_send(fortune_ctx->req, iovec_list->iov, iovec_list->iovcnt, state);
+			h2o_send(fortune_ctx->req, &body, 1, H2O_SEND_STATE_FINAL);
 		}
 		else
 			send_error(INTERNAL_SERVER_ERROR, REQ_ERROR, fortune_ctx->req);
@@ -306,6 +257,7 @@ static result_return_t on_fortune_result(db_query_param_t *param, PGresult *resu
 		send_error(BAD_GATEWAY, DB_ERROR, fortune_ctx->req);
 	}
 
+	PQclear(result);
 	return DONE;
 }
 

+ 11 - 1
frameworks/C/h2o/src/main.c

@@ -29,6 +29,7 @@
 #include <sys/resource.h>
 #include <sys/signalfd.h>
 #include <sys/time.h>
+#include <sys/utsname.h>
 
 #include "database.h"
 #include "error.h"
@@ -96,6 +97,7 @@ static int initialize_global_data(const config_t *config, global_data_t *global_
 	sigset_t signals;
 
 	memset(global_data, 0, sizeof(*global_data));
+	global_data->buffer_prototype._initial_buf.capacity = H2O_SOCKET_INITIAL_INPUT_BUFFER_SIZE;
 	global_data->memory_alignment = get_maximum_cache_line_size();
 	CHECK_ERRNO(sigemptyset, &signals);
 #ifdef NDEBUG
@@ -139,7 +141,15 @@ static int initialize_global_data(const config_t *config, global_data_t *global_
 	global_data->global_thread_data = initialize_global_thread_data(config, global_data);
 
 	if (global_data->global_thread_data) {
-		printf("Number of processors: %zu\nMaximum cache line size: %zu\n",
+		struct utsname name = {.sysname = {'\0'}};
+
+		uname(&name);
+		printf("Operating system: %s %s %s\n"
+		       "Number of processors: %zu\n"
+		       "Maximum cache line size: %zu\n",
+		       name.sysname,
+		       name.release,
+		       name.version,
 		       h2o_numproc(),
 		       global_data->memory_alignment);
 		return 0;

+ 1 - 0
frameworks/C/h2o/src/request_handler.c

@@ -188,6 +188,7 @@ void set_default_response_param(content_type_t content_type, size_t content_leng
 	req->res.content_length = content_length;
 	req->res.status = OK;
 	req->res.reason = status_code_to_string(req->res.status);
+	h2o_resp_add_date_header(req);
 
 	switch (content_type) {
 		case JSON:

+ 18 - 16
frameworks/C/h2o/src/thread.c

@@ -38,26 +38,26 @@
 #include "thread.h"
 
 static void *run_thread(void *arg);
-static void set_thread_memory_allocation_policy(size_t thread_num);
+static void set_thread_memory_allocation_policy(void);
 
 static void *run_thread(void *arg)
 {
 	thread_context_t ctx;
 
 	initialize_thread_context(arg, false, &ctx);
-	set_thread_memory_allocation_policy(ctx.global_thread_data->config->thread_num);
+
+	// There is no need to set a memory allocation policy unless
+	// the application controls the processor affinity as well.
+	if (!(ctx.global_thread_data->config->thread_num % h2o_numproc()))
+		set_thread_memory_allocation_policy();
+
 	event_loop(&ctx);
 	free_thread_context(&ctx);
 	pthread_exit(NULL);
 }
 
-static void set_thread_memory_allocation_policy(size_t thread_num)
+static void set_thread_memory_allocation_policy(void)
 {
-	// There is no need to set a memory allocation policy unless
-	// the application controls the processor affinity as well.
-	if (thread_num % h2o_numproc())
-		return;
-
 	void *stack_addr;
 	size_t stack_size;
 	unsigned memory_node;
@@ -74,14 +74,16 @@ static void set_thread_memory_allocation_policy(size_t thread_num)
 	memset(nodemask, 0, sizeof(nodemask));
 	nodemask[memory_node / (sizeof(*nodemask) * CHAR_BIT)] |=
 		1UL << (memory_node % (sizeof(*nodemask) * CHAR_BIT));
-	CHECK_ERRNO(mbind,
-	            stack_addr,
-	            stack_size,
-	            MPOL_PREFERRED,
-	            nodemask,
-	            memory_node + 1,
-	            MPOL_MF_MOVE | MPOL_MF_STRICT);
-	CHECK_ERRNO(set_mempolicy, MPOL_PREFERRED, NULL, 0);
+
+	if (mbind(stack_addr,
+	          stack_size,
+	          MPOL_PREFERRED,
+	          nodemask,
+	          memory_node + 1,
+	          MPOL_MF_MOVE | MPOL_MF_STRICT))
+		STANDARD_ERROR("mbind");
+	else if (set_mempolicy(MPOL_PREFERRED, NULL, 0))
+		STANDARD_ERROR("set_mempolicy");
 }
 
 void free_thread_context(thread_context_t *ctx)

+ 1 - 1
frameworks/CFML/CFML/cfml-adobe.dockerfile

@@ -1,4 +1,4 @@
-FROM ortussolutions/commandbox:3.4.4
+FROM ortussolutions/commandbox:3.6.4
 
 ENV cfconfig_adminPassword=password
 ENV box_server_runwar_args=--cache-servlet-paths=true

+ 1 - 1
frameworks/CFML/CFML/cfml.dockerfile

@@ -1,4 +1,4 @@
-FROM ortussolutions/commandbox:3.4.4
+FROM ortussolutions/commandbox:3.6.4
 
 ENV cfconfig_adminPassword=password
 ENV box_server_runwar_args=--cache-servlet-paths=true

+ 1 - 1
frameworks/CFML/coldbox/coldbox-adobe.dockerfile

@@ -1,4 +1,4 @@
-FROM ortussolutions/commandbox:3.4.4
+FROM ortussolutions/commandbox:3.6.4
 
 ENV cfconfig_adminPassword=password
 ENV box_server_runwar_args=--cache-servlet-paths=true

+ 1 - 1
frameworks/CFML/coldbox/coldbox.dockerfile

@@ -1,4 +1,4 @@
-FROM ortussolutions/commandbox:3.2.1
+FROM ortussolutions/commandbox:3.6.4
 
 ENV cfconfig_adminPassword=password
 ENV box_server_runwar_args=--cache-servlet-paths=true

+ 1 - 1
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.Caching.cs

@@ -10,7 +10,7 @@ namespace PlatformBenchmarks;
 
 public partial class BenchmarkApplication
 {
-    private async Task Caching(PipeWriter pipeWriter, int count)
+    private static async Task Caching(PipeWriter pipeWriter, int count)
     {
         OutputMultipleQueries(pipeWriter, await Db.LoadCachedQueries(count), SerializerContext.CachedWorldArray);
     }

+ 41 - 31
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs

@@ -3,53 +3,63 @@
 
 #if DATABASE
 
-using System.Collections.Generic;
 using System.IO.Pipelines;
-using System.Text.Encodings.Web;
-using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
+using RazorSlices;
 
 namespace PlatformBenchmarks
 {
     public partial class BenchmarkApplication
     {
-        private static ReadOnlySpan<byte> _fortunesPreamble =>
-            "HTTP/1.1 200 OK\r\n"u8 +
-            "Server: K\r\n"u8 +
-            "Content-Type: text/html; charset=UTF-8\r\n"u8 +
-            "Content-Length: "u8;
-
         private async Task Fortunes(PipeWriter pipeWriter)
         {
-            OutputFortunes(pipeWriter, await Db.LoadFortunesRows());
+            await OutputFortunes(pipeWriter, await Db.LoadFortunesRows(), FortunesTemplateFactory);
         }
 
-        private void OutputFortunes(PipeWriter pipeWriter, List<Fortune> model)
+        private ValueTask OutputFortunes<TModel>(PipeWriter pipeWriter, TModel model, SliceFactory<TModel> templateFactory)
         {
-            var writer = GetWriter(pipeWriter, sizeHint: 1600); // in reality it's 1361
-
-            writer.Write(_fortunesPreamble);
-
-            var lengthWriter = writer;
-            writer.Write(_contentLengthGap);
+            // Render headers
+            var preamble = """
+                HTTP/1.1 200 OK
+                Server: K
+                Content-Type: text/html; charset=utf-8
+                Transfer-Encoding: chunked
+                """u8;
+            var headersLength = preamble.Length + DateHeader.HeaderBytes.Length;
+            var headersSpan = pipeWriter.GetSpan(headersLength);
+            preamble.CopyTo(headersSpan);
+            DateHeader.HeaderBytes.CopyTo(headersSpan[preamble.Length..]);
+            pipeWriter.Advance(headersLength);
 
-            // Date header
-            writer.Write(DateHeader.HeaderBytes);
+            // Render body
+            var template = templateFactory(model);
+            // Kestrel PipeWriter span size is 4K, headers above already written to first span & template output is ~1350 bytes,
+            // so 2K chunk size should result in only a single span and chunk being used.
+            var chunkedWriter = GetChunkedWriter(pipeWriter, chunkSizeHint: 2048);
+            var renderTask = template.RenderAsync(chunkedWriter, null, HtmlEncoder);
 
-            var bodyStart = writer.Buffered;
-            // Body
-            writer.Write(_fortunesTableStart);
-            foreach (var item in model)
+            if (renderTask.IsCompletedSuccessfully)
             {
-                writer.Write(_fortunesRowStart);
-                writer.WriteNumeric((uint)item.Id);
-                writer.Write(_fortunesColumn);
-                writer.WriteUtf8String(HtmlEncoder.Encode(item.Message));
-                writer.Write(_fortunesRowEnd);
+                renderTask.GetAwaiter().GetResult();
+                EndTemplateRendering(chunkedWriter, template);
+                return ValueTask.CompletedTask;
             }
-            writer.Write(_fortunesTableEnd);
-            lengthWriter.WriteNumeric((uint)(writer.Buffered - bodyStart));
 
-            writer.Commit();
+            return AwaitTemplateRenderTask(renderTask, chunkedWriter, template);
+        }
+
+        private static async ValueTask AwaitTemplateRenderTask(ValueTask renderTask, ChunkedBufferWriter<WriterAdapter> chunkedWriter, RazorSlice template)
+        {
+            await renderTask;
+            EndTemplateRendering(chunkedWriter, template);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void EndTemplateRendering(ChunkedBufferWriter<WriterAdapter> chunkedWriter, RazorSlice template)
+        {
+            chunkedWriter.End();
+            ReturnChunkedWriter(chunkedWriter);
+            template.Dispose();
         }
     }
 }

+ 12 - 1
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs

@@ -251,7 +251,18 @@ public partial class BenchmarkApplication : IHttpConnection
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     private static BufferWriter<WriterAdapter> GetWriter(PipeWriter pipeWriter, int sizeHint)
-        => new(new WriterAdapter(pipeWriter), sizeHint);
+        => new(new(pipeWriter), sizeHint);
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static ChunkedBufferWriter<WriterAdapter> GetChunkedWriter(PipeWriter pipeWriter, int chunkSizeHint)
+    {
+        var writer = ChunkedWriterPool.Get();
+        writer.SetOutput(new WriterAdapter(pipeWriter), chunkSizeHint);
+        return writer;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void ReturnChunkedWriter(ChunkedBufferWriter<WriterAdapter> writer) => ChunkedWriterPool.Return(writer);
 
     private struct WriterAdapter : IBufferWriter<byte>
     {

+ 1 - 1
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.Json.cs

@@ -25,7 +25,7 @@ public partial class BenchmarkApplication
 
         writer.Commit();
 
-        Utf8JsonWriter utf8JsonWriter = t_writer ??= new Utf8JsonWriter(bodyWriter, new JsonWriterOptions { SkipValidation = true });
+        var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(bodyWriter, new JsonWriterOptions { SkipValidation = true });
         utf8JsonWriter.Reset(bodyWriter);
 
         // Body

+ 3 - 3
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.MultipleQueries.cs

@@ -12,7 +12,7 @@ namespace PlatformBenchmarks
 {
     public partial class BenchmarkApplication
     {
-        private async Task MultipleQueries(PipeWriter pipeWriter, int count)
+        private static async Task MultipleQueries(PipeWriter pipeWriter, int count)
         {
             OutputMultipleQueries(pipeWriter, await Db.LoadMultipleQueriesRows(count), SerializerContext.WorldArray);
         }
@@ -31,11 +31,11 @@ namespace PlatformBenchmarks
 
             writer.Commit();
 
-            Utf8JsonWriter utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
+            var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
             utf8JsonWriter.Reset(pipeWriter);
 
             // Body
-            JsonSerializer.Serialize<TWorld[]>(utf8JsonWriter, rows, jsonTypeInfo);
+            JsonSerializer.Serialize(utf8JsonWriter, rows, jsonTypeInfo);
 
             // Content-Length
             lengthWriter.WriteNumeric((uint)utf8JsonWriter.BytesCommitted);

+ 2 - 2
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.SingleQuery.cs

@@ -11,7 +11,7 @@ namespace PlatformBenchmarks
 {
     public partial class BenchmarkApplication
     {
-        private async Task SingleQuery(PipeWriter pipeWriter)
+        private static async Task SingleQuery(PipeWriter pipeWriter)
         {
             OutputSingleQuery(pipeWriter, await Db.LoadSingleQueryRow());
         }
@@ -30,7 +30,7 @@ namespace PlatformBenchmarks
 
             writer.Commit();
 
-            Utf8JsonWriter utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
+            var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
             utf8JsonWriter.Reset(pipeWriter);
 
             // Body

+ 3 - 3
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.Updates.cs

@@ -11,7 +11,7 @@ namespace PlatformBenchmarks
 {
     public partial class BenchmarkApplication
     {
-        private async Task Updates(PipeWriter pipeWriter, int count)
+        private static async Task Updates(PipeWriter pipeWriter, int count)
         {
             OutputUpdates(pipeWriter, await Db.LoadMultipleUpdatesRows(count));
         }
@@ -30,11 +30,11 @@ namespace PlatformBenchmarks
 
             writer.Commit();
 
-            Utf8JsonWriter utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
+            var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
             utf8JsonWriter.Reset(pipeWriter);
 
             // Body
-            JsonSerializer.Serialize( utf8JsonWriter, rows, SerializerContext.WorldArray);
+            JsonSerializer.Serialize(utf8JsonWriter, rows, SerializerContext.WorldArray);
 
             // Content-Length
             lengthWriter.WriteNumeric((uint)utf8JsonWriter.BytesCommitted);

+ 62 - 42
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.cs

@@ -9,6 +9,8 @@ using System.Text.Json.Serialization;
 using System.Threading.Tasks;
 
 using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
+using Microsoft.Extensions.ObjectPool;
+using RazorSlices;
 
 namespace PlatformBenchmarks;
 
@@ -34,31 +36,49 @@ public sealed partial class BenchmarkApplication
         "Content-Length: "u8;
 
     private static ReadOnlySpan<byte> _plainTextBody => "Hello, World!"u8;
+    private static ReadOnlySpan<byte> _contentLengthGap => "    "u8;
 
-    private static readonly JsonContext SerializerContext = JsonContext.Default;
+#if DATABASE
+        public static RawDb Db { get; set; }
+#endif
 
-    [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
-    [JsonSerializable(typeof(JsonMessage))]
-    [JsonSerializable(typeof(CachedWorld[]))]
-    [JsonSerializable(typeof(World[]))]
-    private sealed partial class JsonContext : JsonSerializerContext
+    private static readonly DefaultObjectPool<ChunkedBufferWriter<WriterAdapter>> ChunkedWriterPool
+        = new(new ChunkedWriterObjectPolicy());
+
+    private sealed class ChunkedWriterObjectPolicy : IPooledObjectPolicy<ChunkedBufferWriter<WriterAdapter>>
     {
-    }
+        public ChunkedBufferWriter<WriterAdapter> Create() => new();
 
-    private static ReadOnlySpan<byte> _fortunesTableStart => "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>"u8;
-    private static ReadOnlySpan<byte> _fortunesRowStart => "<tr><td>"u8;
-    private static ReadOnlySpan<byte> _fortunesColumn => "</td><td>"u8;
-    private static ReadOnlySpan<byte> _fortunesRowEnd => "</td></tr>"u8;
-    private static ReadOnlySpan<byte> _fortunesTableEnd => "</table></body></html>"u8;
-    private static ReadOnlySpan<byte> _contentLengthGap => "    "u8;
+        public bool Return(ChunkedBufferWriter<WriterAdapter> writer)
+        {
+            writer.Reset();
+            return true;
+        }
+    }
 
 #if DATABASE
-        public static RawDb Db { get; set; }
+#if NPGSQL
+    private readonly static SliceFactory<List<FortuneUtf8>> FortunesTemplateFactory = RazorSlice.ResolveSliceFactory<List<FortuneUtf8>>("/Templates/FortunesUtf8.cshtml");
+#elif MYSQLCONNECTOR
+    private readonly static SliceFactory<List<FortuneUtf16>> FortunesTemplateFactory = RazorSlice.ResolveSliceFactory<List<FortuneUtf16>>("/Templates/FortunesUtf16.cshtml");
+#else
+#error "DATABASE defined by neither NPGSQL nor MYSQLCONNECTOR are defined"
+#endif
 #endif
 
     [ThreadStatic]
     private static Utf8JsonWriter t_writer;
 
+    private static readonly JsonContext SerializerContext = JsonContext.Default;
+
+    [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
+    [JsonSerializable(typeof(JsonMessage))]
+    [JsonSerializable(typeof(CachedWorld[]))]
+    [JsonSerializable(typeof(World[]))]
+    private sealed partial class JsonContext : JsonSerializerContext
+    {
+    }
+
     public static class Paths
     {
         public static ReadOnlySpan<byte> Json => "/json"u8;
@@ -78,41 +98,41 @@ public sealed partial class BenchmarkApplication
         _requestType = versionAndMethod.Method == Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod.Get ? GetRequestType(startLine.Slice(targetPath.Offset, targetPath.Length), ref _queries) : RequestType.NotRecognized;
     }
 
-    private RequestType GetRequestType(ReadOnlySpan<byte> path, ref int queries)
+    private static RequestType GetRequestType(ReadOnlySpan<byte> path, ref int queries)
     {
 #if !DATABASE
         if (path.Length == 10 && path.SequenceEqual(Paths.Plaintext))
         {
             return RequestType.PlainText;
         }
-        else if (path.Length == 5 && path.SequenceEqual(Paths.Json))
+        if (path.Length == 5 && path.SequenceEqual(Paths.Json))
         {
             return RequestType.Json;
         }
 #else
-            if (path.Length == 3 && path[0] == '/' && path[1] == 'd' && path[2] == 'b')
-            {
-                return RequestType.SingleQuery;
-            }
-            else if (path.Length == 9 && path[1] == 'f' && path.SequenceEqual(Paths.Fortunes))
-            {
-                return RequestType.Fortunes;
-            }
-            else if (path.Length >= 15 && path[1] == 'c' && path.StartsWith(Paths.Caching))
-            {
-                queries = ParseQueries(path.Slice(15));
-                return RequestType.Caching;
-            }
-            else if (path.Length >= 9 && path[1] == 'u' && path.StartsWith(Paths.Updates))
-            {
-                queries = ParseQueries(path.Slice(9));
-                return RequestType.Updates;
-            }
-            else if (path.Length >= 9 && path[1] == 'q' && path.StartsWith(Paths.MultipleQueries))
-            {
-                queries = ParseQueries(path.Slice(9));
-                return RequestType.MultipleQueries;
-            }
+        if (path.Length == 3 && path[0] == '/' && path[1] == 'd' && path[2] == 'b')
+        {
+            return RequestType.SingleQuery;
+        }
+        if (path.Length == 9 && path[1] == 'f' && path.SequenceEqual(Paths.Fortunes))
+        {
+            return RequestType.Fortunes;
+        }
+        if (path.Length >= 15 && path[1] == 'c' && path.StartsWith(Paths.Caching))
+        {
+            queries = ParseQueries(path.Slice(15));
+            return RequestType.Caching;
+        }
+        if (path.Length >= 9 && path[1] == 'u' && path.StartsWith(Paths.Updates))
+        {
+            queries = ParseQueries(path.Slice(9));
+            return RequestType.Updates;
+        }
+        if (path.Length >= 9 && path[1] == 'q' && path.StartsWith(Paths.MultipleQueries))
+        {
+            queries = ParseQueries(path.Slice(9));
+            return RequestType.MultipleQueries;
+        }
 #endif
         return RequestType.NotRecognized;
     }
@@ -138,13 +158,13 @@ public sealed partial class BenchmarkApplication
 
         private static int ParseQueries(ReadOnlySpan<byte> parameter)
         {
-            if (!Utf8Parser.TryParse(parameter, out int queries, out _) || queries < 1)
+            if (!Utf8Parser.TryParse(parameter, out int queries, out _))
             {
                 queries = 1;
             }
-            else if (queries > 500)
+            else
             {
-                queries = 500;
+                queries = Math.Clamp(queries, 1, 500);
             }
 
             return queries;

+ 1 - 1
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkConfigurationHelpers.cs

@@ -14,7 +14,7 @@ public static class BenchmarkConfigurationHelpers
 
         builder.UseSockets(options =>
         {
-            if (int.TryParse(builder.GetSetting("threadCount"), out int threadCount))
+            if (int.TryParse(builder.GetSetting("threadCount"), out var threadCount))
             {
                 options.IOQueueCount = threadCount;
             }

+ 2 - 2
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BufferWriter.cs

@@ -44,7 +44,7 @@ public ref struct BufferWriter<T> where T : IBufferWriter<byte>
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void Write(ReadOnlySpan<byte> source)
+    public void Write(scoped ReadOnlySpan<byte> source)
     {
         if (_span.Length >= source.Length)
         {
@@ -77,7 +77,7 @@ public ref struct BufferWriter<T> where T : IBufferWriter<byte>
         _span = _output.GetSpan(count);
     }
 
-    private void WriteMultiBuffer(ReadOnlySpan<byte> source)
+    private void WriteMultiBuffer(scoped ReadOnlySpan<byte> source)
     {
         while (source.Length > 0)
         {

+ 241 - 0
frameworks/CSharp/aspnetcore/PlatformBenchmarks/ChunkedBufferWriter.cs

@@ -0,0 +1,241 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace PlatformBenchmarks;
+
+internal sealed class ChunkedBufferWriter<TWriter> : IBufferWriter<byte> where TWriter : IBufferWriter<byte>
+{
+    private const int DefaultChunkSizeHint = 2048;
+    private static readonly StandardFormat DefaultHexFormat = GetHexFormat(DefaultChunkSizeHint);
+    private static ReadOnlySpan<byte> ChunkTerminator => "\r\n"u8;
+
+    private TWriter _output;
+    private int _chunkSizeHint;
+    private StandardFormat _hexFormat = DefaultHexFormat;
+    private Memory<byte> _currentFullChunk;
+    private Memory<byte> _currentChunk;
+    private int _buffered;
+    private bool _ended = false;
+
+    public Memory<byte> Memory => _currentChunk;
+
+    public TWriter Output => _output;
+
+    public int Buffered => _buffered;
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void SetOutput(TWriter output, int chunkSizeHint = DefaultChunkSizeHint)
+    {
+        _buffered = 0;
+        _chunkSizeHint = chunkSizeHint;
+        _output = output;
+
+        StartNewChunk(chunkSizeHint, isFirst: true);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Reset()
+    {
+        _buffered = 0;
+        _output = default;
+        _ended = false;
+        _hexFormat = DefaultHexFormat;
+        _currentFullChunk = default;
+        _currentChunk = default;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Advance(int count)
+    {
+        ThrowIfEnded();
+
+        _buffered += count;
+        _currentChunk = _currentChunk[count..];
+    }
+
+    public Memory<byte> GetMemory(int sizeHint = 0)
+    {
+        ThrowIfEnded();
+
+        if (_currentChunk.Length <= sizeHint)
+        {
+            EnsureMore(sizeHint);
+        }
+        return _currentChunk;
+    }
+
+    public Span<byte> GetSpan(int sizeHint = 0) => GetMemory(sizeHint).Span;
+
+    public void End()
+    {
+        ThrowIfEnded();
+
+        CommitCurrentChunk(isFinal: true);
+
+        _ended = true;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static StandardFormat GetHexFormat(int maxValue)
+    {
+        var hexDigitCount = CountHexDigits(maxValue);
+
+        return new StandardFormat('X', (byte)hexDigitCount);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static int CountHexDigits(int n) => n <= 16 ? 1 : (BitOperations.Log2((uint)n) >> 2) + 1;
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void StartNewChunk(int sizeHint, bool isFirst = false)
+    {
+        ThrowIfEnded();
+
+        // Header is like:
+        // 520\r\n
+
+        var oldFullChunkHexLength = -1;
+        if (!isFirst)
+        {
+            oldFullChunkHexLength = CountHexDigits(_currentFullChunk.Length);
+        }
+        _currentFullChunk = _output.GetMemory(Math.Max(_chunkSizeHint, sizeHint));
+        var newFullChunkHexLength = CountHexDigits(_currentFullChunk.Length);
+
+        var currentFullChunkSpan = _currentFullChunk.Span;
+
+        // Write space for HEX digits
+        currentFullChunkSpan[..newFullChunkHexLength].Fill(48); // 48 == '0'
+
+        // Write header terminator
+        var terminator = "\r\n"u8;
+        terminator.CopyTo(currentFullChunkSpan[newFullChunkHexLength..]);
+        var chunkHeaderLength = newFullChunkHexLength + terminator.Length;
+        _currentChunk = _currentFullChunk[chunkHeaderLength..];
+
+        if ((!isFirst && oldFullChunkHexLength != newFullChunkHexLength) || (isFirst && DefaultChunkSizeHint != _chunkSizeHint))
+        {
+            // Update HEX format if changed
+            _hexFormat = GetHexFormat(_currentFullChunk.Length);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void CommitCurrentChunk(bool isFinal = false, int sizeHint = 0)
+    {
+        ThrowIfEnded();
+
+        var contentLength = _buffered;
+
+        if (contentLength > 0)
+        {
+            // Update the chunk header
+            var chunkLengthHexDigitsLength = CountHexDigits(contentLength);
+            var span = _currentFullChunk.Span;
+            if (!Utf8Formatter.TryFormat(contentLength, span, out var bytesWritten, _hexFormat))
+            {
+                throw new NotSupportedException("Chunk size too large");
+            }
+            Debug.Assert(chunkLengthHexDigitsLength == bytesWritten, "HEX formatting math problem.");
+            var headerLength = chunkLengthHexDigitsLength + 2;
+
+            // Total chunk length: content length as HEX string + \r\n + content + \r\n
+            var spanOffset = headerLength + contentLength;
+            var chunkTotalLength = spanOffset + ChunkTerminator.Length;
+
+            Debug.Assert(span.Length >= chunkTotalLength, "Bad chunk size calculation.");
+
+            // Write out the chunk terminator
+            ChunkTerminator.CopyTo(span[spanOffset..]);
+            spanOffset = chunkTotalLength;
+
+            if (!isFinal)
+            {
+                _output.Advance(chunkTotalLength);
+                StartNewChunk(sizeHint);
+            }
+            else
+            {
+                // Write out final chunk (zero-length chunk)
+                var terminator = "0\r\n\r\n"u8;
+                if ((spanOffset + terminator.Length) <= span.Length)
+                {
+                    // There's space for the final chunk in the current span
+                    terminator.CopyTo(span[spanOffset..]);
+                    _output.Advance(chunkTotalLength + terminator.Length);
+                }
+                else
+                {
+                    // Final chunk doesn't fit in current span so just write it directly after advancing the writer
+                    _output.Advance(chunkTotalLength);
+                    _output.Write(terminator);
+                }
+            }
+
+            _buffered = 0;
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Write(ReadOnlySpan<byte> source)
+    {
+        ThrowIfEnded();
+
+        if (_currentChunk.Length >= (source.Length + ChunkTerminator.Length))
+        {
+            source.CopyTo(_currentChunk.Span);
+            Advance(source.Length);
+        }
+        else
+        {
+            WriteMultiBuffer(source);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private void EnsureMore(int count = 0)
+    {
+        if (count > (_currentChunk.Length - _buffered - ChunkTerminator.Length))
+        {
+            if (_buffered > 0)
+            {
+                CommitCurrentChunk(isFinal: false, count);
+            }
+            else
+            {
+                StartNewChunk(count);
+            }
+        }
+    }
+
+    private void WriteMultiBuffer(ReadOnlySpan<byte> source)
+    {
+        while (source.Length > 0)
+        {
+            if ((_currentChunk.Length - ChunkTerminator.Length) == 0)
+            {
+                EnsureMore();
+            }
+
+            var writable = Math.Min(source.Length, _currentChunk.Length - ChunkTerminator.Length);
+            source[..writable].CopyTo(_currentChunk.Span);
+            source = source[writable..];
+            Advance(writable);
+        }
+    }
+
+    private void ThrowIfEnded()
+    {
+        if (_ended)
+        {
+            throw new InvalidOperationException("Cannot use the writer after calling End().");
+        }
+    }
+}

+ 13 - 9
frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/BatchUpdateString.cs

@@ -15,21 +15,25 @@ internal sealed class BatchUpdateString
     private static readonly string[] _queries = new string[MaxBatch + 1];
 
     public static string Query(int batchSize)
-    {
-        if (_queries[batchSize] != null)
-        {
-            return _queries[batchSize];
-        }
-
-        var lastIndex = batchSize - 1;
+        => _queries[batchSize] is null
+            ? CreateBatch(batchSize)
+            : _queries[batchSize];
 
+    private static string CreateBatch(int batchSize)
+    {
         var sb = StringBuilderCache.Acquire();
 
         if (DatabaseServer == DatabaseServer.PostgreSql)
         {
             sb.Append("UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ");
-            Enumerable.Range(0, lastIndex).ToList().ForEach(i => sb.Append($"(@Id_{i}, @Random_{i}), "));
-            sb.Append($"(@Id_{lastIndex}, @Random_{lastIndex}) ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id");
+            var c = 1;
+            for (var i = 0; i < batchSize; i++)
+            {
+                if (i > 0)
+                    sb.Append(", ");
+                sb.Append($"(${c++}, ${c++})");
+            }
+            sb.Append(" ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id");
         }
         else
         {

+ 3 - 3
frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/Fortune.cs → frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/FortuneUtf16.cs

@@ -3,9 +3,9 @@
 
 namespace PlatformBenchmarks;
 
-public readonly struct Fortune : IComparable<Fortune>, IComparable
+public readonly struct FortuneUtf16 : IComparable<FortuneUtf16>, IComparable
 {
-    public Fortune(int id, string message)
+    public FortuneUtf16(int id, string message)
     {
         Id = id;
         Message = message;
@@ -18,5 +18,5 @@ public readonly struct Fortune : IComparable<Fortune>, IComparable
     public int CompareTo(object obj) => throw new InvalidOperationException("The non-generic CompareTo should not be used");
 
     // Performance critical, using culture insensitive comparison
-    public int CompareTo(Fortune other) => string.CompareOrdinal(Message, other.Message);
+    public int CompareTo(FortuneUtf16 other) => string.CompareOrdinal(Message, other.Message);
 }

+ 22 - 0
frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/FortuneUtf8.cs

@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+namespace PlatformBenchmarks;
+
+public readonly struct FortuneUtf8 : IComparable<FortuneUtf8>, IComparable
+{
+    public FortuneUtf8(int id, byte[] message)
+    {
+        Id = id;
+        Message = message;
+    }
+
+    public int Id { get; }
+
+    public byte[] Message { get; }
+
+    public int CompareTo(object obj) => throw new InvalidOperationException("The non-generic CompareTo should not be used");
+
+    // Performance critical, using culture insensitive comparison
+    public int CompareTo(FortuneUtf8 other) => Message.AsSpan().SequenceCompareTo(other.Message.AsSpan());
+}

+ 4 - 4
frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/Providers/RawDbMySqlConnector.cs

@@ -185,9 +185,9 @@ namespace PlatformBenchmarks
             return results;
         }
 
-        public async Task<List<Fortune>> LoadFortunesRows()
+        public async Task<List<FortuneUtf16>> LoadFortunesRows()
         {
-            var result = new List<Fortune>();
+            var result = new List<FortuneUtf16>();
 
             using (var db = new MySqlConnection(_connectionString))
             {
@@ -202,7 +202,7 @@ namespace PlatformBenchmarks
                         while (await rdr.ReadAsync())
                         {
                             result.Add(
-                                new Fortune
+                                new FortuneUtf16
                                 (
                                     id: rdr.GetInt32(0),
                                     message: rdr.GetString(1)
@@ -212,7 +212,7 @@ namespace PlatformBenchmarks
                 }
             }
 
-            result.Add(new Fortune(id: 0, message: "Additional fortune added at request time." ));
+            result.Add(new FortuneUtf16(id: 0, message: "Additional fortune added at request time." ));
             result.Sort();
 
             return result;

+ 112 - 108
frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/Providers/RawDbNpgsql.cs

@@ -3,11 +3,7 @@
 
 #if NPGSQL
 
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
 using Microsoft.Extensions.Caching.Memory;
 using Npgsql;
 
@@ -18,53 +14,63 @@ namespace PlatformBenchmarks
     public sealed class RawDb
     {
         private readonly ConcurrentRandom _random;
-        private readonly string _connectionString;
         private readonly MemoryCache _cache = new(
             new MemoryCacheOptions
             {
                 ExpirationScanFrequency = TimeSpan.FromMinutes(60)
             });
 
+        private readonly NpgsqlDataSource _dataSource;
+
         public RawDb(ConcurrentRandom random, AppSettings appSettings)
         {
             _random = random;
-            _connectionString = appSettings.ConnectionString;
+            _dataSource = NpgsqlDataSource.Create(appSettings.ConnectionString);
         }
 
         public async Task<World> LoadSingleQueryRow()
         {
-            using (var db = new NpgsqlConnection(_connectionString))
-            {
-                await db.OpenAsync();
+            using var db = _dataSource.CreateConnection();
+            await db.OpenAsync();
 
-                var (cmd, _) = CreateReadCommand(db);
-                using (cmd)
-                {
-                    return await ReadSingleRow(cmd);
-                }
-            }
+            var (cmd, _) = CreateReadCommand(db);
+            using var command = cmd;
+            return await ReadSingleRow(cmd);
         }
 
         public async Task<World[]> LoadMultipleQueriesRows(int count)
         {
-            var result = new World[count];
+            var results = new World[count];
+
+            using var connection = await _dataSource.OpenConnectionAsync();
 
-            using (var db = new NpgsqlConnection(_connectionString))
+            using var batch = new NpgsqlBatch(connection)
             {
-                await db.OpenAsync();
+                // Inserts a PG Sync message between each statement in the batch, required for compliance with
+                // TechEmpower general test requirement 7
+                // https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview
+                EnableErrorBarriers = true
+            };
 
-                var (cmd, idParameter) = CreateReadCommand(db);
-                using (cmd)
+            for (var i = 0; i < count; i++)
+            {
+                batch.BatchCommands.Add(new()
                 {
-                    for (int i = 0; i < result.Length; i++)
-                    {
-                        result[i] = await ReadSingleRow(cmd);
-                        idParameter.TypedValue = _random.Next(1, 10001);
-                    }
-                }
+                    CommandText = "SELECT id, randomnumber FROM world WHERE id = $1",
+                    Parameters = { new NpgsqlParameter<int> { TypedValue = _random.Next(1, 10001) } }
+                });
             }
 
-            return result;
+            using var reader = await batch.ExecuteReaderAsync();
+
+            for (var i = 0; i < count; i++)
+            {
+                await reader.ReadAsync();
+                results[i] = new World { Id = reader.GetInt32(0), RandomNumber = reader.GetInt32(1) };
+                await reader.NextResultAsync();
+            }
+
+            return results;
         }
 
         public Task<CachedWorld[]> LoadCachedQueries(int count)
@@ -77,7 +83,7 @@ namespace PlatformBenchmarks
             {
                 var id = random.Next(1, 10001);
                 var key = cacheKeys[id];
-                if (cache.TryGetValue(key, out object cached))
+                if (cache.TryGetValue(key, out var cached))
                 {
                     result[i] = (CachedWorld)cached;
                 }
@@ -91,32 +97,25 @@ namespace PlatformBenchmarks
 
             static async Task<CachedWorld[]> LoadUncachedQueries(int id, int i, int count, RawDb rawdb, CachedWorld[] result)
             {
-                using (var db = new NpgsqlConnection(rawdb._connectionString))
-                {
-                    await db.OpenAsync();
+                using var db = rawdb._dataSource.CreateConnection();
+                await db.OpenAsync();
 
-                    var (cmd, idParameter) = rawdb.CreateReadCommand(db);
-                    using (cmd)
-                    {
-                        Func<ICacheEntry, Task<CachedWorld>> create = async _ =>
-                        {
-                            return await rawdb.ReadSingleRow(cmd);
-                        };
+                var (cmd, idParameter) = rawdb.CreateReadCommand(db);
+                using var command = cmd;
+                async Task<CachedWorld> create(ICacheEntry _) => await ReadSingleRow(cmd);
 
-                        var cacheKeys = _cacheKeys;
-                        var key = cacheKeys[id];
+                var cacheKeys = _cacheKeys;
+                var key = cacheKeys[id];
 
-                        idParameter.TypedValue = id;
+                idParameter.TypedValue = id;
 
-                        for (; i < result.Length; i++)
-                        {
-                            result[i] = await rawdb._cache.GetOrCreateAsync(key, create);
+                for (; i < result.Length; i++)
+                {
+                    result[i] = await rawdb._cache.GetOrCreateAsync(key, create);
 
-                            id = rawdb._random.Next(1, 10001);
-                            idParameter.TypedValue = id;
-                            key = cacheKeys[id];
-                        }
-                    }
+                    id = rawdb._random.Next(1, 10001);
+                    idParameter.TypedValue = id;
+                    key = cacheKeys[id];
                 }
 
                 return result;
@@ -125,21 +124,17 @@ namespace PlatformBenchmarks
 
         public async Task PopulateCache()
         {
-            using (var db = new NpgsqlConnection(_connectionString))
-            {
-                await db.OpenAsync();
+            using var db = _dataSource.CreateConnection();
+            await db.OpenAsync();
 
-                var (cmd, idParameter) = CreateReadCommand(db);
-                using (cmd)
-                {
-                    var cacheKeys = _cacheKeys;
-                    var cache = _cache;
-                    for (var i = 1; i < 10001; i++)
-                    {
-                        idParameter.TypedValue = i;
-                        cache.Set<CachedWorld>(cacheKeys[i], await ReadSingleRow(cmd));
-                    }
-                }
+            var (cmd, idParameter) = CreateReadCommand(db);
+            using var command = cmd;
+            var cacheKeys = _cacheKeys;
+            var cache = _cache;
+            for (var i = 1; i < 10001; i++)
+            {
+                idParameter.TypedValue = i;
+                cache.Set<CachedWorld>(cacheKeys[i], await ReadSingleRow(cmd));
             }
 
             Console.WriteLine("Caching Populated");
@@ -149,70 +144,81 @@ namespace PlatformBenchmarks
         {
             var results = new World[count];
 
-            using (var db = new NpgsqlConnection(_connectionString))
+            using var connection = _dataSource.CreateConnection();
+            await connection.OpenAsync();
+
+            using (var batch = new NpgsqlBatch(connection))
             {
-                await db.OpenAsync();
+                // Inserts a PG Sync message between each statement in the batch, required for compliance with
+                // TechEmpower general test requirement 7
+                // https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview
+                batch.EnableErrorBarriers = true;
 
-                var (queryCmd, queryParameter) = CreateReadCommand(db);
-                using (queryCmd)
+                for (var i = 0; i < count; i++)
                 {
-                    for (int i = 0; i < results.Length; i++)
+                    batch.BatchCommands.Add(new()
                     {
-                        results[i] = await ReadSingleRow(queryCmd);
-                        queryParameter.TypedValue = _random.Next(1, 10001);
-                    }
+                        CommandText = "SELECT id, randomnumber FROM world WHERE id = $1",
+                        Parameters = { new NpgsqlParameter<int> { TypedValue = _random.Next(1, 10001) } }
+                    });
                 }
 
-                using (var updateCmd = new NpgsqlCommand(BatchUpdateString.Query(count), db))
-                {
-                    var ids = BatchUpdateString.Ids;
-                    var randoms = BatchUpdateString.Randoms;
+                using var reader = await batch.ExecuteReaderAsync();
 
-                    for (int i = 0; i < results.Length; i++)
-                    {
-                        var randomNumber = _random.Next(1, 10001);
+                for (var i = 0; i < count; i++)
+                {
+                    await reader.ReadAsync();
+                    results[i] = new World { Id = reader.GetInt32(0), RandomNumber = reader.GetInt32(1) };
+                    await reader.NextResultAsync();
+                }
+            }
 
-                        updateCmd.Parameters.Add(new NpgsqlParameter<int>(parameterName: ids[i], value: results[i].Id));
-                        updateCmd.Parameters.Add(new NpgsqlParameter<int>(parameterName: randoms[i], value: randomNumber));
+            using (var updateCmd = new NpgsqlCommand(BatchUpdateString.Query(count), connection))
+            {
+                for (int i = 0; i < results.Length; i++)
+                {
+                    var randomNumber = _random.Next(1, 10001);
 
-                        results[i].RandomNumber = randomNumber;
-                    }
+                    updateCmd.Parameters.Add(new NpgsqlParameter<int> { TypedValue = results[i].Id });
+                    updateCmd.Parameters.Add(new NpgsqlParameter<int> { TypedValue = randomNumber });
 
-                    await updateCmd.ExecuteNonQueryAsync();
+                    results[i].RandomNumber = randomNumber;
                 }
+
+                await updateCmd.ExecuteNonQueryAsync();
             }
 
             return results;
         }
 
-        public async Task<List<Fortune>> LoadFortunesRows()
+        public async Task<List<FortuneUtf8>> LoadFortunesRows()
         {
-            var result = new List<Fortune>(20);
+            var result = new List<FortuneUtf8>();
 
-            using (var db = new NpgsqlConnection(_connectionString))
+            using (var db = _dataSource.CreateConnection())
             {
                 await db.OpenAsync();
 
-                using (var cmd = new NpgsqlCommand("SELECT id, message FROM fortune", db))
-                using (var rdr = await cmd.ExecuteReaderAsync())
+                using var cmd = new NpgsqlCommand("SELECT id, message FROM fortune", db);
+                using var rdr = await cmd.ExecuteReaderAsync();
+                while (await rdr.ReadAsync())
                 {
-                    while (await rdr.ReadAsync())
-                    {
-                        result.Add(new Fortune
-                        (
-                            id:rdr.GetInt32(0),
-                            message: rdr.GetString(1)
-                        ));
-                    }
+                    result.Add(new FortuneUtf8
+                    (
+                        id:rdr.GetInt32(0),
+                        message: rdr.GetFieldValue<byte[]>(1)
+                    ));
                 }
             }
 
-            result.Add(new Fortune(id: 0, message: "Additional fortune added at request time." ));
+            result.Add(new FortuneUtf8(id: 0, AdditionalFortune));
             result.Sort();
 
             return result;
         }
 
+        private readonly byte[] AdditionalFortune = "Additional fortune added at request time."u8.ToArray();
+
         private (NpgsqlCommand readCmd, NpgsqlParameter<int> idParameter) CreateReadCommand(NpgsqlConnection connection)
         {
             var cmd = new NpgsqlCommand("SELECT id, randomnumber FROM world WHERE id = $1", connection);
@@ -224,18 +230,16 @@ namespace PlatformBenchmarks
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private async Task<World> ReadSingleRow(NpgsqlCommand cmd)
+        private static async Task<World> ReadSingleRow(NpgsqlCommand cmd)
         {
-            using (var rdr = await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.SingleRow))
-            {
-                await rdr.ReadAsync();
+            using var rdr = await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.SingleRow);
+            await rdr.ReadAsync();
 
-                return new World
-                {
-                    Id = rdr.GetInt32(0),
-                    RandomNumber = rdr.GetInt32(1)
-                };
-            }
+            return new World
+            {
+                Id = rdr.GetInt32(0),
+                RandomNumber = rdr.GetInt32(1)
+            };
         }
 
         private static readonly object[] _cacheKeys = Enumerable.Range(0, 10001).Select(i => new CacheKey(i)).ToArray();
@@ -262,4 +266,4 @@ namespace PlatformBenchmarks
     }
 }
 
-#endif
+#endif

+ 1 - 4
frameworks/CSharp/aspnetcore/PlatformBenchmarks/DateHeader.cs

@@ -17,10 +17,7 @@ internal static class DateHeader
     const int suffixLength = 2; // crlf
     const int suffixIndex = dateTimeRLength + prefixLength;
 
-    private static readonly Timer s_timer = new((s) =>
-    {
-        SetDateValues(DateTimeOffset.UtcNow);
-    }, null, 1000, 1000);
+    private static readonly Timer s_timer = new(_ => SetDateValues(DateTimeOffset.UtcNow), null, 1000, 1000);
 
     private static byte[] s_headerBytesMaster = new byte[prefixLength + dateTimeRLength + 2 * suffixLength];
     private static byte[] s_headerBytesScratch = new byte[prefixLength + dateTimeRLength + 2 * suffixLength];

+ 2 - 1
frameworks/CSharp/aspnetcore/PlatformBenchmarks/PlatformBenchmarks.csproj

@@ -22,7 +22,8 @@
   </PropertyGroup>
   
   <ItemGroup>
-    <PackageReference Condition=" '$(DatabaseProvider)' == 'Npgsql' " Include="Npgsql" Version="6.0.0" />
+    <PackageReference Include="RazorSlices" Version="0.3.0" />
+    <PackageReference Condition=" '$(DatabaseProvider)' == 'Npgsql' " Include="Npgsql" Version="7.0.2" />
     <PackageReference Condition=" '$(DatabaseProvider)' == 'MySqlConnector' " Include="MySqlConnector" Version="2.0.0" />
   </ItemGroup>
 

+ 33 - 15
frameworks/CSharp/aspnetcore/PlatformBenchmarks/Program.cs

@@ -14,15 +14,23 @@ public sealed class Program
     {
         Args = args;
 
+#if NPGSQL
+        // This disables SQL parsing/rewriting, which requires using positional parameters and NpgsqlBatch everywhere.
+        // This helps commands where there are no parameters (Fortunes); when there are parameters, their ParameterName
+        // being null already triggers positional parameters and disables parsing)
+        // Note that Dapper and EF aren't yet compatible with this mode.
+        AppContext.SetSwitch("Npgsql.EnableSqlRewriting", false);
+#endif
+
         Console.WriteLine(Encoding.UTF8.GetString(BenchmarkApplication.ApplicationName));
 #if !DATABASE
         Console.WriteLine(Encoding.UTF8.GetString(BenchmarkApplication.Paths.Plaintext));
         Console.WriteLine(Encoding.UTF8.GetString(BenchmarkApplication.Paths.Json));
 #else
-            Console.WriteLine(Encoding.UTF8.GetString(BenchmarkApplication.Paths.Fortunes));
-            Console.WriteLine(Encoding.UTF8.GetString(BenchmarkApplication.Paths.SingleQuery));
-            Console.WriteLine(Encoding.UTF8.GetString(BenchmarkApplication.Paths.Updates));
-            Console.WriteLine(Encoding.UTF8.GetString(BenchmarkApplication.Paths.MultipleQueries));
+        Console.WriteLine(Encoding.UTF8.GetString(BenchmarkApplication.Paths.Fortunes));
+        Console.WriteLine(Encoding.UTF8.GetString(BenchmarkApplication.Paths.SingleQuery));
+        Console.WriteLine(Encoding.UTF8.GetString(BenchmarkApplication.Paths.Updates));
+        Console.WriteLine(Encoding.UTF8.GetString(BenchmarkApplication.Paths.MultipleQueries));
 #endif
         DateHeader.SyncDateTimer();
 
@@ -30,7 +38,14 @@ public sealed class Program
         var config = (IConfiguration)host.Services.GetService(typeof(IConfiguration));
         BatchUpdateString.DatabaseServer = config.Get<AppSettings>().Database;
 #if DATABASE
+        try
+        {
             await BenchmarkApplication.Db.PopulateCache();
+        }
+        catch (Exception ex)
+        {
+            Console.WriteLine($"Error trying to populate database cache: {ex}");
+        }
 #endif
         await host.RunAsync();
     }
@@ -39,6 +54,9 @@ public sealed class Program
     {
         var config = new ConfigurationBuilder()
             .AddJsonFile("appsettings.json")
+#if DEBUG
+            .AddUserSecrets<Program>()
+#endif
             .AddEnvironmentVariables()
             .AddEnvironmentVariables(prefix: "ASPNETCORE_")
             .AddCommandLine(args)
@@ -46,18 +64,18 @@ public sealed class Program
 
         var appSettings = config.Get<AppSettings>();
 #if DATABASE
-            Console.WriteLine($"Database: {appSettings.Database}");
-            Console.WriteLine($"ConnectionString: {appSettings.ConnectionString}");
+        Console.WriteLine($"Database: {appSettings.Database}");
+        Console.WriteLine($"ConnectionString: {appSettings.ConnectionString}");
 
-            if (appSettings.Database is DatabaseServer.PostgreSql
-                                     or DatabaseServer.MySql)
-            {
-                BenchmarkApplication.Db = new RawDb(new ConcurrentRandom(), appSettings);
-            }
-            else
-            {
-                throw new NotSupportedException($"{appSettings.Database} is not supported");
-            }
+        if (appSettings.Database is DatabaseServer.PostgreSql
+                                 or DatabaseServer.MySql)
+        {
+            BenchmarkApplication.Db = new RawDb(new ConcurrentRandom(), appSettings);
+        }
+        else
+        {
+            throw new NotSupportedException($"{appSettings.Database} is not supported");
+        }
 #endif
 
         var hostBuilder = new WebHostBuilder()

+ 2 - 0
frameworks/CSharp/aspnetcore/PlatformBenchmarks/Templates/FortunesUtf16.cshtml

@@ -0,0 +1,2 @@
+@inherits RazorSlice<List<FortuneUtf16>>
+<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>@foreach (var item in Model){<tr><td>@item.Id</td><td>@item.Message</td></tr>}</table></body></html>

+ 2 - 0
frameworks/CSharp/aspnetcore/PlatformBenchmarks/Templates/FortunesUtf8.cshtml

@@ -0,0 +1,2 @@
+@inherits RazorSlice<List<FortuneUtf8>>
+<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>@foreach (var item in Model){<tr><td>@item.Id</td><td>@item.Message</td></tr>}</table></body></html>

+ 9 - 0
frameworks/CSharp/aspnetcore/PlatformBenchmarks/Templates/_ViewImports.cshtml

@@ -0,0 +1,9 @@
+@inherits RazorSlice
+
+@using System.Globalization;
+@using Microsoft.AspNetCore.Razor;
+@using RazorSlices;
+@using PlatformBenchmarks;
+
+@tagHelperPrefix __disable_tagHelpers__:
+@removeTagHelper *, Microsoft.AspNetCore.Mvc.Razor

+ 2 - 1
frameworks/CSharp/aspnetcore/PlatformBenchmarks/appsettings.json

@@ -1,3 +1,4 @@
 {
-  "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnetcore-Benchmarks;Trusted_Connection=True;MultipleActiveResultSets=true"
+  "ConnectionString": "Server=localhost;Database=fortunes;User Id=test;Password=test",
+  "Database": "PostgreSQL"
 }

+ 2 - 1
frameworks/CSharp/evhttp-sharp/benchmark_config.json

@@ -18,7 +18,8 @@
       "database_os": "Linux",
       "display_name": "evhttp-sharp",
       "notes": "",
-      "versus": "evhttp-sharp"
+      "versus": "evhttp-sharp",
+      "tags": ["broken"]
     }
   }]
 }

+ 2 - 1
frameworks/CSharp/watson/benchmark_config.json

@@ -16,7 +16,8 @@
       "os": "Linux",
       "database_os": "Linux",
       "display_name": "Watson Webserver",
-      "notes": ""
+      "notes": "",
+      "tags": ["broken"]
     }
   }]
 }

+ 3 - 3
frameworks/Clojure/aleph/README.md

@@ -6,9 +6,9 @@ This is the [Aleph](https://github.com/clj-commons/aleph) portion of a [benchmar
 The dependencies are documented in [project.clj](project.clj),
 but the main ones are:
 
-* [Aleph 0.4.7](https://github.com/clj-commons/aleph)
-* [Clojure 1.11.0](http://clojure.org/)
-* [metosin/jsonista 0.3.5](https://github.com/metosin/jsonista), which in turn uses [Jackson](http://jackson.codehaus.org/)
+* [Aleph 0.6.1](https://github.com/clj-commons/aleph)
+* [Clojure 1.11.1](http://clojure.org/)
+* [metosin/jsonista 0.3.7](https://github.com/metosin/jsonista), which in turn uses [Jackson](http://jackson.codehaus.org/)
 * [hiccup 1.0.5](https://github.com/weavejester/hiccup)
 * [porsas 0.0.1-alpha14](https://github.com/arnaudgeiser/porsas)
 

+ 3 - 3
frameworks/Clojure/aleph/aleph.dockerfile

@@ -1,4 +1,4 @@
-FROM clojure:openjdk-17-lein-2.9.8
+FROM clojure:temurin-19-lein
 WORKDIR /aleph
 COPY src src
 COPY project.clj project.clj
@@ -14,7 +14,7 @@ EXPOSE 9999
 RUN apt update -y
 RUN apt install perl -y
 
-CMD ["java", "-server", "-Xms2G", "-Xmx2G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dvertx.disableMetrics=true", "-Dvertx.threadChecks=false", "-Dvertx.disableContextTimings=true", "-Dvertx.disableTCCL=true", "-Djava.net.preferIPv4Stack=true", "-jar", "target/hello-aleph-standalone.jar"]
+CMD ["java", "-server", "-Xms2G", "-Xmx2G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Djava.net.preferIPv4Stack=true", "-Dio.netty.leakDetection.level=disabled", "-jar", "target/hello-aleph-standalone.jar"]
 
 # To enable JMX and async-profiler
-#CMD ["java", "-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints", "-Djdk.attach.allowAttachSelf", "-Dcom.sun.management.jmxremote=true", "-Djava.rmi.server.hostname=0.0.0.0","-Dcom.sun.management.jmxremote.rmi.port=9999" ,"-Dcom.sun.management.jmxremote.port=9999", "-Dcom.sun.management.jmxremote.ssl=false", "-Dcom.sun.management.jmxremote.authenticate=false", "-server", "-Xms2G", "-Xmx2G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dvertx.disableMetrics=true", "-Dvertx.threadChecks=false", "-Dvertx.disableContextTimings=true", "-Dvertx.disableTCCL=true", "-Djava.net.preferIPv4Stack=true", "-jar", "target/hello-aleph-standalone.jar"]
+#CMD ["java", "-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints", "-Djdk.attach.allowAttachSelf", "-Dcom.sun.management.jmxremote=true", "-Djava.rmi.server.hostname=0.0.0.0","-Dcom.sun.management.jmxremote.rmi.port=9999" ,"-Dcom.sun.management.jmxremote.port=9999", "-Dcom.sun.management.jmxremote.ssl=false", "-Dcom.sun.management.jmxremote.authenticate=false", "-server", "-Xms2G", "-Xmx2G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Djava.net.preferIPv4Stack=true", "-jar", "target/hello-aleph-standalone.jar"]

+ 5 - 6
frameworks/Clojure/aleph/project.clj

@@ -1,10 +1,9 @@
 (defproject hello "aleph"
-  :description "JSON/plaintext tests"
-  :dependencies [[org.clojure/clojure "1.11.0"]
-                 [aleph "0.4.7"]
-                 [metosin/jsonista "0.3.5"]
+  :description "Aleph benchmarks"
+  :dependencies [[org.clojure/clojure "1.11.1"]
+                 [aleph "0.6.1"]
+                 [metosin/jsonista "0.3.7"]
                  [hiccup "1.0.5"]
-                 [io.netty/netty-transport-native-epoll "4.1.65.Final" :classifier "linux-x86_64"]
                  [com.github.arnaudgeiser/porsas "0.0.1-alpha14"
                   :exclusions [io.netty/netty-codec-dns
                                io.netty/netty-codec
@@ -18,7 +17,7 @@
                                io.netty/netty-transport
                                io.netty/netty-resolver-dns
                                io.netty/netty-resolver]]
-                 [com.clojure-goes-fast/clj-async-profiler "0.5.1"]]
+                 [com.clojure-goes-fast/clj-async-profiler "1.0.3"]]
   :main hello.handler
   :jvm-opts ^:replace ["-Dclojure.compiler.direct-linking=true"]
   :aot :all)

+ 4 - 2
frameworks/Clojure/duct/benchmark_config.json

@@ -42,7 +42,8 @@
         "database_os": "Linux",
         "display_name": "duct-mongodb",
         "notes": "",
-        "versus": "None"
+        "versus": "None",
+        "tags": ["broken"]
       },
       "httpkit": {
         "json_url": "/json",
@@ -108,7 +109,8 @@
         "database_os": "Linux",
         "display_name": "duct-immutant",
         "notes": "",
-        "versus": "None"
+        "versus": "None",
+        "tags": ["broken"]
       }
     }
   ]

+ 1 - 1
frameworks/Crystal/spider-gazelle/config.toml

@@ -16,4 +16,4 @@ os = "Linux"
 orm = "Full"
 platform = "None"
 webserver = "None"
-versus = "amber"
+versus = "lucky"

+ 5 - 0
frameworks/Crystal/spider-gazelle/run.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+bin/app -w $(nproc --all) -b 0.0.0.0 -p 8080
+
+wait

+ 70 - 0
frameworks/Crystal/spider-gazelle/shard.lock

@@ -0,0 +1,70 @@
+version: 2.0
+shards:
+  action-controller:
+    git: https://github.com/spider-gazelle/action-controller.git
+    version: 5.6.2
+
+  active-model:
+    git: https://github.com/spider-gazelle/active-model.git
+    version: 4.2.3
+
+  backtracer:
+    git: https://github.com/sija/backtracer.cr.git
+    version: 1.2.2
+
+  db:
+    git: https://github.com/crystal-lang/crystal-db.git
+    version: 0.11.0
+
+  eventbus:
+    git: https://github.com/spider-gazelle/eventbus.git
+    version: 0.9.9+git.commit.086b2ba92475b88e8481b0387eb56c735cbfd7bd
+
+  exception_page:
+    git: https://github.com/crystal-loot/exception_page.git
+    version: 0.3.0
+
+  future:
+    git: https://github.com/crystal-community/future.cr.git
+    version: 1.0.0
+
+  habitat:
+    git: https://github.com/luckyframework/habitat.git
+    version: 0.4.7
+
+  hot_topic:
+    git: https://github.com/jgaskins/hot_topic.git
+    version: 0.1.0+git.commit.c4577d949221d535f29162343bf503b578308954
+
+  http-params-serializable:
+    git: https://github.com/place-labs/http-params-serializable.git
+    version: 0.5.0
+
+  json-schema:
+    git: https://github.com/spider-gazelle/json-schema.git
+    version: 1.3.0
+
+  kilt:
+    git: https://github.com/jeromegn/kilt.git
+    version: 0.6.1
+
+  lucky_router:
+    git: https://github.com/luckyframework/lucky_router.git
+    version: 0.5.2
+
+  pg:
+    git: https://github.com/will/crystal-pg.git
+    version: 0.26.0
+
+  pg-orm:
+    git: https://github.com/spider-gazelle/pg-orm.git
+    version: 1.0.0+git.commit.2bbafec9579f175880281279d33168360176540c
+
+  pool:
+    git: https://github.com/ysbaddaden/pool.git
+    version: 0.3.0
+
+  redis:
+    git: https://github.com/stefanwille/crystal-redis.git
+    version: 2.8.3
+

+ 2 - 14
frameworks/Crystal/spider-gazelle/shard.yml

@@ -4,26 +4,14 @@ version: 1.0.0
 dependencies:
   action-controller:
     github: spider-gazelle/action-controller
-    version: "1.4.2"
 
-  granite:
-    github: amberframework/granite
-    version: "0.15.0"
+  pg-orm:
+    github: spider-gazelle/pg-orm
 
   # https://github.com/jeromegn/kilt
   # Generic template interface for Crystal
   kilt:
     github: jeromegn/kilt
-    version: "0.4.0"
-
-  pg:
-    github: will/crystal-pg
-    version: "0.15.0"
-
-development_dependencies:
-  ameba:
-    github: veelenga/ameba
-    version: "0.8.1"
 
 # compile target
 targets:

+ 9 - 3
frameworks/Crystal/spider-gazelle/spider-gazelle.dockerfile

@@ -1,15 +1,21 @@
-FROM crystallang/crystal:0.27.0
+FROM 84codes/crystal:1.7.2-alpine
+RUN apk add --update --no-cache bash gmp-dev
+
 WORKDIR /usr/src/app
 
 COPY shard.yml ./
 COPY src src
+COPY run.sh run.sh
 
 # Build App
 RUN shards build --release --no-debug
 
-ENV DATABASE_URL postgres://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world?initial_pool_size=56&max_pool_size=56&max_idle_pool_size=56
+ENV DATABASE_URL postgres://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world?initial_pool_size=56&max_idle_pool_size=56
+
 ENV SG_ENV production
 
 # Run the app binding on port 8080
 EXPOSE 8080
-CMD bin/app -w $(nproc) -b 0.0.0.0 -p 8080
+ENTRYPOINT []
+
+CMD bash run.sh

+ 4 - 4
frameworks/Crystal/spider-gazelle/src/config.cr

@@ -1,9 +1,6 @@
 # Application dependencies
 require "action-controller"
-
-require "granite/adapter/pg"
-Granite.settings.logger = Logger.new(nil)
-Granite::Adapters << Granite::Adapter::Pg.new({name: "pg", url: ENV["DATABASE_URL"]})
+require "pg-orm"
 
 # Application code
 require "./controllers/application"
@@ -13,6 +10,9 @@ require "./models/*"
 # Server required after application controllers
 require "action-controller/server"
 
+# Configure PG Database connection
+PgORM::Database.parse(ENV["DATABASE_URL"])
+
 # Configure session cookies
 # NOTE:: Change these from defaults
 ActionController::Session.configure do |settings|

+ 1 - 1
frameworks/Crystal/spider-gazelle/src/controllers/application.cr

@@ -6,6 +6,6 @@ abstract class Application < ActionController::Base
 
   def set_date_header
     response.headers["Server"] = "Spider-Gazelle"
-    response.headers["Date"] = HTTP.format_time(Time.now)
+    response.headers["Date"] = HTTP.format_time(Time.local)
   end
 end

+ 15 - 21
frameworks/Crystal/spider-gazelle/src/controllers/benchmark.cr

@@ -21,33 +21,31 @@ class Benchmark < Application
 
   # Postgres Test 2: Single database query
   get "/db", :db do
-    results = {} of Symbol => Int32
-    if world = World.find(Random.rand(ID_MAXIMUM).succ)
-      results = {id: world.id, randomNumber: world.randomnumber}
-    end
-
+    world = World.find(Random.rand(1..ID_MAXIMUM))
+    results = {id: world.id, randomNumber: world.randomnumber}
     render json: results
   end
 
   # Postgres Test 3: Multiple database query
   get "/queries", :queries do
     results = (1..get_query_count).map do
-      if world = World.find(Random.rand(ID_MAXIMUM).succ)
-        {id: world.id, randomNumber: world.randomnumber}
-      end
+      world = World.find(Random.rand(1..ID_MAXIMUM))
+      {id: world.id, randomNumber: world.randomnumber}
     end
-
     render json: results
   end
 
   # Postgres Test 5: Database Updates
   get "/updates", :updates do
     results = (1..get_query_count).map do
-      if world = World.find(Random.rand(ID_MAXIMUM).succ)
-        world.randomnumber = Random.rand(ID_MAXIMUM).succ
-        world.save
-        {id: world.id, randomNumber: world.randomnumber}
+      world = World.find(Random.rand(1..ID_MAXIMUM))
+      random_number = Random.rand(1..ID_MAXIMUM)
+      while random_number == world.randomnumber
+        random_number = Random.rand(1..ID_MAXIMUM)
       end
+      world.randomnumber = random_number
+      world.save!
+      {id: world.id, randomNumber: random_number}
     end
 
     render json: results
@@ -55,16 +53,12 @@ class Benchmark < Application
 
   # Postgres Test 4: Fortunes
   FORTUNE_MESSAGE = "Additional fortune added at request time."
-  FORTUNE_CTYPE = "text/html; charset=UTF-8"
+  FORTUNE_CTYPE   = "text/html; charset=UTF-8"
 
   get "/fortunes", :fortunes do
-    fortune = Fortune.new
-    fortune.id = 0
-    fortune.message = FORTUNE_MESSAGE
-
-    fortunes = Fortune.all
-    fortunes << fortune
-    fortunes.sort_by! { |fortune| fortune.message || "" }
+    fortunes = Fortune.all.to_a
+    fortunes << Fortune.new(id: 0, message: FORTUNE_MESSAGE)
+    fortunes.sort_by!(&.message)
 
     # by default this would have been returned as text/html
     response.content_type = FORTUNE_CTYPE

+ 4 - 7
frameworks/Crystal/spider-gazelle/src/models/fortune.cr

@@ -1,9 +1,6 @@
-require "granite/adapter/pg"
+require "pg-orm"
 
-class Fortune < Granite::Base
-  adapter pg
-
-  table_name fortune
-  primary id : Int32
-  field message : String
+class Fortune < PgORM::Base
+  attribute id : Int32, primary_key: true
+  attribute message : String
 end

+ 4 - 7
frameworks/Crystal/spider-gazelle/src/models/world.cr

@@ -1,9 +1,6 @@
-require "granite/adapter/pg"
+require "pg-orm"
 
-class World < Granite::Base
-  adapter pg
-
-  table_name world
-  primary id : Int32
-  field randomnumber : Int32
+class World < PgORM::Base
+  attribute id : Int32, primary_key: true
+  attribute randomnumber : Int32
 end

+ 4 - 2
frameworks/D/vibed/benchmark_config.json

@@ -22,7 +22,8 @@
       "database_os": "Linux",
       "display_name": "vibe.d",
       "notes": "",
-      "versus": "vibed"
+      "versus": "vibed",
+      "tags": ["broken"]
     },
     "dmd-pgsql": {
       "db_url": "/db",
@@ -66,7 +67,8 @@
       "database_os": "Linux",
       "display_name": "vibe.d-ldc",
       "notes": "",
-      "versus": "vibed"
+      "versus": "vibed",
+      "tags": ["broken"]
     },
     "ldc-pgsql": {
       "db_url": "/db",

+ 1 - 1
frameworks/Dart/angel3/angel3-mysql.dockerfile

@@ -1,4 +1,4 @@
-FROM dart:2.18.1
+FROM dart:2.19.6
 
 COPY ./orm-mysql/config /app/config
 COPY ./orm-mysql/lib /app/lib

+ 1 - 1
frameworks/Dart/angel3/angel3.dockerfile

@@ -1,4 +1,4 @@
-FROM dart:2.18.1
+FROM dart:2.19.6
 
 COPY ./orm/config /app/config
 COPY ./orm/lib /app/lib

+ 2 - 2
frameworks/Dart/angel3/benchmark_config.json

@@ -21,7 +21,7 @@
         "webserver": "None",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Angel3",
+        "display_name": "Angel3_postgresql",
         "notes": "",
         "versus": "None"
       },
@@ -43,7 +43,7 @@
         "webserver": "None",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Angel3",
+        "display_name": "Angel3_mysql",
         "notes": "",
         "versus": "None"
       }

+ 2 - 1
frameworks/Dart/start/benchmark_config.json

@@ -20,7 +20,8 @@
       "database_os": "Linux",
       "display_name": "start",
       "notes": "",
-      "versus": "dart"
+      "versus": "dart",
+      "tags": ["broken"]
     }
   }]
 }

+ 2 - 1
frameworks/Dart/stream/benchmark_config.json

@@ -20,7 +20,8 @@
        "database_os": "Linux",
        "display_name": "stream",
        "notes": "",
-       "versus": "dart"
+       "versus": "dart",
+       "tags": ["broken"]
      }
   }]
 }

+ 1 - 1
frameworks/Elixir/phoenix/config/bandit.exs

@@ -2,7 +2,7 @@ import Config
 
 config :hello, HelloWeb.Endpoint,
   adapter: Bandit.PhoenixAdapter,
-  http: [port: 8080, ip: {0, 0, 0, 0}, transport_options: [backlog: 8096]],
+  http: [port: 8080, ip: {0, 0, 0, 0}],
   cache_static_lookup: false,
   check_orgin: false,
   debug_errors: false,

+ 65 - 26
frameworks/Elixir/phoenix/lib/hello_web.ex

@@ -1,65 +1,104 @@
 defmodule HelloWeb do
   @moduledoc """
-  A module that keeps using definitions for controllers,
-  views and so on.
+  The entrypoint for defining your web interface, such
+  as controllers, views, channels and so on.
 
   This can be used in your application as:
 
       use HelloWeb, :controller
-      use HelloWeb, :view
+      use HelloWeb, :html
 
-  The definitions below will be executed for every view,
-  controller, etc, so keep them short and clean, focused
+  The definitions below will be executed for every controller,
+  component, etc, so keep them short and clean, focused
   on imports, uses and aliases.
 
   Do NOT define functions inside the quoted expressions
-  below. Instead, define any helper function in modules
-  and import those modules here.
+  below. Instead, define additional modules and import
+  those modules here.
   """
 
+  def static_paths,
+    do: ~w(assets favicon.svg apple-touch-icon.png robots.txt font-files mask-icon.svg)
+
   def controller do
     quote do
-      use Phoenix.Controller, namespace: HelloWeb, log: false
+      use Phoenix.Controller,
+        namespace: HelloWeb,
+        formats: [:html, :json],
+        layouts: [html: HelloWeb.Layouts],
+        log: false
+
+      import Plug.Conn
+      import HelloWeb.Gettext
+
+      unquote(verified_routes())
+    end
+  end
+
+  def component do
+    quote do
+      use Phoenix.Component
 
-      # Alias the data repository and import query/model functions
-      alias Hello.Repo
-      import Ecto
-      import Ecto.Query
+      import HelloWeb.Gettext
 
-      # Import URL helpers from the router
-      import HelloWeb.Router.Helpers
+      # Routes generation with the ~p sigil
+      unquote(verified_routes())
     end
   end
 
-  def view do
+  def html do
     quote do
-      use Phoenix.View,
-        root: "lib/hello_web/templates",
-        namespace: HelloWeb
+      use Phoenix.Component
+
+      # Import convenience functions from controllers
+      import Phoenix.Controller,
+        only: [get_csrf_token: 0, view_module: 1, view_template: 1]
 
-      alias HelloWeb.Router.Helpers, as: Routes
+      # Include general helpers for rendering HTML
+      unquote(html_helpers())
+    end
+  end
+
+  defp html_helpers do
+    quote do
+      # Use all HTML functionality (forms, tags, etc)
+      use Phoenix.HTML
+      # Core UI Components and translation
+      import HelloWeb.Gettext
+
+      # Routes generation with the ~p sigil
+      unquote(verified_routes())
+    end
+  end
+
+  def verified_routes do
+    quote do
+      use Phoenix.VerifiedRoutes,
+        endpoint: HelloWeb.Endpoint,
+        router: HelloWeb.Router,
+        statics: HelloWeb.static_paths()
     end
   end
 
   def router do
     quote do
-      use Phoenix.Router
+      use Phoenix.Router, helpers: false
+
+      # Import common connection and controller functions to use in pipelines
+      import Plug.Conn
+      import Phoenix.Controller
     end
   end
 
   def channel do
     quote do
       use Phoenix.Channel
-      # Alias the data repository and import query/model functions
-      alias Hello.Repo
-      import Ecto
-      import Ecto.Query
     end
   end
 
   @doc """
-  When used, dispatch to the appropriate controller/view/etc.
-  """
+    When used, dispatch to the appropriate controller/view/etc.
+    """
   defmacro __using__(which) when is_atom(which) do
     apply(__MODULE__, which, [])
   end

+ 2 - 2
frameworks/Elixir/phoenix/lib/hello_web/views/error_view.ex → frameworks/Elixir/phoenix/lib/hello_web/controllers/error_html.ex

@@ -1,5 +1,5 @@
-defmodule HelloWeb.ErrorView do
-  use HelloWeb, :view
+defmodule HelloWeb.ErrorHTML do
+  use HelloWeb, :html
 
   def render("404.html", _assigns) do
     "Page not found - 404"

+ 19 - 0
frameworks/Elixir/phoenix/lib/hello_web/controllers/error_json.ex

@@ -0,0 +1,19 @@
+defmodule HelloWeb.ErrorJSON do
+  @moduledoc """
+  JSON error pages
+  """
+
+  # If you want to customize a particular status code,
+  # you may add your own clauses, such as:
+  #
+  # def render("500.json", _assigns) do
+  #   %{errors: %{detail: "Internal Server Error"}}
+  # end
+
+  # By default, Phoenix returns the status message from
+  # the template name. For example, "404.json" becomes
+  # "Not Found".
+  def render(template, _assigns) do
+    %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
+  end
+end

+ 2 - 1
frameworks/Elixir/phoenix/lib/hello_web/controllers/page_controller.ex

@@ -3,6 +3,7 @@ defmodule HelloWeb.PageController do
 
   use HelloWeb, :controller
 
+  alias Hello.Repo
   alias Hello.Cache
 
   @json "application/json"
@@ -59,7 +60,7 @@ defmodule HelloWeb.PageController do
 
     fortunes = [additional_fortune | Repo.all(Fortune)]
 
-    render(conn, "fortunes.html",
+    render(conn, :fortunes,
       fortunes: Enum.sort(fortunes, fn f1, f2 -> f1.message < f2.message end)
     )
   end

+ 9 - 0
frameworks/Elixir/phoenix/lib/hello_web/controllers/page_html.ex

@@ -0,0 +1,9 @@
+defmodule HelloWeb.PageHTML do
+  use HelloWeb, :html
+
+  @moduledoc """
+  Standard Page HTML Helper
+  """
+
+  embed_templates "page_html/*"
+end

+ 0 - 0
frameworks/Elixir/phoenix/lib/hello_web/templates/page/fortunes.html.eex → frameworks/Elixir/phoenix/lib/hello_web/controllers/page_html/fortunes.html.eex


+ 24 - 0
frameworks/Elixir/phoenix/lib/hello_web/gettext.ex

@@ -0,0 +1,24 @@
+defmodule HelloWeb.Gettext do
+  @moduledoc """
+  A module providing Internationalization with a gettext-based API.
+
+  By using [Gettext](https://hexdocs.pm/gettext),
+  your module gains a set of macros for translations, for example:
+
+      import HelloWeb.Gettext
+
+      # Simple translation
+      gettext("Here is the string to translate")
+
+      # Plural translation
+      ngettext("Here is the string to translate",
+               "Here are the strings to translate",
+               3)
+
+      # Domain-based translation
+      dgettext("errors", "Here is the error message to translate")
+
+  See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
+  """
+  use Gettext, otp_app: :hello
+end

+ 7 - 0
frameworks/Elixir/phoenix/lib/hello_web/layouts.ex

@@ -0,0 +1,7 @@
+defmodule HelloWeb.Layouts do
+  use HelloWeb, :html
+
+  @moduledoc false
+
+  embed_templates "layouts/*"
+end

Неке датотеке нису приказане због велике количине промена