Эх сурвалжийг харах

Merge branch 'round-14' of https://github.com/TechEmpower/FrameworkBenchmarks into round-14

Keith Newman 9 жил өмнө
parent
commit
1d1e20a609
100 өөрчлөгдсөн 5834 нэмэгдсэн , 544 устгасан
  1. 5 0
      .travis.yml
  2. 29 0
      frameworks/C++/cutelyst/CMakeLists.txt
  3. 127 0
      frameworks/C++/cutelyst/benchmark_config.json
  4. 21 0
      frameworks/C++/cutelyst/config/config.ini
  5. 23 0
      frameworks/C++/cutelyst/config/config_socket.ini
  6. 48 0
      frameworks/C++/cutelyst/nginx.conf
  7. 22 0
      frameworks/C++/cutelyst/setup.sh
  8. 25 0
      frameworks/C++/cutelyst/setup_uwsgi_nginx.sh
  9. 20 0
      frameworks/C++/cutelyst/source_code
  10. 19 0
      frameworks/C++/cutelyst/src/CMakeLists.txt
  11. 78 0
      frameworks/C++/cutelyst/src/cutelyst-benchmarks.cpp
  12. 22 0
      frameworks/C++/cutelyst/src/cutelyst-benchmarks.h
  13. 74 0
      frameworks/C++/cutelyst/src/databaseupdatestest.cpp
  14. 26 0
      frameworks/C++/cutelyst/src/databaseupdatestest.h
  15. 90 0
      frameworks/C++/cutelyst/src/fortunetest.cpp
  16. 32 0
      frameworks/C++/cutelyst/src/fortunetest.h
  17. 17 0
      frameworks/C++/cutelyst/src/jsontest.cpp
  18. 19 0
      frameworks/C++/cutelyst/src/jsontest.h
  19. 60 0
      frameworks/C++/cutelyst/src/multipledatabasequeriestest.cpp
  20. 26 0
      frameworks/C++/cutelyst/src/multipledatabasequeriestest.h
  21. 13 0
      frameworks/C++/cutelyst/src/plaintexttest.cpp
  22. 19 0
      frameworks/C++/cutelyst/src/plaintexttest.h
  23. 37 0
      frameworks/C++/cutelyst/src/root.cpp
  24. 22 0
      frameworks/C++/cutelyst/src/root.h
  25. 47 0
      frameworks/C++/cutelyst/src/singledatabasequerytest.cpp
  26. 26 0
      frameworks/C++/cutelyst/src/singledatabasequerytest.h
  27. 3 0
      frameworks/C++/ulib/README.md
  28. 55 1
      frameworks/C++/ulib/benchmark_config.json
  29. 24 0
      frameworks/C++/ulib/setup_elasticsearch.sh
  30. 2 4
      frameworks/C++/ulib/setup_json.sh
  31. 28 0
      frameworks/C++/ulib/setup_json_extra.sh
  32. 23 0
      frameworks/C++/ulib/setup_json_large.sh
  33. 28 0
      frameworks/C++/ulib/setup_json_medium.sh
  34. 3 0
      frameworks/C++/ulib/source_code
  35. 5 5
      frameworks/C++/ulib/src/db.usp
  36. 68 0
      frameworks/C++/ulib/src/edb.usp
  37. 102 0
      frameworks/C++/ulib/src/equery.usp
  38. 108 0
      frameworks/C++/ulib/src/eupdate.usp
  39. 16 8
      frameworks/C++/ulib/src/fortune.usp
  40. 2 2
      frameworks/C++/ulib/src/json.usp
  41. 25 36
      frameworks/C++/ulib/src/mdb.usp
  42. 48 44
      frameworks/C++/ulib/src/mfortune.usp
  43. 38 41
      frameworks/C++/ulib/src/mquery.usp
  44. 17 16
      frameworks/C++/ulib/src/mupdate.usp
  45. 11 7
      frameworks/C++/ulib/src/query.usp
  46. 22 31
      frameworks/C++/ulib/src/rdb.usp
  47. 14 6
      frameworks/C++/ulib/src/rfortune.usp
  48. 36 33
      frameworks/C++/ulib/src/rquery.usp
  49. 38 35
      frameworks/C++/ulib/src/rupdate.usp
  50. 13 11
      frameworks/C++/ulib/src/update.usp
  51. 2 4
      frameworks/C++/ulib/src/world.h
  52. 23 0
      frameworks/C/h2o/CMakeLists.txt
  53. 11 0
      frameworks/C/h2o/README.md
  54. 27 0
      frameworks/C/h2o/benchmark_config.json
  55. 57 0
      frameworks/C/h2o/setup.sh
  56. 23 0
      frameworks/C/h2o/source_code
  57. 60 0
      frameworks/C/h2o/src/bitset.h
  58. 518 0
      frameworks/C/h2o/src/database.c
  59. 88 0
      frameworks/C/h2o/src/database.h
  60. 66 0
      frameworks/C/h2o/src/error.c
  61. 85 0
      frameworks/C/h2o/src/error.h
  62. 205 0
      frameworks/C/h2o/src/event_loop.c
  63. 52 0
      frameworks/C/h2o/src/event_loop.h
  64. 422 0
      frameworks/C/h2o/src/fortune.c
  65. 38 0
      frameworks/C/h2o/src/fortune.h
  66. 35 0
      frameworks/C/h2o/src/list.h
  67. 366 0
      frameworks/C/h2o/src/main.c
  68. 255 0
      frameworks/C/h2o/src/request_handler.c
  69. 51 0
      frameworks/C/h2o/src/request_handler.h
  70. 138 0
      frameworks/C/h2o/src/template.c
  71. 28 0
      frameworks/C/h2o/src/template.h
  72. 107 0
      frameworks/C/h2o/src/thread.c
  73. 56 0
      frameworks/C/h2o/src/thread.h
  74. 169 0
      frameworks/C/h2o/src/tls.c
  75. 29 0
      frameworks/C/h2o/src/tls.h
  76. 95 0
      frameworks/C/h2o/src/utility.c
  77. 73 0
      frameworks/C/h2o/src/utility.h
  78. 497 0
      frameworks/C/h2o/src/world.c
  79. 30 0
      frameworks/C/h2o/src/world.h
  80. 10 0
      frameworks/C/h2o/template/fortunes.mustache
  81. 1 1
      frameworks/D/vibed/README.md
  82. 23 0
      frameworks/D/vibed/benchmark_config.json
  83. 3 4
      frameworks/D/vibed/dub.selections.json
  84. 11 0
      frameworks/D/vibed/setup_ldc.sh
  85. 1 1
      frameworks/D/vibed/source/mongodb.d
  86. 2 5
      frameworks/Go/beego/src/hello/hello.go
  87. 8 8
      frameworks/Go/echo/README.md
  88. 50 4
      frameworks/Go/echo/benchmark_config.json
  89. 0 20
      frameworks/Go/echo/fortune.html
  90. 0 204
      frameworks/Go/echo/server.go
  91. 4 5
      frameworks/Go/echo/setup.sh
  92. 9 0
      frameworks/Go/echo/setup_prefork.sh
  93. 9 0
      frameworks/Go/echo/setup_std.sh
  94. 3 0
      frameworks/Go/echo/source_code
  95. 293 0
      frameworks/Go/echo/src/common/common.go
  96. 69 0
      frameworks/Go/echo/src/fasthttp/main.go
  97. 16 0
      frameworks/Go/echo/src/standard/main.go
  98. 5 8
      frameworks/Go/falcore/src/framework_benchmarks/falcore.go
  99. 5 0
      frameworks/Haskell/servant/ChangeLog.md
  100. 30 0
      frameworks/Haskell/servant/LICENSE

+ 5 - 0
.travis.yml

@@ -20,6 +20,7 @@ env:
     - "TESTDIR=C/duda"
     - "TESTDIR=C/haywire"
     - "TESTDIR=C/onion"
+    - "TESTDIR=C/h2o"
     - "TESTDIR=CSharp/aspnet"
     ## - "TESTDIR=CSharp/aspnet-stripped"
     - "TESTDIR=CSharp/evhttp-sharp"
@@ -28,6 +29,7 @@ env:
     - "TESTDIR=CSharp/revenj"
     - "TESTDIR=CSharp/servicestack"
     - "TESTDIR=C++/cpoll_cppsp"
+    - "TESTDIR=C++/cutelyst"
     - "TESTDIR=C++/silicon"
     - "TESTDIR=C++/treefrog"
     - "TESTDIR=C++/ulib"
@@ -54,6 +56,7 @@ env:
     - "TESTDIR=Erlang/mochiweb"
     - "TESTDIR=Erlang/misultin"
     - "TESTDIR=Go/beego"
+    - "TESTDIR=Go/echo"
     - "TESTDIR=Go/falcore"
     - "TESTDIR=Go/fasthttp"
     - "TESTDIR=Go/gin"
@@ -65,6 +68,7 @@ env:
     - "TESTDIR=Haskell/snap"
     - "TESTDIR=Haskell/wai"
     - "TESTDIR=Haskell/yesod"
+    - "TESTDIR=Haskell/servant"
     - "TESTDIR=Haskell/spock"
     - "TESTDIR=Java/activeweb"
     - "TESTDIR=Java/baratine"
@@ -187,6 +191,7 @@ env:
     - "TESTDIR=Scala/scalatra"
     - "TESTDIR=Scala/scruffy"
     - "TESTDIR=Scala/spray"
+    - "TESTDIR=Scala/s-server"
     - "TESTDIR=Scala/spray-es"
     - "TESTDIR=Scala/unfiltered"
     - "TESTDIR=Scala/http4s"

+ 29 - 0
frameworks/C++/cutelyst/CMakeLists.txt

@@ -0,0 +1,29 @@
+project(cutelyst_benchmarks)
+
+cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
+if (POLICY CMP0043)
+  cmake_policy(SET CMP0043 NEW)
+endif()
+
+find_package(Qt5 COMPONENTS Core Network Sql REQUIRED)
+find_package(CutelystQt5 REQUIRED)
+
+# Auto generate moc files
+set(CMAKE_AUTOMOC ON)
+
+# As moc files are generated in the binary dir, tell CMake
+# to always look for includes there:
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+# Enable C++11 features
+add_definitions(-std=c++11)
+
+include_directories(
+    ${CMAKE_SOURCE_DIR}
+    ${CMAKE_CURRENT_BINARY_DIR}
+    ${CutelystQt5_INCLUDE_DIR}
+)
+
+file(GLOB_RECURSE TEMPLATES_SRC root/*)
+
+add_subdirectory(src)

+ 127 - 0
frameworks/C++/cutelyst/benchmark_config.json

@@ -0,0 +1,127 @@
+{
+    "framework": "cutelyst",
+    "tests": [{
+            "default": {
+                "setup_file": "setup",
+                "json_url": "/json",
+                "plaintext_url": "/plaintext",
+                "port": 8080,
+                "approach": "Realistic",
+                "classification": "Fullstack",
+                "database": "None",
+                "framework": "cutelyst",
+                "language": "C++",
+                "orm": "Raw",
+                "platform": "Qt",
+                "webserver": "None",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "cutelyst-uwsgi",
+                "notes": "",
+                "versus": ""
+            },
+            "postgres-raw": {
+                "setup_file": "setup",
+                "db_url": "/db_postgres",
+                "query_url": "/query_postgres?queries=",
+                "update_url": "/updates_postgres?queries=",
+                "fortune_url": "/fortunes_raw_postgres",
+                "port": 8080,
+                "approach": "Realistic",
+                "classification": "Platform",
+                "database": "Postgres",
+                "framework": "cutelyst",
+                "language": "C++",
+                "orm": "Raw",
+                "platform": "Qt",
+                "webserver": "None",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "cutelyst-uwsgi-pg-raw",
+                "notes": "",
+                "versus": ""
+            },
+            "mysql-raw": {
+                "setup_file": "setup",
+                "db_url": "/db_mysql",
+                "query_url": "/query_mysql?queries=",
+                "update_url": "/updates_mysql?queries=",
+                "fortune_url": "/fortunes_raw_mysql",
+                "port": 8080,
+                "approach": "Realistic",
+                "classification": "Platform",
+                "database": "MySQL",
+                "framework": "cutelyst",
+                "language": "C++",
+                "orm": "Raw",
+                "platform": "Qt",
+                "webserver": "None",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "cutelyst-uwsgi-mysql-raw",
+                "notes": "",
+                "versus": ""
+            },
+            "nginx": {
+                "setup_file": "setup_uwsgi_nginx",
+                "json_url": "/json",
+                "plaintext_url": "/plaintext",
+                "port": 8080,
+                "approach": "Realistic",
+                "classification": "Fullstack",
+                "database": "None",
+                "framework": "cutelyst",
+                "language": "C++",
+                "orm": "Raw",
+                "platform": "Qt",
+                "webserver": "None",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "cutelyst-uwsgi-nginx",
+                "notes": "",
+                "versus": ""
+            },
+            "nginx-postgres-raw": {
+                "setup_file": "setup_uwsgi_nginx",
+                "db_url": "/db_postgres",
+                "query_url": "/query_postgres?queries=",
+                "update_url": "/updates_postgres?queries=",
+                "fortune_url": "/fortunes_raw_postgres",
+                "port": 8080,
+                "approach": "Realistic",
+                "classification": "Platform",
+                "database": "Postgres",
+                "framework": "cutelyst",
+                "language": "C++",
+                "orm": "Raw",
+                "platform": "Qt",
+                "webserver": "None",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "cutelyst-uwsgi-nginx-pg-raw",
+                "notes": "",
+                "versus": ""
+            },
+            "nginx-mysql-raw": {
+                "setup_file": "setup_uwsgi_nginx",
+                "db_url": "/db_mysql",
+                "query_url": "/query_mysql?queries=",
+                "update_url": "/updates_mysql?queries=",
+                "fortune_url": "/fortunes_raw_mysql",
+                "port": 8080,
+                "approach": "Realistic",
+                "classification": "Platform",
+                "database": "MySQL",
+                "framework": "cutelyst",
+                "language": "C++",
+                "orm": "Raw",
+                "platform": "Qt",
+                "webserver": "None",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "cutelyst-uwsgi-nginx-mysql-raw",
+                "notes": "",
+                "versus": ""
+            }
+        }]
+}

+ 21 - 0
frameworks/C++/cutelyst/config/config.ini

@@ -0,0 +1,21 @@
+[uwsgi]
+master
+; Increase listen queue used for nginx connecting to uWSGI. This matches
+; net.ipv4.tcp_max_syn_backlog and net.core.somaxconn.
+; for performance
+disable-logging
+; use UNIX sockets instead of TCP loopback for performance
+http-socket = :8080
+; Avoid thundering herd problem http://uwsgi-docs.readthedocs.org/en/latest/articles/SerializingAccept.html .
+; This is currently disabled because when I tried it with flask, it caused a
+; 20% performance hit. The CPU cores could not be saturated with thunder-lock.
+; I'm not yet sure the full story, so this is presently disabled. Also,
+; disabling this caused bottle to get ~13% faster.
+;thunder-lock
+; used by uwsgi_stop.ini
+pidfile = /tmp/uwsgi.pid
+plugin = cutelyst
+
+[Cutelyst]
+DatabaseHostName=
+SendDate=

+ 23 - 0
frameworks/C++/cutelyst/config/config_socket.ini

@@ -0,0 +1,23 @@
+[uwsgi]
+master
+; Increase listen queue used for nginx connecting to uWSGI. This matches
+; net.ipv4.tcp_max_syn_backlog and net.core.somaxconn.
+; for performance
+disable-logging
+; use UNIX sockets instead of TCP loopback for performance
+socket = /tmp/uwsgi.sock
+; allow nginx to access the UNIX socket
+chmod-socket = 666
+; Avoid thundering herd problem http://uwsgi-docs.readthedocs.org/en/latest/articles/SerializingAccept.html .
+; This is currently disabled because when I tried it with flask, it caused a
+; 20% performance hit. The CPU cores could not be saturated with thunder-lock.
+; I'm not yet sure the full story, so this is presently disabled. Also,
+; disabling this caused bottle to get ~13% faster.
+;thunder-lock
+; used by uwsgi_stop.ini
+pidfile = /tmp/uwsgi.pid
+plugin = cutelyst
+
+[Cutelyst]
+DatabaseHostName=
+SendDate=

+ 48 - 0
frameworks/C++/cutelyst/nginx.conf

@@ -0,0 +1,48 @@
+# This file is based on /usr/local/nginx/conf/nginx.conf.default.
+
+# One worker process per core
+error_log stderr error;
+
+events {
+    # This needed to be increased because the nginx error log said so.
+    # http://nginx.org/en/docs/ngx_core_module.html#worker_connections
+    worker_connections  65535;
+    multi_accept on;
+}
+
+http {
+    default_type  application/octet-stream;
+    client_body_temp_path      /tmp;
+
+    # turn off request logging for performance
+    access_log off;
+
+    # I think these only options affect static file serving
+    sendfile        on;
+    tcp_nopush      on;
+
+    # Allow many HTTP Keep-Alive requests in a single TCP connection before
+    # closing it (the default is 100). This will minimize the total number
+    # of TCP connections opened/closed. The problem is that this may cause
+    # some worker processes to be handling too connections relative to the
+    # other workers based on an initial imbalance, so this is disabled for
+    # now.
+#    keepalive_requests 1000;
+
+    #keepalive_timeout  0;
+    keepalive_timeout  65;
+
+    server {
+        # For information on deferred, see:
+        # http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
+        # http://www.techrepublic.com/article/take-advantage-of-tcp-ip-options-to-optimize-data-transmission/
+        # The backlog argument to listen() is set to match net.ipv4.tcp_max_syn_backlog and net.core.somaxconn
+        listen       8080 default_server deferred backlog=65535;
+        server_name  localhost;
+
+        location / {
+            uwsgi_pass unix:/tmp/uwsgi.sock;
+            include /usr/local/nginx/conf/uwsgi_params;
+        }
+    }
+}

+ 22 - 0
frameworks/C++/cutelyst/setup.sh

@@ -0,0 +1,22 @@
+#!/bin/bash
+
+fw_depends cutelyst
+
+sed -i 's|DatabaseHostName=.*|DatabaseHostName='"$DBHOST"'|g' config/config.ini
+sed -i 's|SendDate=.*|SendDate=true|g' config/config.ini
+
+cd $IROOT
+mkdir cutelyst-benchmarks || true
+cd cutelyst-benchmarks
+rm -rf *
+
+QT_VERSION_MM=56
+export CMAKE_PREFIX_PATH=/opt/qt${QT_VERSION_MM}:${IROOT}
+
+cmake $TROOT -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$IROOT
+
+make clean
+make -j $MAX_THREADS
+
+export LD_LIBRARY_PATH=/opt/qt${QT_VERSION_MM}/lib:${IROOT}/lib/x86_64-linux-gnu/
+uwsgi --ini ${TROOT}/config/config.ini --cutelyst-app ${IROOT}/cutelyst-benchmarks/src/libcutelyst_benchmarks.so -p $(( $MAX_THREADS * 2 )) &

+ 25 - 0
frameworks/C++/cutelyst/setup_uwsgi_nginx.sh

@@ -0,0 +1,25 @@
+#!/bin/bash
+
+fw_depends cutelyst nginx
+
+sed -i 's|DatabaseHostName=.*|DatabaseHostName='"$DBHOST"'|g' config/config_socket.ini
+sed -i 's|SendDate=.*|SendDate=false|g' config/config_socket.ini
+sed -i 's|include .*/conf/uwsgi_params;|include '"${NGINX_HOME}"'/conf/uwsgi_params;|g' nginx.conf
+
+cd $IROOT
+mkdir cutelyst-benchmarks || true
+cd cutelyst-benchmarks
+rm -rf *
+
+QT_VERSION_MM=56
+export CMAKE_PREFIX_PATH=/opt/qt${QT_VERSION_MM}:${IROOT}
+
+cmake $TROOT -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$IROOT
+
+make clean
+make -j $MAX_THREADS
+
+nginx -c $TROOT/nginx.conf
+
+export LD_LIBRARY_PATH=/opt/qt${QT_VERSION_MM}/lib:${IROOT}/lib/x86_64-linux-gnu/
+uwsgi --ini ${TROOT}/config/config_socket.ini --cutelyst-app ${IROOT}/cutelyst-benchmarks/src/libcutelyst_benchmarks.so -p $(( $MAX_THREADS * 2 )) &

+ 20 - 0
frameworks/C++/cutelyst/source_code

@@ -0,0 +1,20 @@
+cutelyst/
+cutelyst/CMakeLists.txt
+cutelyst/src
+cutelyst/src/plaintexttest.cpp
+cutelyst/src/jsontest.cpp
+cutelyst/src/fortunetest.h
+cutelyst/src/CMakeLists.txt
+cutelyst/src/plaintexttest.h
+cutelyst/src/multipledatabasequeriestest.h
+cutelyst/src/multipledatabasequeriestest.cpp
+cutelyst/src/databaseupdatestest.h
+cutelyst/src/databaseupdatestest.cpp
+cutelyst/src/jsontest.h
+cutelyst/src/singledatabasequerytest.cpp
+cutelyst/src/singledatabasequerytest.h
+cutelyst/src/cutelyst-benchmarks.cpp
+cutelyst/src/cutelyst-benchmarks.h
+cutelyst/src/root.h
+cutelyst/src/root.cpp
+cutelyst/src/fortunetest.cpp

+ 19 - 0
frameworks/C++/cutelyst/src/CMakeLists.txt

@@ -0,0 +1,19 @@
+file(GLOB_RECURSE cutelyst_benchmarks_SRCS *.cpp *.h)
+
+set(cutelyst_benchmarks_SRCS
+    ${cutelyst_benchmarks_SRCS}
+    ${TEMPLATES_SRC}
+)
+
+# Create the application
+add_library(cutelyst_benchmarks SHARED ${cutelyst_benchmarks_SRCS})
+
+# Link to Cutelyst
+target_link_libraries(cutelyst_benchmarks
+    Cutelyst::Core
+    Cutelyst::Utils::Sql
+    Qt5::Core
+    Qt5::Network
+    Qt5::Sql
+)
+

+ 78 - 0
frameworks/C++/cutelyst/src/cutelyst-benchmarks.cpp

@@ -0,0 +1,78 @@
+#include "cutelyst-benchmarks.h"
+
+#include <Cutelyst/Plugins/StaticSimple/staticsimple.h>
+
+#include <QtSql/QSqlDatabase>
+#include <QtSql/QSqlError>
+#include <QDebug>
+
+#include "root.h"
+#include "jsontest.h"
+#include "singledatabasequerytest.h"
+#include "multipledatabasequeriestest.h"
+#include "databaseupdatestest.h"
+#include "fortunetest.h"
+#include "plaintexttest.h"
+
+using namespace Cutelyst;
+
+cutelyst_benchmarks::cutelyst_benchmarks(QObject *parent) : Application(parent)
+{
+}
+
+cutelyst_benchmarks::~cutelyst_benchmarks()
+{
+}
+
+bool cutelyst_benchmarks::init()
+{
+    if (config(QLatin1String("SendDate")).value<bool>()) {
+        qDebug() << "Manually send date";
+        new Root(this);
+    }
+
+    new JsonTest(this);
+    new SingleDatabaseQueryTest(this);
+    new MultipleDatabaseQueriesTest(this);
+    new DatabaseUpdatesTest(this);
+    new FortuneTest(this);
+    new PlaintextTest(this);
+
+    defaultHeaders().setServer(QLatin1String("Cutelyst"));
+    defaultHeaders().removeHeader(QLatin1String("X-Cutelyst"));
+
+    return true;
+}
+
+bool cutelyst_benchmarks::postFork()
+{
+    QSqlDatabase db;
+    db = QSqlDatabase::addDatabase(QLatin1String("QPSQL"), QLatin1String("postgres"));
+    db.setDatabaseName(QLatin1String("hello_world"));
+    db.setUserName(QLatin1String("benchmarkdbuser"));
+    db.setPassword(QLatin1String("benchmarkdbpass"));
+    db.setHostName(config(QLatin1String("DatabaseHostName")).toString());
+    if (!db.open()) {
+        qDebug() << "Error opening db:" << db << db.lastError().databaseText();
+        return false;
+    }
+
+    db = QSqlDatabase::addDatabase(QLatin1String("QMYSQL"), QLatin1String("mysql"));
+    db.setDatabaseName(QLatin1String("hello_world"));
+    db.setUserName(QLatin1String("benchmarkdbuser"));
+    db.setPassword(QLatin1String("benchmarkdbpass"));
+    db.setHostName(config(QLatin1String("DatabaseHostName")).toString());
+    if (!db.open()) {
+        qDebug() << "Error opening db:" << db << db.lastError().databaseText();
+        return false;
+    }
+
+//    db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), QLatin1String("sqlite"));
+//    if (!db.open()) {
+//        qDebug() << "Error opening db:" << db << db.lastError().databaseText();
+//        return false;
+//    }
+
+    return true;
+}
+

+ 22 - 0
frameworks/C++/cutelyst/src/cutelyst-benchmarks.h

@@ -0,0 +1,22 @@
+#ifndef CUTELYST_BENCHMARKS_H
+#define CUTELYST_BENCHMARKS_H
+
+#include <Cutelyst/Application>
+
+using namespace Cutelyst;
+
+class cutelyst_benchmarks : public Application
+{
+    Q_OBJECT
+    CUTELYST_APPLICATION(IID "cutelyst_benchmarks")
+public:
+    Q_INVOKABLE explicit cutelyst_benchmarks(QObject *parent = 0);
+    ~cutelyst_benchmarks();
+
+    bool init();
+
+    bool postFork();
+};
+
+#endif //CUTELYST_BENCHMARKS_H
+

+ 74 - 0
frameworks/C++/cutelyst/src/databaseupdatestest.cpp

@@ -0,0 +1,74 @@
+#include "databaseupdatestest.h"
+
+#include <Cutelyst/Plugins/Utils/Sql>
+
+#include <QtSql/QSqlQuery>
+
+#include <QtCore/QThread>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+
+DatabaseUpdatesTest::DatabaseUpdatesTest(QObject *parent) : Controller(parent)
+{
+
+}
+
+void DatabaseUpdatesTest::updates_postgres(Context *c)
+{
+    QSqlQuery query = CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT randomNumber FROM world WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("postgres")));
+    QSqlQuery updateQuery = CPreparedSqlQueryForDatabase(
+                QLatin1String("UPDATE world SET randomNumber = :randomNumber WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("postgres")));
+    processQuery(c, query, updateQuery);
+}
+
+void DatabaseUpdatesTest::updates_mysql(Context *c)
+{
+    QSqlQuery query = CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT randomNumber FROM world WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("mysql")));
+    QSqlQuery updateQuery = CPreparedSqlQueryForDatabase(
+                QLatin1String("UPDATE world SET randomNumber = :randomNumber WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("mysql")));
+    processQuery(c, query, updateQuery);
+}
+
+void DatabaseUpdatesTest::processQuery(Context *c, QSqlQuery &query, QSqlQuery &updateQuery)
+{
+    QJsonArray array;
+
+    int queries = c->request()->queryParam(QStringLiteral("queries"), QStringLiteral("1")).toInt();
+    if (queries < 1) {
+        queries = 1;
+    } else if (queries > 500) {
+        queries = 500;
+    }
+
+    for (int i = 0; i < queries; ++i) {
+        int id = (qrand() % 9999) + 1;
+
+        query.bindValue(QStringLiteral(":id"), id);
+        if (!query.exec() || !query.next()) {
+            c->res()->setStatus(Response::InternalServerError);
+            return;
+        }
+
+        int randomNumber = (qrand() % 9999) + 1;
+        updateQuery.bindValue(QStringLiteral(":id"), id);
+        updateQuery.bindValue(QStringLiteral(":randomNumber"), randomNumber);
+        if (!updateQuery.exec()) {
+            c->res()->setStatus(Response::InternalServerError);
+            return;
+        }
+
+        QJsonObject obj;
+        obj.insert(QStringLiteral("id"), query.value(0).toInt());
+        obj.insert(QStringLiteral("randomNumber"), randomNumber);
+        array.append(obj);
+    }
+
+    c->response()->setJsonBody(QJsonDocument(array));
+}

+ 26 - 0
frameworks/C++/cutelyst/src/databaseupdatestest.h

@@ -0,0 +1,26 @@
+#ifndef DATABASEUPDATESTEST_H
+#define DATABASEUPDATESTEST_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class QSqlQuery;
+class DatabaseUpdatesTest : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit DatabaseUpdatesTest(QObject *parent = 0);
+
+    C_ATTR(updates_postgres, :Local :AutoArgs)
+    void updates_postgres(Context *c);
+
+    C_ATTR(updates_mysql, :Local :AutoArgs)
+    void updates_mysql(Context *c);
+
+private:
+    inline void processQuery(Context *c, QSqlQuery &query, QSqlQuery &updateQuery);
+};
+
+#endif // DATABASEUPDATESTEST_H

+ 90 - 0
frameworks/C++/cutelyst/src/fortunetest.cpp

@@ -0,0 +1,90 @@
+#include "fortunetest.h"
+
+#include <Cutelyst/Plugins/Utils/Sql>
+
+#include <QStringBuilder>
+
+#include <QtSql/QSqlQuery>
+
+#include <QtCore/QThread>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+
+FortuneTest::FortuneTest(QObject *parent) : Controller(parent)
+{
+
+}
+
+void FortuneTest::fortunes_raw_postgres(Context *c)
+{
+    QSqlQuery query = postgresQuery();
+    auto fortunes = processQuery(c, query);
+    renderRaw(c, fortunes);
+}
+
+void FortuneTest::fortunes_raw_mysql(Context *c)
+{
+    QSqlQuery query = mysqlQuery();
+    auto fortunes = processQuery(c, query);
+    renderRaw(c, fortunes);
+}
+
+QSqlQuery FortuneTest::postgresQuery()
+{
+    return CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT id, message FROM fortune"),
+                QSqlDatabase::database(QLatin1String("postgres")));
+}
+
+QSqlQuery FortuneTest::mysqlQuery()
+{
+    return CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT id, message FROM fortune"),
+                QSqlDatabase::database(QLatin1String("mysql")));
+}
+
+static bool caseSensitiveLessThan(const Fortune &a1, const Fortune &a2)
+{
+    return a1.second < a2.second;
+}
+
+FortuneList FortuneTest::processQuery(Context *c, QSqlQuery &query)
+{
+    FortuneList fortunes;
+
+    if (!query.exec()) {
+        c->res()->setStatus(Response::InternalServerError);
+        return fortunes;
+    }
+
+    while (query.next()) {
+        fortunes.append(qMakePair(query.value(0).toInt(), query.value(1).toString()));
+    }
+    fortunes.append(qMakePair(0, QStringLiteral("Additional fortune added at request time.")));
+
+    qSort(fortunes.begin(), fortunes.end(), caseSensitiveLessThan);
+
+    c->response()->setContentType(QStringLiteral("text/html; charset=UTF-8"));
+
+    return fortunes;
+}
+
+void FortuneTest::renderRaw(Context *c, const FortuneList &fortunes)
+{
+    QString out;
+    out.append(QStringLiteral("<!DOCTYPE html>"
+                              "<html>"
+                              "<head><title>Fortunes</title></head>"
+                              "<body>"
+                              "<table>"
+                              "<tr><th>id</th><th>message</th></tr>"));
+
+    Q_FOREACH (const Fortune &fortune, fortunes) {
+        out.append(QLatin1String("<tr><td>") % QString::number(fortune.first) % QLatin1String("</td><td>") % fortune.second.toHtmlEscaped() % QLatin1String("</td></tr>"));
+    }
+
+    out.append(QStringLiteral("</table></body></html>"));
+
+    c->response()->setBody(out);
+}

+ 32 - 0
frameworks/C++/cutelyst/src/fortunetest.h

@@ -0,0 +1,32 @@
+#ifndef FORTUNETEST_H
+#define FORTUNETEST_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+typedef QPair<int, QString> Fortune;
+typedef QList<Fortune> FortuneList;
+
+class QSqlQuery;
+class FortuneTest : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit FortuneTest(QObject *parent = 0);
+
+    C_ATTR(fortunes_raw_postgres, :Local :AutoArgs)
+    void fortunes_raw_postgres(Context *c);
+
+    C_ATTR(fortunes_raw_mysql, :Local :AutoArgs)
+    void fortunes_raw_mysql(Context *c);
+
+private:
+    inline QSqlQuery postgresQuery();
+    inline QSqlQuery mysqlQuery();
+    inline FortuneList processQuery(Context *c, QSqlQuery &query);
+    inline void renderRaw(Context *c, const FortuneList &fortunes);
+};
+
+#endif // FORTUNETEST_H

+ 17 - 0
frameworks/C++/cutelyst/src/jsontest.cpp

@@ -0,0 +1,17 @@
+#include "jsontest.h"
+
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+
+JsonTest::JsonTest(QObject *parent) : Controller(parent)
+{
+
+}
+
+void JsonTest::json(Context *c)
+{
+    QJsonObject obj;
+    obj.insert(QStringLiteral("message"), QStringLiteral("Hello, World!"));
+
+    c->response()->setJsonBody(QJsonDocument(obj));
+}

+ 19 - 0
frameworks/C++/cutelyst/src/jsontest.h

@@ -0,0 +1,19 @@
+#ifndef JSONTEST_H
+#define JSONTEST_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class JsonTest : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit JsonTest(QObject *parent = 0);
+
+    C_ATTR(json, :Local :AutoArgs)
+    void json(Context *c);
+};
+
+#endif // JSONTEST_H

+ 60 - 0
frameworks/C++/cutelyst/src/multipledatabasequeriestest.cpp

@@ -0,0 +1,60 @@
+#include "multipledatabasequeriestest.h"
+
+#include <Cutelyst/Plugins/Utils/Sql>
+
+#include <QtSql/QSqlQuery>
+
+#include <QtCore/QThread>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+
+MultipleDatabaseQueriesTest::MultipleDatabaseQueriesTest(QObject *parent) : Controller(parent)
+{
+
+}
+
+void MultipleDatabaseQueriesTest::query_postgres(Context *c)
+{
+    QSqlQuery query = CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("postgres")));
+    processQuery(c, query);
+}
+
+void MultipleDatabaseQueriesTest::query_mysql(Context *c)
+{
+    QSqlQuery query = CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("mysql")));
+    processQuery(c, query);
+}
+
+void MultipleDatabaseQueriesTest::processQuery(Context *c, QSqlQuery &query)
+{
+    QJsonArray array;
+
+    int queries = c->request()->queryParam(QStringLiteral("queries"), QStringLiteral("1")).toInt();
+    if (queries < 1) {
+        queries = 1;
+    } else if (queries > 500) {
+        queries = 500;
+    }
+
+    for (int i = 0; i < queries; ++i) {
+        int id = qrand() % 9999;
+
+        query.bindValue(QStringLiteral(":id"), id + 1);
+        if (!query.exec() || !query.next()) {
+            c->res()->setStatus(Response::InternalServerError);
+            return;
+        }
+
+        QJsonObject obj;
+        obj.insert(QStringLiteral("id"), query.value(0).toInt());
+        obj.insert(QStringLiteral("randomNumber"), query.value(1).toInt());
+        array.append(obj);
+    }
+
+    c->response()->setJsonBody(QJsonDocument(array));
+}

+ 26 - 0
frameworks/C++/cutelyst/src/multipledatabasequeriestest.h

@@ -0,0 +1,26 @@
+#ifndef MULTIPLEDATABASEQUERIESTEST_H
+#define MULTIPLEDATABASEQUERIESTEST_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class QSqlQuery;
+class MultipleDatabaseQueriesTest : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit MultipleDatabaseQueriesTest(QObject *parent = 0);
+
+    C_ATTR(query_postgres, :Local :AutoArgs)
+    void query_postgres(Context *c);
+
+    C_ATTR(query_mysql, :Local :AutoArgs)
+    void query_mysql(Context *c);
+
+private:
+    inline void processQuery(Context *c, QSqlQuery &query);
+};
+
+#endif // MULTIPLEDATABASEQUERIESTEST_H

+ 13 - 0
frameworks/C++/cutelyst/src/plaintexttest.cpp

@@ -0,0 +1,13 @@
+#include "plaintexttest.h"
+
+PlaintextTest::PlaintextTest(QObject *parent) : Controller(parent)
+{
+
+}
+
+void PlaintextTest::plaintext(Context *c)
+{
+    Response *res = c->response();
+    res->setBody(QByteArrayLiteral("Hello, World!"));
+    res->setContentType(QStringLiteral("text/plain"));
+}

+ 19 - 0
frameworks/C++/cutelyst/src/plaintexttest.h

@@ -0,0 +1,19 @@
+#ifndef PLAINTEXTTEST_H
+#define PLAINTEXTTEST_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class PlaintextTest : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit PlaintextTest(QObject *parent = 0);
+
+    C_ATTR(plaintext, :Local :AutoArgs)
+    void plaintext(Context *c);
+};
+
+#endif // PLAINTEXTTEST_H

+ 37 - 0
frameworks/C++/cutelyst/src/root.cpp

@@ -0,0 +1,37 @@
+#include "root.h"
+
+#include <QElapsedTimer>
+
+using namespace Cutelyst;
+
+Root::Root(QObject *parent) : Controller(parent)
+{
+}
+
+Root::~Root()
+{
+}
+
+QElapsedTimer timerSetup(Context *c)
+{
+    QElapsedTimer timer;
+    timer.start();
+    return timer;
+}
+
+QString setupHeader(Context *c)
+{
+    return c->response()->headers().setDateWithDateTime(QDateTime::currentDateTimeUtc());
+}
+
+void Root::End(Context *c)
+{
+    static QString lastDate = setupHeader(c);
+    static QElapsedTimer timer = timerSetup(c);
+    if (timer.hasExpired(1000)) {
+        lastDate = setupHeader(c);
+        timer.restart();
+    } else {
+        c->response()->setHeader(QStringLiteral("date"), lastDate);
+    }
+}

+ 22 - 0
frameworks/C++/cutelyst/src/root.h

@@ -0,0 +1,22 @@
+#ifndef ROOT_H
+#define ROOT_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class Root : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit Root(QObject *parent = 0);
+    ~Root();
+
+private:
+    C_ATTR(End, :AutoArgs)
+    void End(Context *c);
+};
+
+#endif //ROOT_H
+

+ 47 - 0
frameworks/C++/cutelyst/src/singledatabasequerytest.cpp

@@ -0,0 +1,47 @@
+#include "singledatabasequerytest.h"
+
+#include <Cutelyst/Plugins/Utils/Sql>
+
+#include <QtSql/QSqlQuery>
+
+#include <QtCore/QThread>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+
+SingleDatabaseQueryTest::SingleDatabaseQueryTest(QObject *parent) : Controller(parent)
+{
+    qsrand(QDateTime::currentMSecsSinceEpoch());
+}
+
+void SingleDatabaseQueryTest::db_postgres(Context *c)
+{
+    QSqlQuery query = CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("postgres")));
+    processQuery(c, query);
+}
+
+void SingleDatabaseQueryTest::db_mysql(Context *c)
+{
+    QSqlQuery query = CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("mysql")));
+    processQuery(c, query);
+}
+
+void SingleDatabaseQueryTest::processQuery(Context *c, QSqlQuery &query)
+{
+    int id = qrand() % 9999;
+
+    query.bindValue(QStringLiteral(":id"), id + 1);
+    if (!query.exec() || !query.next()) {
+        c->res()->setStatus(Response::InternalServerError);
+        return;
+    }
+
+    QJsonObject obj;
+    obj.insert(QStringLiteral("id"), query.value(0).toInt());
+    obj.insert(QStringLiteral("randomNumber"), query.value(1).toInt());
+
+    c->response()->setJsonBody(QJsonDocument(obj));
+}

+ 26 - 0
frameworks/C++/cutelyst/src/singledatabasequerytest.h

@@ -0,0 +1,26 @@
+#ifndef SINGLEDATABASEQUERYTEST_H
+#define SINGLEDATABASEQUERYTEST_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class QSqlQuery;
+class SingleDatabaseQueryTest : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit SingleDatabaseQueryTest(QObject *parent = 0);
+
+    C_ATTR(db_postgres, :Local :AutoArgs)
+    void db_postgres(Context *c);
+
+    C_ATTR(db_mysql, :Local :AutoArgs)
+    void db_mysql(Context *c);
+
+private:
+    inline void processQuery(Context *c, QSqlQuery &query);
+};
+
+#endif // SINGLEDATABASEQUERYTEST_H

+ 3 - 0
frameworks/C++/ulib/README.md

@@ -11,12 +11,14 @@ This is the [ULib](http://stefanocasazza.github.io/ULib/) portion of a [benchmar
 * [Database test source (SQL)](src/db.usp)
 * [Database test source (REDIS)](src/rdb.usp)
 * [Database test source (MONGODB)](src/mdb.usp)
+* [Database test source (ELASTICSEARCH)](src/edb.usp)
 
 ### Variable Query Test
 
 * [Variable Query test source (SQL)](src/query.usp)
 * [Variable Query test source (REDIS)](src/rquery.usp)
 * [Variable Query test source (MONGODB)](src/mquery.usp)
+* [Variable Query test source (ELASTICSEARCH)](src/equery.usp)
 
 ### Fortune Query Test
 
@@ -29,6 +31,7 @@ This is the [ULib](http://stefanocasazza.github.io/ULib/) portion of a [benchmar
 * [Variable Query (update) test source (SQL)](src/update.usp)
 * [Variable Query (update) test source (REDIS)](src/rupdate.usp)
 * [Variable Query (update) test source (MONGODB)](src/mupdate.usp)
+* [Variable Query (update) test source (ELASTICSEARCH)](src/eupdate.usp)
 
 ### Plaintext Test
 

+ 55 - 1
frameworks/C++/ulib/benchmark_config.json

@@ -19,7 +19,7 @@
       "notes": "",
       "versus": ""
     },
-    "json": {
+    "json_normal": {
       "setup_file": "setup_json",
       "json_url": "/json",
       "port": 8080,
@@ -37,6 +37,60 @@
       "notes": "",
       "versus": ""
     },
+    "json_medium": {
+      "setup_file": "setup_json_medium",
+      "json_url": "/json",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "None",
+      "framework": "ULib",
+      "language": "C++",
+      "orm": "Micro",
+      "platform": "ULib",
+      "webserver": "ULib",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "ULib",
+      "notes": "",
+      "versus": ""
+    },
+    "json_large": {
+      "setup_file": "setup_json_large",
+      "json_url": "/json",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "None",
+      "framework": "ULib",
+      "language": "C++",
+      "orm": "Micro",
+      "platform": "ULib",
+      "webserver": "ULib",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "ULib",
+      "notes": "",
+      "versus": ""
+    },
+    "json_extra": {
+      "setup_file": "setup_json_extra",
+      "json_url": "/json",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "None",
+      "framework": "ULib",
+      "language": "C++",
+      "orm": "Micro",
+      "platform": "ULib",
+      "webserver": "ULib",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "ULib",
+      "notes": "",
+      "versus": ""
+    },
     "mysql": {
       "setup_file": "setup_mysql",
       "db_url": "/db",

+ 24 - 0
frameworks/C++/ulib/setup_elasticsearch.sh

@@ -0,0 +1,24 @@
+#!/bin/bash
+
+fw_depends ulib
+
+MAX_THREADS=$(( 2 * $MAX_THREADS ))
+
+# 1. Change ULib Server (userver_tcp) configuration
+sed -i "s|TCP_LINGER_SET .*|TCP_LINGER_SET 0|g"									  $IROOT/ULib/benchmark.cfg
+sed -i "s|LISTEN_BACKLOG .*|LISTEN_BACKLOG 256|g"								  $IROOT/ULib/benchmark.cfg
+sed -i "s|PREFORK_CHILD .*|PREFORK_CHILD ${MAX_THREADS}|g"					  $IROOT/ULib/benchmark.cfg
+sed -i "s|CLIENT_FOR_PARALLELIZATION .*|CLIENT_FOR_PARALLELIZATION 100|g" $IROOT/ULib/benchmark.cfg
+
+# 2. Start ULib Server (userver_tcp)
+export ELASTICSEARCH_HOST=$DBHOST
+export UMEMPOOL="1261,0,0,49,274,-14,-15,-24,40"
+
+# Never use setcap inside of TRAVIS 
+[ "$TRAVIS" != "true" ] || { \
+if [ `ulimit -r` -eq 99 ]; then
+	sudo setcap cap_sys_nice,cap_sys_resource,cap_net_bind_service,cap_net_raw+eip $IROOT/ULib/bin/userver_tcp
+fi
+}
+
+$IROOT/ULib/bin/userver_tcp -c $IROOT/ULib/benchmark.cfg &

+ 2 - 4
frameworks/C++/ulib/setup_json.sh

@@ -2,10 +2,8 @@
 
 fw_depends ulib
 
-# Travis is broken
-if [ "$TRAVIS" != "true" ]; then
-MAX_THREADS=$(( 3 * $MAX_THREADS / 2 ))
-else
+# Travis is really broken!!
+if [ "$TRAVIS" == "true" ]; then
 MAX_THREADS=$(( 2 * $MAX_THREADS ))
 fi
 

+ 28 - 0
frameworks/C++/ulib/setup_json_extra.sh

@@ -0,0 +1,28 @@
+#!/bin/bash
+
+fw_depends ulib
+
+# Travis is broken
+if [ "$TRAVIS" != "true" ]; then
+MAX_THREADS=$(( 3 * $MAX_THREADS ))
+else
+MAX_THREADS=$(( 2 * $MAX_THREADS ))
+fi
+
+# 1. Change ULib Server (userver_tcp) configuration
+sed -i "s|TCP_LINGER_SET .*|TCP_LINGER_SET 0|g"									  $IROOT/ULib/benchmark.cfg
+sed -i "s|LISTEN_BACKLOG .*|LISTEN_BACKLOG 256|g"								  $IROOT/ULib/benchmark.cfg
+sed -i "s|PREFORK_CHILD .*|PREFORK_CHILD ${MAX_THREADS}|g"					  $IROOT/ULib/benchmark.cfg
+sed -i "s|CLIENT_FOR_PARALLELIZATION .*|CLIENT_FOR_PARALLELIZATION 100|g" $IROOT/ULib/benchmark.cfg
+
+# 2. Start ULib Server (userver_tcp)
+export UMEMPOOL="58,0,0,41,273,-15,-14,-20,36"
+
+# Never use setcap inside of TRAVIS 
+[ "$TRAVIS" != "true" ] || { \
+if [ `ulimit -r` -eq 99 ]; then
+	sudo setcap cap_sys_nice,cap_sys_resource,cap_net_bind_service,cap_net_raw+eip $IROOT/ULib/bin/userver_tcp
+fi
+}
+
+$IROOT/ULib/bin/userver_tcp -c $IROOT/ULib/benchmark.cfg &

+ 23 - 0
frameworks/C++/ulib/setup_json_large.sh

@@ -0,0 +1,23 @@
+#!/bin/bash
+
+fw_depends ulib
+
+MAX_THREADS=$(( 2 * $MAX_THREADS ))
+
+# 1. Change ULib Server (userver_tcp) configuration
+sed -i "s|TCP_LINGER_SET .*|TCP_LINGER_SET 0|g"									  $IROOT/ULib/benchmark.cfg
+sed -i "s|LISTEN_BACKLOG .*|LISTEN_BACKLOG 256|g"								  $IROOT/ULib/benchmark.cfg
+sed -i "s|PREFORK_CHILD .*|PREFORK_CHILD ${MAX_THREADS}|g"					  $IROOT/ULib/benchmark.cfg
+sed -i "s|CLIENT_FOR_PARALLELIZATION .*|CLIENT_FOR_PARALLELIZATION 100|g" $IROOT/ULib/benchmark.cfg
+
+# 2. Start ULib Server (userver_tcp)
+export UMEMPOOL="58,0,0,41,273,-15,-14,-20,36"
+
+# Never use setcap inside of TRAVIS 
+[ "$TRAVIS" != "true" ] || { \
+if [ `ulimit -r` -eq 99 ]; then
+	sudo setcap cap_sys_nice,cap_sys_resource,cap_net_bind_service,cap_net_raw+eip $IROOT/ULib/bin/userver_tcp
+fi
+}
+
+$IROOT/ULib/bin/userver_tcp -c $IROOT/ULib/benchmark.cfg &

+ 28 - 0
frameworks/C++/ulib/setup_json_medium.sh

@@ -0,0 +1,28 @@
+#!/bin/bash
+
+fw_depends ulib
+
+# Travis is broken
+if [ "$TRAVIS" != "true" ]; then
+MAX_THREADS=$(( 3 * $MAX_THREADS / 2 ))
+else
+MAX_THREADS=$(( 2 * $MAX_THREADS ))
+fi
+
+# 1. Change ULib Server (userver_tcp) configuration
+sed -i "s|TCP_LINGER_SET .*|TCP_LINGER_SET 0|g"									  $IROOT/ULib/benchmark.cfg
+sed -i "s|LISTEN_BACKLOG .*|LISTEN_BACKLOG 256|g"								  $IROOT/ULib/benchmark.cfg
+sed -i "s|PREFORK_CHILD .*|PREFORK_CHILD ${MAX_THREADS}|g"					  $IROOT/ULib/benchmark.cfg
+sed -i "s|CLIENT_FOR_PARALLELIZATION .*|CLIENT_FOR_PARALLELIZATION 100|g" $IROOT/ULib/benchmark.cfg
+
+# 2. Start ULib Server (userver_tcp)
+export UMEMPOOL="58,0,0,41,273,-15,-14,-20,36"
+
+# Never use setcap inside of TRAVIS 
+[ "$TRAVIS" != "true" ] || { \
+if [ `ulimit -r` -eq 99 ]; then
+	sudo setcap cap_sys_nice,cap_sys_resource,cap_net_bind_service,cap_net_raw+eip $IROOT/ULib/bin/userver_tcp
+fi
+}
+
+$IROOT/ULib/bin/userver_tcp -c $IROOT/ULib/benchmark.cfg &

+ 3 - 0
frameworks/C++/ulib/source_code

@@ -1,13 +1,16 @@
 ./src/db.usp
+./src/edb.usp
 ./src/rdb.usp
 ./src/mdb.usp
 ./src/world.h
 ./src/json.usp
 ./src/fortune.h
 ./src/query.usp
+./src/equery.usp
 ./src/rquery.usp
 ./src/mquery.usp
 ./src/update.usp
+./src/eupdate.usp
 ./src/rupdate.usp
 ./src/mupdate.usp
 ./src/fortune.usp

+ 5 - 5
frameworks/C++/ulib/src/db.usp

@@ -17,21 +17,21 @@ static void usp_fork_db()
 {
    U_TRACE(5, "::usp_fork_db()")
 
-   psql_db = U_NEW(UOrmSession(U_CONSTANT_TO_PARAM("hello_world")));
+   U_NEW(UOrmSession, psql_db, UOrmSession(U_CONSTANT_TO_PARAM("hello_world")));
 
    if (psql_db->isReady())
       {
-      pstmt_db = U_NEW(UOrmStatement(*psql_db, U_CONSTANT_TO_PARAM("SELECT randomNumber FROM World WHERE id = ?")));
+      U_NEW(UOrmStatement, pstmt_db, UOrmStatement(*psql_db, U_CONSTANT_TO_PARAM("SELECT randomNumber FROM World WHERE id = ?")));
 
       if (pstmt_db == 0) U_WARNING("usp_fork_db(): we cound't connect to db");
 
-      pworld_db = U_NEW(World);
+      U_NEW(World, pworld_db, World);
 
       pstmt_db->use( pworld_db->id);
       pstmt_db->into(pworld_db->randomNumber);
 
 #  ifndef AS_cpoll_cppsp_DO
-      pvalue = U_NEW(UValue(OBJECT_VALUE));
+      U_NEW(UValue, pvalue, UValue(OBJECT_VALUE));
 #  endif
       }
 }
@@ -58,7 +58,7 @@ static void usp_end_db()
 Content-Type: application/json
 -->
 <!--#code
-pworld_db->id = u_get_num_random(10000);
+pworld_db->id = u_get_num_random(10000-1);
 
 pstmt_db->execute();
 

+ 68 - 0
frameworks/C++/ulib/src/edb.usp

@@ -0,0 +1,68 @@
+<!--#
+Test type 2: Single database query
+TechEmpower Web Framework Benchmarks
+-->
+<!--#declaration
+#include "world.h"
+
+static char buffer[128];
+static UElasticSearchClient* es;
+
+#ifndef AS_cpoll_cppsp_DO
+static UValue*	pvalue;
+#endif
+
+#define QLEN U_CONSTANT_SIZE("{\"query\":{\"match\":{\"_id\":\"")
+
+static void usp_fork_edb()
+{
+	U_TRACE(5, "::usp_fork_edb()")
+
+	U_NEW(UElasticSearchClient, es, UElasticSearchClient);
+
+	if (es->connect() == false)
+		{
+		U_WARNING("usp_fork_edb(): connection disabled or failed");
+
+		return;
+		}
+
+	u__memcpy(buffer,	"{\"query\":{\"match\":{\"_id\":\"", QLEN, __PRETTY_FUNCTION__);
+
+#ifndef AS_cpoll_cppsp_DO
+	U_NEW(UValue, pvalue, UValue(OBJECT_VALUE));
+#endif
+}
+
+#ifdef DEBUG
+static void usp_end_edb()
+{
+	U_TRACE(5, "::usp_end_edb()")
+
+	delete es;
+
+#ifndef AS_cpoll_cppsp_DO
+	if (pvalue) delete pvalue;
+#endif
+}
+#endif
+-->
+<!--#header
+Content-Type: application/json
+-->
+<!--#code
+uint32_t id;
+UString result;
+
+(void) es->sendPOST(U_CONSTANT_TO_PARAM("/tfb/world/_search"), buffer, u__snprintf(buffer+QLEN, 128, "%u\"}}}", id = u_get_num_random(10000-1))+QLEN);
+
+(void) UValue::jfind(es->getContent(), U_CONSTANT_TO_PARAM("randomNumber"), result);
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PRINTF_ADD("{\"id\":%u,\"randomNumber\":%v}", id, result.rep);
+#else
+World world(id, u_strtoul(result.data(), result.end()));
+USP_JSON_stringify(*pvalue, World, world);
+pvalue->clear();
+#endif
+-->

+ 102 - 0
frameworks/C++/ulib/src/equery.usp

@@ -0,0 +1,102 @@
+<!--#
+Test type 3: Multiple database queries
+TechEmpower Web Framework Benchmarks
+-->
+<!--#declaration
+#include "world.h"
+
+static char buffer[128];
+static UElasticSearchClient* es;
+
+#ifndef AS_cpoll_cppsp_DO
+static UValue* pvalue;
+static UVector<World*>* pvworld_query;
+#endif
+
+#define QLEN U_CONSTANT_SIZE("{\"query\":{\"match\":{\"_id\":\"")
+
+static void usp_fork_equery()
+{
+   U_TRACE(5, "::usp_fork_equery()")
+
+   U_NEW(UElasticSearchClient, es, UElasticSearchClient);
+
+   if (es->connect() == false)
+      {
+      U_WARNING("usp_fork_equery(): connection disabled or failed");
+
+      return;
+      }
+
+   u__memcpy(buffer, "{\"query\":{\"match\":{\"_id\":\"", QLEN, __PRETTY_FUNCTION__);
+
+#ifndef AS_cpoll_cppsp_DO
+   U_NEW(UValue, pvalue, UValue(ARRAY_VALUE));
+   U_NEW(UVector<World*>, pvworld_query, UVector<World*>(500));
+#endif
+}
+
+#ifdef DEBUG
+static void usp_end_equery()
+{
+   U_TRACE(5, "::usp_end_equery()")
+
+   delete es;
+
+#ifndef AS_cpoll_cppsp_DO
+   if (pvalue)
+      {
+      delete pvalue;
+      delete pvworld_query;
+      }
+#endif
+}
+#endif
+-->
+<!--#header
+Content-Type: application/json
+-->
+<!--#code
+uint32_t id;
+UString rnumber;
+int i = 0, num_queries = UHTTP::getFormFirstNumericValue(1, 500);
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PUTS_CHAR('[');
+#endif
+
+while (true)
+   {
+   (void) es->sendPOST(U_CONSTANT_TO_PARAM("/tfb/world/_search"), buffer, QLEN+u__snprintf(buffer+QLEN, 128, "%u\"}}}", id = u_get_num_random(10000-1)));
+
+   rnumber.clear();
+
+   (void) UValue::jfind(es->getContent(), U_CONSTANT_TO_PARAM("randomNumber"), rnumber);
+
+#ifdef AS_cpoll_cppsp_DO
+   USP_PRINTF("{\"id\":%u,\"randomNumber\":%v}", id, rnumber.rep);
+#else
+   World* pworld;
+
+   U_NEW(World, pworld, World(id, u_strtoul(rnumber.data(), rnumber.end())));
+
+   pvworld_query->push_back(pworld);
+#endif
+
+   if (++i == num_queries) break;
+
+#ifdef AS_cpoll_cppsp_DO
+   USP_PUTS_CHAR(',');
+#endif
+
+   rnumber.clear();
+   }
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PUTS_CHAR(']');
+#else
+USP_JSON_stringify(*pvalue, UVector<World*>, *pvworld_query);
+pvalue->clear();
+pvworld_query->clear();
+#endif
+-->

+ 108 - 0
frameworks/C++/ulib/src/eupdate.usp

@@ -0,0 +1,108 @@
+<!--#
+Test type 5: Database updates
+TechEmpower Web Framework Benchmarks
+-->
+<!--#declaration
+#include "world.h"
+
+static char* pbuffer1;
+static char* pbuffer2;
+static char buffer1[128];
+static char buffer2[128];
+static UElasticSearchClient* es;
+
+#ifndef AS_cpoll_cppsp_DO
+static UValue* pvalue;
+static UVector<World*>* pvworld_update;
+#endif
+
+#define ULEN U_CONSTANT_SIZE("/tfb/world/")
+#define QLEN U_CONSTANT_SIZE("{\"doc\":{\"_id\":\"")
+
+static void usp_fork_eupdate()
+{
+   U_TRACE(5, "::usp_fork_eupdate()")
+
+   U_NEW(UElasticSearchClient, es, UElasticSearchClient);
+
+   if (es->connect() == false)
+      {
+      U_WARNING("usp_fork_eupdate(): connection disabled or failed");
+
+      return;
+      }
+
+   u__memcpy(buffer1, "/tfb/world/", ULEN, __PRETTY_FUNCTION__);
+
+   pbuffer1 = buffer1 + ULEN;
+
+   u__memcpy(buffer2, "{\"doc\":{\"_id\":\"", QLEN, __PRETTY_FUNCTION__);
+
+   pbuffer2 = buffer2 + QLEN;
+
+#ifndef AS_cpoll_cppsp_DO
+   U_NEW(UValue, pvalue, UValue(ARRAY_VALUE));
+   U_NEW(UVector<World*>, pvworld_update, UVector<World*>(500));
+#endif
+}
+
+#ifdef DEBUG
+static void usp_end_eupdate()
+{
+   U_TRACE(5, "::usp_end_eupdate()")
+
+   delete es;
+
+#ifndef AS_cpoll_cppsp_DO
+   if (pvalue)
+      {
+      delete pvalue;
+      delete pvworld_update;
+      }
+#endif
+}
+#endif
+-->
+<!--#header
+Content-Type: application/json
+-->
+<!--#code
+uint32_t len1, len2, id, rnum;
+int i = 0, num_queries = UHTTP::getFormFirstNumericValue(1, 500);
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PUTS_CHAR('[');
+#endif
+
+while (true)
+   {
+   len1 = u__snprintf(pbuffer1, 100, "%u/_update", id = u_get_num_random(10000-1));
+   len2 = u__snprintf(pbuffer2, 100, "%u\"}}",   rnum = u_get_num_random(10000-1));
+
+   (void) es->sendPOST(buffer1, len1+ULEN, buffer2, len2+QLEN);
+
+#ifdef AS_cpoll_cppsp_DO
+   USP_PRINTF("{\"id\":%u,\"randomNumber\":%u}", id, rnum);
+#else
+   World* pworld;
+
+   U_NEW(World, pworld, World(id, rnum));
+
+   pvworld_update->push_back(pworld);
+#endif
+
+   if (++i == num_queries) break;
+
+#ifdef AS_cpoll_cppsp_DO
+   USP_PUTS_CHAR(',');
+#endif
+   }
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PUTS_CHAR(']');
+#else
+USP_JSON_stringify(*pvalue, UVector<World*>, *pvworld_update);
+pvalue->clear();
+pvworld_update->clear();
+#endif
+-->

+ 16 - 8
frameworks/C++/ulib/src/fortune.usp

@@ -16,23 +16,23 @@ static void usp_fork_fortune()
 {
    U_TRACE(5, "::usp_fork_fortune()")
 
-   psql_fortune = U_NEW(UOrmSession(U_CONSTANT_TO_PARAM("fortune")));
+   U_NEW(UOrmSession, psql_fortune, UOrmSession(U_CONSTANT_TO_PARAM("fortune")));
 
    if (psql_fortune->isReady())
       {
-      pstmt_fortune = U_NEW(UOrmStatement(*psql_fortune, U_CONSTANT_TO_PARAM("SELECT id, message FROM Fortune")));
+      U_NEW(UOrmStatement, pstmt_fortune, UOrmStatement(*psql_fortune, U_CONSTANT_TO_PARAM("SELECT id, message FROM Fortune")));
 
       if (pstmt_fortune == 0) U_WARNING("usp_fork_fortune(): we cound't connect to db");
 
       if (UOrmDriver::isPGSQL()) *psql_fortune << "BEGIN ISOLATION LEVEL SERIALIZABLE; COMMIT";
 
-      pfortune = U_NEW(Fortune);
+      U_NEW(Fortune, pfortune, Fortune);
 
       pstmt_fortune->into(*pfortune);
 
-      pencoded     = U_NEW(UString(100U));
-      pvfortune    = U_NEW(UVector<Fortune*>);
-      pfortune2add = U_NEW(Fortune(0, U_STRING_FROM_CONSTANT("Additional fortune added at request time.")));
+      U_NEW(UString, pencoded, UString(100U));
+      U_NEW(UVector<Fortune*>, pvfortune, UVector<Fortune*>);
+      U_NEW(Fortune, pfortune2add, Fortune(0, U_STRING_FROM_CONSTANT("Additional fortune added at request time.")));
       }
 }
 
@@ -55,10 +55,18 @@ static void usp_end_fortune()
 #endif
 -->
 <!doctype html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr><!--#code
+Fortune* item;
+
+U_NEW(Fortune, item, Fortune(*pfortune2add));
+pvfortune->push_back(item);
+
 pstmt_fortune->execute();
 
-     pvfortune->push_back(U_NEW(Fortune(*pfortune2add)));
-do { pvfortune->push_back(U_NEW(Fortune(*pfortune))); } while (pstmt_fortune->nextRow());
+do {
+   U_NEW(Fortune, item, Fortune(*pfortune));
+   pvfortune->push_back(item);
+   }
+while (pstmt_fortune->nextRow());
 
 pvfortune->sort(Fortune::cmp_obj);
 

+ 2 - 2
frameworks/C++/ulib/src/json.usp

@@ -18,8 +18,8 @@ static void usp_init_json()
    U_TRACE(5, "::usp_init_json()")
 
 #ifndef AS_cpoll_cppsp_DO
-   pkey   = U_NEW(U_STRING_FROM_CONSTANT("message"));
-   pvalue = U_NEW(U_STRING_FROM_CONSTANT("Hello, World!"));
+   U_NEW(UString, pkey,   U_STRING_FROM_CONSTANT("message"));
+   U_NEW(UString, pvalue, U_STRING_FROM_CONSTANT("Hello, World!"));
 #endif
 }
 

+ 25 - 36
frameworks/C++/ulib/src/mdb.usp

@@ -5,58 +5,47 @@ TechEmpower Web Framework Benchmarks
 <!--#declaration
 #include "world.h"
 
-static World* pworld;
-static UString* jquery;
 static UMongoDBClient* mc;
 
 #ifndef AS_cpoll_cppsp_DO
-static UValue* pvalue;
+static UValue*	pvalue;
 #endif
 
 static void usp_fork_mdb()
 {
-   U_TRACE(5, "::usp_fork_mdb()")
+	U_TRACE(5, "::usp_fork_mdb()")
 
-   mc = U_NEW(UMongoDBClient);
+	U_NEW(UMongoDBClient, mc, UMongoDBClient);
 
-   if (mc->connect() == false)
-      {
-      U_WARNING("usp_fork_mdb(): connection failed");
+	if (mc->connect() == false)
+		{
+		U_WARNING("usp_fork_mdb(): connection failed");
 
-      return;
-      }
+		return;
+		}
 
-   if (mc->selectCollection("hello_world", "World") == false)
-      {
-      U_WARNING("usp_fork_mdb(): selectCollection() failed");
+	if (mc->selectCollection("hello_world", "World") == false)
+		{
+		U_WARNING("usp_fork_mdb(): selectCollection() failed");
 
-      return;
-      }
-
-   pworld = U_NEW(World);
-   jquery = U_NEW(U_STRING_FROM_CONSTANT("{'randomNumber'"));
+		return;
+		}
 
 #ifndef AS_cpoll_cppsp_DO
-   pvalue = U_NEW(UValue(OBJECT_VALUE));
+	U_NEW(UValue, pvalue, UValue(OBJECT_VALUE));
 #endif
 }
 
 #ifdef DEBUG
 static void usp_end_mdb()
 {
-   U_TRACE(5, "::usp_end_mdb()")
-
-   delete mc;
+	U_TRACE(5, "::usp_end_mdb()")
 
-   if (pworld)
-      {
-      delete pworld;
-      delete jquery;
+	delete mc;
 
-#  ifndef AS_cpoll_cppsp_DO
-      delete pvalue;
-#  endif
-      }
+#ifndef AS_cpoll_cppsp_DO
+	if (pvalue) delete pvalue;
+#endif
 }
 #endif
 -->
@@ -64,18 +53,18 @@ static void usp_end_mdb()
 Content-Type: application/json
 -->
 <!--#code
+uint32_t id;
 UString result;
 
-(void) mc->findOne(pworld->id = u_get_num_random(10000));
-
-(void) UValue::jread(mc->vitem[0], *jquery, result); // { "_id" : 8980.000000, "id" : 8980.000000, "randomNumber" : 2131.000000 }
+(void) mc->findOne(id = u_get_num_random(10000-1));
 
-pworld->randomNumber = u_strtoul(result.data(), result.end());
+(void) UValue::jfind(mc->vitem[0], U_CONSTANT_TO_PARAM("randomNumber"), result);
 
 #ifdef AS_cpoll_cppsp_DO
-USP_PRINTF_ADD("{\"id\":%u,\"randomNumber\":%v}", pworld->id, result.rep);
+USP_PRINTF_ADD("{\"id\":%u,\"randomNumber\":%v}", id, result.rep);
 #else
-USP_JSON_stringify(*pvalue, World, *pworld);
+World world(id, u_strtoul(result.data(), result.end()));
+USP_JSON_stringify(*pvalue, World, world);
 pvalue->clear();
 #endif
 -->

+ 48 - 44
frameworks/C++/ulib/src/mfortune.usp

@@ -5,7 +5,6 @@ TechEmpower Web Framework Benchmarks
 <!--#declaration
 #include "fortune.h"
 
-static UString* jquery;
 static UString* pencoded;
 static UMongoDBClient* mc;
 static Fortune* pfortune2add;
@@ -13,77 +12,82 @@ static UVector<Fortune*>* pvfortune;
 
 static void usp_fork_mfortune()
 {
-   U_TRACE(5, "::usp_fork_mfortune()")
+	U_TRACE(5, "::usp_fork_mfortune()")
 
-   mc = U_NEW(UMongoDBClient);
+	U_NEW(UMongoDBClient, mc, UMongoDBClient);
 
-   if (mc->connect() == false)
-      {
-      U_WARNING("usp_fork_mfortune(): connection failed");
+	if (mc->connect() == false)
+		{
+		U_WARNING("usp_fork_mfortune(): connection failed");
 
-      return;
-      }
+		return;
+		}
 
-   if (mc->selectCollection("hello_world", "Fortune") == false)
-      {
-      U_WARNING("usp_fork_mfortune(): selectCollection() failed");
+	if (mc->selectCollection("hello_world", "Fortune") == false)
+		{
+		U_WARNING("usp_fork_mfortune(): selectCollection() failed");
 
-      return;
-      }
+		return;
+		}
 
-   jquery       = U_NEW(U_STRING_FROM_CONSTANT("{'message'"));
-   pencoded     = U_NEW(UString(100U));
-   pvfortune    = U_NEW(UVector<Fortune*>);
-   pfortune2add = U_NEW(Fortune(0, U_STRING_FROM_CONSTANT("Additional fortune added at request time.")));
+	U_NEW(UString, pencoded, UString(100U));
+	U_NEW(UVector<Fortune*>, pvfortune, UVector<Fortune*>);
+	U_NEW(Fortune, pfortune2add, Fortune(0, U_STRING_FROM_CONSTANT("Additional fortune added at request time.")));
 }
 
 #ifdef DEBUG
 static void usp_end_mfortune()
 {
-   U_TRACE(5, "::usp_end_mfortune()")
+	U_TRACE(5, "::usp_end_mfortune()")
 
-   delete mc;
+	delete mc;
 
-   if (jquery)
-      {
-      delete jquery;
-      delete pencoded;
-      delete pvfortune;
-      delete pfortune2add;
-      }
+	if (pencoded)
+		{
+		delete pencoded;
+		delete pvfortune;
+		delete pfortune2add;
+		}
 }
 #endif
 -->
 <!doctype html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr><!--#code
+Fortune* item;
 uint32_t i, n;
 UString result;
 
-(void) mc->findAll();
+U_NEW(Fortune, item, Fortune(*pfortune2add));
+
+pvfortune->push_back(item);
 
-pvfortune->push_back(U_NEW(Fortune(*pfortune2add)));
+(void) mc->findAll();
 
 for (i = 0, n = mc->vitem.size(); i < n; ++i)
-   {
-   (void) UValue::jread(mc->vitem[i], *jquery, result); // { "_id" : 5.000000, "id" : 5.000000, "message" : "A computer program does what you tell it to do, not what you want it to do." }
+	{
+	result.clear();
+
+	(void) UValue::jfind(mc->vitem[i], U_CONSTANT_TO_PARAM("message"), result);
+
+   U_NEW(Fortune, item, Fortune(i+1, result));
 
-   pvfortune->push_back(U_NEW(Fortune(i+1, result)));
-   }
+   pvfortune->push_back(item);
+	}
 
 pvfortune->sort(Fortune::cmp_obj);
 
 for (i = 0, ++n; i < n; ++i)
-   {
-   Fortune* elem = (*pvfortune)[i];
-
-   UXMLEscape::encode(elem->message, *pencoded);
-
-   USP_PRINTF_ADD(
-      "<tr>"
-      "<td>%u</td>"
-      "<td>%v</td>"
-      "</tr>",
-      elem->id, pencoded->rep);
-   }
+	{
+	Fortune* elem = (*pvfortune)[i];
+
+	UXMLEscape::encode(elem->message, *pencoded);
+
+	USP_PRINTF_ADD(
+		"<tr>"
+		"<td>%u</td>"
+		"<td>%v</td>"
+		"</tr>",
+		elem->id, pencoded->rep);
+	}
 
 pvfortune->clear();
 --></table></body></html>

+ 38 - 41
frameworks/C++/ulib/src/mquery.usp

@@ -5,61 +5,53 @@ TechEmpower Web Framework Benchmarks
 <!--#declaration
 #include "world.h"
 
-static UString* jquery;
 static UMongoDBClient* mc;
-static World* pworld_query;
 
 #ifndef AS_cpoll_cppsp_DO
-static UValue* pvalue;
+static UValue*	pvalue;
 static UVector<World*>* pvworld_query;
 #endif
 
 static void usp_fork_mquery()
 {
-   U_TRACE(5, "::usp_fork_mquery()")
+	U_TRACE(5, "::usp_fork_mquery()")
 
-   mc = U_NEW(UMongoDBClient);
+   U_NEW(UMongoDBClient, mc, UMongoDBClient);
 
-   if (mc->connect() == false)
-      {
-      U_WARNING("usp_fork_mquery(): connection disabled or failed");
+	if (mc->connect() == false)
+		{
+		U_WARNING("usp_fork_mquery(): connection disabled or failed");
 
-      return;
-      }
+		return;
+		}
 
-   if (mc->selectCollection("hello_world", "World") == false)
-      {
-      U_WARNING("usp_fork_mquery(): selectCollection() failed");
+	if (mc->selectCollection("hello_world", "World") == false)
+		{
+		U_WARNING("usp_fork_mquery(): selectCollection() failed");
 
-      return;
-      }
-
-   jquery       = U_NEW(U_STRING_FROM_CONSTANT("{'randomNumber'"));
-   pworld_query = U_NEW(World);
+		return;
+		}
 
 #ifndef AS_cpoll_cppsp_DO
-   pvalue        = U_NEW(UValue(ARRAY_VALUE));
-   pvworld_query = U_NEW(UVector<World*>(500));
+	U_NEW(UValue, pvalue, UValue(ARRAY_VALUE));
+	U_NEW(UVector<World*>, pvworld_query, UVector<World*>(500));
 #endif
 }
 
 #ifdef DEBUG
 static void usp_end_mquery()
 {
-   U_TRACE(5, "::usp_end_mquery()")
-
-   delete mc;
+	U_TRACE(5, "::usp_end_mquery()")
 
-   if (jquery)
-      {
-      delete jquery;
-      delete pworld_query;
+	delete mc;
 
-#  ifndef AS_cpoll_cppsp_DO
-      delete pvalue;
-      delete pvworld_query;
-#  endif
-      }
+#ifndef AS_cpoll_cppsp_DO
+	if (pvalue)
+		{
+		delete pvalue;
+		delete pvworld_query;
+		}
+#endif
 }
 #endif
 -->
@@ -67,6 +59,7 @@ static void usp_end_mquery()
 Content-Type: application/json
 -->
 <!--#code
+uint32_t id;
 UString rnumber;
 int i = 0, num_queries = UHTTP::getFormFirstNumericValue(1, 500);
 
@@ -75,25 +68,29 @@ USP_PUTS_CHAR('[');
 #endif
 
 while (true)
-   {
-   (void) mc->findOne(pworld_query->id = u_get_num_random(10000));
+	{
+	(void) mc->findOne(id = u_get_num_random(10000-1));
+
+	rnumber.clear();
 
-   (void) UValue::jread(mc->vitem[0], *jquery, rnumber); // { "_id" : 8980.000000, "id" : 8980.000000, "randomNumber" : 2131.000000 }
+	(void) UValue::jfind(mc->vitem[0], U_CONSTANT_TO_PARAM("randomNumber"), rnumber);
 
 #ifdef AS_cpoll_cppsp_DO
-   USP_PRINTF("{\"id\":%u,\"randomNumber\":%v}", pworld_query->id, rnumber.rep);
+	USP_PRINTF("{\"id\":%u,\"randomNumber\":%v}", id, rnumber.rep);
 #else
-   pworld_query->randomNumber = u_strtoul(rnumber.data(), rnumber.end());
+	World* pworld;
+
+	U_NEW(World, pworld, World(id, u_strtoul(rnumber.data(), rnumber.end())));
 
-   pvworld_query->push_back(U_NEW(World(*pworld_query)));
+	pvworld_query->push_back(pworld);
 #endif
 
-   if (++i == num_queries) break;
+	if (++i == num_queries) break;
 
 #ifdef AS_cpoll_cppsp_DO
-   USP_PUTS_CHAR(',');
+	USP_PUTS_CHAR(',');
 #endif
-   }
+	}
 
 #ifdef AS_cpoll_cppsp_DO
 USP_PUTS_CHAR(']');

+ 17 - 16
frameworks/C++/ulib/src/mupdate.usp

@@ -6,7 +6,6 @@ TechEmpower Web Framework Benchmarks
 #include "world.h"
 
 static UMongoDBClient* mc;
-static World* pworld_update;
 
 #ifndef AS_cpoll_cppsp_DO
 static UValue* pvalue;
@@ -17,7 +16,7 @@ static void usp_fork_mupdate()
 {
    U_TRACE(5, "::usp_fork_mupdate()")
 
-   mc = U_NEW(UMongoDBClient);
+   U_NEW(UMongoDBClient, mc, UMongoDBClient);
 
    if (mc->connect() == false)
       {
@@ -33,11 +32,9 @@ static void usp_fork_mupdate()
       return;
       }
 
-   pworld_update = U_NEW(World);
-
 #ifndef AS_cpoll_cppsp_DO
-   pvalue         = U_NEW(UValue(ARRAY_VALUE));
-   pvworld_update = U_NEW(UVector<World*>(500));
+   U_NEW(UValue, pvalue, UValue(ARRAY_VALUE));
+   U_NEW(UVector<World*>, pvworld_update, UVector<World*>(500));
 #endif
 }
 
@@ -48,15 +45,13 @@ static void usp_end_mupdate()
 
    delete mc;
 
-   if (pworld_update)
+#ifndef AS_cpoll_cppsp_DO
+   if (pvalue)
       {
-      delete pworld_update;
-
-#  ifndef AS_cpoll_cppsp_DO
       delete pvalue;
       delete pvworld_update;
-#  endif
       }
+#endif
 }
 #endif
 -->
@@ -64,6 +59,8 @@ static void usp_end_mupdate()
 Content-Type: application/json
 -->
 <!--#code
+uint32_t id, rnum;
+mongoc_bulk_operation_t* bulk = mc->createBulk(false);
 int i = 0, num_queries = UHTTP::getFormFirstNumericValue(1, 500);
 
 #ifdef AS_cpoll_cppsp_DO
@@ -72,14 +69,16 @@ USP_PUTS_CHAR('[');
 
 while (true)
    {
-   (void) mc->findOne(pworld_update->id = u_get_num_random(10000));
-
-   (void) mc->update(pworld_update->id, "randomNumber", pworld_update->randomNumber = u_get_num_random(10000));
+   mc->updateOneBulk(bulk, id = u_get_num_random(10000-1), "randomNumber", rnum = u_get_num_random(10000-1));
 
 #ifdef AS_cpoll_cppsp_DO
-   USP_PRINTF("{\"id\":%u,\"randomNumber\":%u}", pworld_update->id, pworld_update->randomNumber);
+   USP_PRINTF("{\"id\":%u,\"randomNumber\":%u}", id, rnum);
 #else
-   pvworld_update->push_back(U_NEW(World(*pworld_update)));
+   World* pworld;
+
+   U_NEW(World, pworld, World(id, rnum));
+
+   pvworld_update->push_back(pworld);
 #endif
 
    if (++i == num_queries) break;
@@ -89,6 +88,8 @@ while (true)
 #endif
    }
 
+(void) mc->executeBulk(bulk);
+
 #ifdef AS_cpoll_cppsp_DO
 USP_PUTS_CHAR(']');
 #else

+ 11 - 7
frameworks/C++/ulib/src/query.usp

@@ -18,24 +18,24 @@ static void usp_fork_query()
 {
    U_TRACE(5, "::usp_fork_query()")
 
-   psql_query = U_NEW(UOrmSession(U_CONSTANT_TO_PARAM("hello_world")));
+   U_NEW(UOrmSession, psql_query, UOrmSession(U_CONSTANT_TO_PARAM("hello_world")));
 
    if (psql_query->isReady())
       {
-      pstmt_query = U_NEW(UOrmStatement(*psql_query, U_CONSTANT_TO_PARAM("SELECT randomNumber FROM World WHERE id = ?")));
+      U_NEW(UOrmStatement, pstmt_query, UOrmStatement(*psql_query, U_CONSTANT_TO_PARAM("SELECT randomNumber FROM World WHERE id = ?")));
 
       if (pstmt_query == 0) U_WARNING("usp_fork_query(): we cound't connect to db");
 
       if (UOrmDriver::isPGSQL()) *psql_query << "BEGIN TRANSACTION";
 
-      pworld_query = U_NEW(World);
+      U_NEW(World, pworld_query, World);
 
       pstmt_query->use( pworld_query->id);
       pstmt_query->into(pworld_query->randomNumber);
 
 #  ifndef AS_cpoll_cppsp_DO
-      pvalue        = U_NEW(UValue(ARRAY_VALUE));
-      pvworld_query = U_NEW(UVector<World*>(500));
+      U_NEW(UValue, pvalue, UValue(ARRAY_VALUE));
+      U_NEW(UVector<World*>, pvworld_query, UVector<World*>(500));
 #  endif
       }
 }
@@ -71,14 +71,18 @@ USP_PUTS_CHAR('[');
 
 while (true)
    {
-   pworld_query->id = u_get_num_random(10000);
+   pworld_query->id = u_get_num_random(10000-1);
 
    pstmt_query->execute();
 
 #ifdef AS_cpoll_cppsp_DO
    USP_PRINTF("{\"id\":%u,\"randomNumber\":%u}", pworld_query->id, pworld_query->randomNumber);
 #else
-   pvworld_query->push_back(U_NEW(World(*pworld_query)));
+   World* pworld;
+
+   U_NEW(World, pworld, World(*pworld_query));
+
+   pvworld_query->push_back(pworld);
 #endif
 
    if (++i == num_queries) break;

+ 22 - 31
frameworks/C++/ulib/src/rdb.usp

@@ -5,50 +5,43 @@ TechEmpower Web Framework Benchmarks
 <!--#declaration
 #include "world.h"
 
-static World* pworld;
+static char buffer[128];
 static UREDISClient_Base* rc;
 
 #ifndef AS_cpoll_cppsp_DO
-static UValue* pvalue;
+static UValue*	pvalue;
 #endif
 
 static void usp_fork_rdb()
 {
-   U_TRACE(5, "::usp_fork_rdb()")
+	U_TRACE(5, "::usp_fork_rdb()")
 
-   rc = U_NEW(UREDISClient<UTCPSocket>);
+	U_NEW(UREDISClient<UTCPSocket>, rc, UREDISClient<UTCPSocket>);
 
-   if (rc->connect() == false)
-      {
-      U_WARNING("usp_fork_rdb(): %V", rc->UClient_Base::getResponse().rep);
+	if (rc->connect() == false)
+		{
+		U_WARNING("usp_fork_rdb(): %V", rc->UClient_Base::getResponse().rep);
 
-      return;
-      }
+		return;
+		}
 
-   pworld = U_NEW(World);
+	u__memcpy(buffer, "world:", U_CONSTANT_SIZE("world:"), __PRETTY_FUNCTION__);
 
 #ifndef AS_cpoll_cppsp_DO
-   pvalue = U_NEW(UValue(OBJECT_VALUE));
+	U_NEW(UValue, pvalue, UValue(OBJECT_VALUE));
 #endif
-
-   u__memcpy(u_buffer, "world:", U_CONSTANT_SIZE("world:"), __PRETTY_FUNCTION__);
 }
 
 #ifdef DEBUG
 static void usp_end_rdb()
 {
-   U_TRACE(5, "::usp_end_rdb()")
-
-   delete rc;
+	U_TRACE(5, "::usp_end_rdb()")
 
-   if (pworld)
-      {
-      delete pworld;
+	delete rc;
 
-#  ifndef AS_cpoll_cppsp_DO
-      delete pvalue;
-#  endif
-      }
+#ifndef AS_cpoll_cppsp_DO
+	if (pvalue) delete pvalue;
+#endif
 }
 #endif
 -->
@@ -56,18 +49,16 @@ static void usp_end_rdb()
 Content-Type: application/json
 -->
 <!--#code
-UStringRep* rep;
-
-(void) rc->get(u_buffer, 6+u_num2str32(u_buffer+6, pworld->id = u_get_num_random(10000)));
-
-rep = rc->vitem[0].rep;
+uint32_t id;
 
-pworld->randomNumber = u_strtoul(rep->data(), rep->end());
+(void) rc->get(buffer, 6+u_num2str32(buffer+6, id = u_get_num_random(10000-1)));
 
 #ifdef AS_cpoll_cppsp_DO
-USP_PRINTF_ADD("{\"id\":%u,\"randomNumber\":%v}", pworld->id, rep);
+USP_PRINTF_ADD("{\"id\":%u,\"randomNumber\":%v}", id, rc->vitem[0].rep);
 #else
-USP_JSON_stringify(*pvalue, World, *pworld);
+UStringRep* rep = rc->vitem[0].rep;
+World world(id, u_strtoul(rep->data(), rep->end()));
+USP_JSON_stringify(*pvalue, World, world);
 pvalue->clear();
 #endif
 -->

+ 14 - 6
frameworks/C++/ulib/src/rfortune.usp

@@ -14,7 +14,7 @@ static void usp_fork_rfortune()
 {
    U_TRACE(5, "::usp_fork_rfortune()")
 
-   rc = U_NEW(UREDISClient<UTCPSocket>);
+   U_NEW(UREDISClient<UTCPSocket>, rc, UREDISClient<UTCPSocket>);
 
    if (rc->connect() == false)
       {
@@ -23,9 +23,9 @@ static void usp_fork_rfortune()
       return;
       }
 
-   pencoded     = U_NEW(UString(100U));
-   pvfortune    = U_NEW(UVector<Fortune*>);
-   pfortune2add = U_NEW(Fortune(0, U_STRING_FROM_CONSTANT("Additional fortune added at request time.")));
+   U_NEW(UString, pencoded, UString(100U));
+   U_NEW(UVector<Fortune*>, pvfortune, UVector<Fortune*>);
+   U_NEW(Fortune, pfortune2add, Fortune(0, U_STRING_FROM_CONSTANT("Additional fortune added at request time.")));
 }
 
 #ifdef DEBUG
@@ -45,13 +45,21 @@ static void usp_end_rfortune()
 #endif
 -->
 <!doctype html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr><!--#code
+Fortune* item;
 uint32_t i, n;
 
+U_NEW(Fortune, item, Fortune(*pfortune2add));
+
+pvfortune->push_back(item);
+
 (void) rc->lrange(U_CONSTANT_TO_PARAM("fortunes 0 -1"));
 
-pvfortune->push_back(U_NEW(Fortune(*pfortune2add)));
+for (i = 0, n = rc->vitem.size(); i < n; ++i)
+   {
+   U_NEW(Fortune, item, Fortune(i+1, rc->vitem[i]));
 
-for (i = 0, n = rc->vitem.size(); i < n; ++i) pvfortune->push_back(U_NEW(Fortune(i+1, rc->vitem[i])));
+   pvfortune->push_back(item);
+   }
 
 pvfortune->sort(Fortune::cmp_obj);
 

+ 36 - 33
frameworks/C++/ulib/src/rquery.usp

@@ -5,48 +5,49 @@ TechEmpower Web Framework Benchmarks
 <!--#declaration
 #include "world.h"
 
+static char buffer[128];
 static UREDISClient_Base* rc;
-static UVector<World*>* pvworld_query;
+static UVector<World*>*	pvworld_query;
 
 #ifndef AS_cpoll_cppsp_DO
-static UValue* pvalue;
+static UValue*	pvalue;
 #endif
 
 static void usp_fork_rquery()
 {
-   U_TRACE(5, "::usp_fork_rquery()")
+	U_TRACE(5, "::usp_fork_rquery()")
 
-   rc = U_NEW(UREDISClient<UTCPSocket>);
+	U_NEW(UREDISClient<UTCPSocket>, rc, UREDISClient<UTCPSocket>);
 
-   if (rc->connect() == false)
-      {
-      U_WARNING("usp_fork_rquery(): %V", rc->UClient_Base::getResponse().rep);
+	if (rc->connect() == false)
+		{
+		U_WARNING("usp_fork_rquery(): %V", rc->UClient_Base::getResponse().rep);
 
-      return;
-      }
+		return;
+		}
 
-   pvworld_query = U_NEW(UVector<World*>(500));
+	U_NEW(UVector<World*>, pvworld_query, UVector<World*>(500));
 
 #ifndef AS_cpoll_cppsp_DO
-   pvalue = U_NEW(UValue(ARRAY_VALUE));
+	U_NEW(UValue, pvalue, UValue(ARRAY_VALUE));
 #endif
 }
 
 #ifdef DEBUG
 static void usp_end_rquery()
 {
-   U_TRACE(5, "::usp_end_rquery()")
+	U_TRACE(5, "::usp_end_rquery()")
 
-   delete rc;
+	delete rc;
 
-   if (pvworld_query)
-      {
-      delete pvworld_query;
+	if (pvworld_query)
+		{
+		delete pvworld_query;
 
-#  ifndef AS_cpoll_cppsp_DO
-      delete pvalue;
-#  endif
-      }
+#	ifndef AS_cpoll_cppsp_DO
+		delete pvalue;
+#	endif
+		}
 }
 #endif
 -->
@@ -56,7 +57,7 @@ Content-Type: application/json
 <!--#code
 World* pworld;
 UStringRep* rep;
-char* pbuffer = u_buffer;
+char* pbuffer = buffer;
 int i, num_queries = UHTTP::getFormFirstNumericValue(1, 500);
 
 #ifdef AS_cpoll_cppsp_DO
@@ -64,36 +65,38 @@ USP_PUTS_CHAR('[');
 #endif
 
 for (i = 0; i < num_queries; ++i)
-   {
-   pvworld_query->push_back(pworld = U_NEW(World));
+	{
+	U_NEW(World, pworld, World);
+
+	pvworld_query->push_back(pworld);
 
-   u_put_unalignedp64(pbuffer, U_MULTICHAR_CONSTANT64(' ','w','o','r','l','d',':','\0'));
+	u_put_unalignedp64(pbuffer, U_MULTICHAR_CONSTANT64(' ','w','o','r','l','d',':','\0'));
 
-   pbuffer += 7+u_num2str32(pbuffer+7, pworld->id = u_get_num_random(10000));
-   }
+	pbuffer += 7+u_num2str32(pbuffer+7, pworld->id = u_get_num_random(10000-1));
+	}
 
-(void) rc->mget(u_buffer, pbuffer-u_buffer);
+(void) rc->mget(buffer, pbuffer-buffer);
 
 i = 0;
 
 while (true)
    {
-   pworld = pvworld_query->at(i);
+	pworld = pvworld_query->at(i);
 
-   rep = rc->vitem[i].rep;
+	rep = rc->vitem[i].rep;
 
-   pworld->randomNumber = u_strtoul(rep->data(), rep->end());
+	pworld->randomNumber = u_strtoul(rep->data(), rep->end());
 
 #ifdef AS_cpoll_cppsp_DO
-   USP_PRINTF("{\"id\":%u,\"randomNumber\":%v}", pworld->id, rep);
+	USP_PRINTF("{\"id\":%u,\"randomNumber\":%v}", pworld->id, rep);
 #endif
 
    if (++i == num_queries) break;
 
 #ifdef AS_cpoll_cppsp_DO
-   USP_PUTS_CHAR(',');
+	USP_PUTS_CHAR(',');
 #endif
-   }
+	}
 
 #ifdef AS_cpoll_cppsp_DO
 USP_PUTS_CHAR(']');

+ 38 - 35
frameworks/C++/ulib/src/rupdate.usp

@@ -5,48 +5,49 @@ TechEmpower Web Framework Benchmarks
 <!--#declaration
 #include "world.h"
 
+static char buffer[128];
 static UREDISClient_Base* rc;
 static UVector<World*>* pvworld;
 
 #ifndef AS_cpoll_cppsp_DO
-static UValue* pvalue;
+static UValue*	pvalue;
 #endif
 
 static void usp_fork_rupdate()
 {
-   U_TRACE(5, "::usp_fork_rupdate()")
+	U_TRACE(5, "::usp_fork_rupdate()")
 
-   rc = U_NEW(UREDISClient<UTCPSocket>);
+	U_NEW(UREDISClient<UTCPSocket>, rc, UREDISClient<UTCPSocket>);
 
-   if (rc->connect() == false)
-      {
-      U_WARNING("usp_fork_rupdate(): %V", rc->UClient_Base::getResponse().rep);
+	if (rc->connect() == false)
+		{
+		U_WARNING("usp_fork_rupdate(): %V", rc->UClient_Base::getResponse().rep);
 
-      return;
-      }
+		return;
+		}
 
-   pvworld = U_NEW(UVector<World*>(500));
+	U_NEW(UVector<World*>, pvworld, UVector<World*>(500));
 
 #ifndef AS_cpoll_cppsp_DO
-   pvalue = U_NEW(UValue(ARRAY_VALUE));
+	U_NEW(UValue, pvalue, UValue(ARRAY_VALUE));
 #endif
 }
 
 #ifdef DEBUG
 static void usp_end_rupdate()
 {
-   U_TRACE(5, "::usp_end_rupdate()")
+	U_TRACE(5, "::usp_end_rupdate()")
 
-   delete rc;
+	delete rc;
 
-   if (pvworld)
-      {
-      delete pvworld;
+	if (pvworld)
+		{
+		delete pvworld;
 
-#  ifndef AS_cpoll_cppsp_DO
-      delete pvalue;
-#  endif
-      }
+#	ifndef AS_cpoll_cppsp_DO
+		delete pvalue;
+#	endif
+		}
 }
 #endif
 -->
@@ -55,7 +56,7 @@ Content-Type: application/json
 -->
 <!--#code
 World* pworld;
-char* pbuffer = u_buffer;
+char* pbuffer = buffer;
 int i, num_queries = UHTTP::getFormFirstNumericValue(1, 500);
 
 #ifdef AS_cpoll_cppsp_DO
@@ -63,41 +64,43 @@ USP_PUTS_CHAR('[');
 #endif
 
 for (i = 0; i < num_queries; ++i)
-   {
-   pvworld->push_back(pworld = U_NEW(World));
+	{
+	U_NEW(World, pworld, World);
+
+	pvworld->push_back(pworld);
 
-   u_put_unalignedp64(pbuffer, U_MULTICHAR_CONSTANT64(' ','w','o','r','l','d',':','\0'));
+	u_put_unalignedp64(pbuffer, U_MULTICHAR_CONSTANT64(' ','w','o','r','l','d',':','\0'));
 
-   pbuffer += 7+u_num2str32(pbuffer+7, pworld->id = u_get_num_random(10000));
-   }
+	pbuffer += 7+u_num2str32(pbuffer+7, pworld->id = u_get_num_random(10000-1));
+	}
 
-(void) rc->mget(u_buffer, pbuffer-u_buffer);
+(void) rc->mget(buffer, pbuffer-buffer);
 
 i = 0;
-pbuffer = u_buffer;
+pbuffer = buffer;
 
 while (true)
    {
-   pworld = pvworld->at(i);
+	pworld = pvworld->at(i);
 
-   u_put_unalignedp64(pbuffer, U_MULTICHAR_CONSTANT64(' ','w','o','r','l','d',':','\0'));
+	u_put_unalignedp64(pbuffer, U_MULTICHAR_CONSTANT64(' ','w','o','r','l','d',':','\0'));
 
-   pbuffer += 7+u_num2str32(pbuffer+7, pworld->id);
+	pbuffer += 7+u_num2str32(pbuffer+7, pworld->id);
   *pbuffer  = ' ';
-   pbuffer += 1+u_num2str32(pbuffer+1, pworld->randomNumber = u_get_num_random(10000));
+	pbuffer += 1+u_num2str32(pbuffer+1, pworld->randomNumber = u_get_num_random(10000-1));
 
 #ifdef AS_cpoll_cppsp_DO
-   USP_PRINTF("{\"id\":%u,\"randomNumber\":%u}", pworld->id, pworld->randomNumber);
+	USP_PRINTF("{\"id\":%u,\"randomNumber\":%u}", pworld->id, pworld->randomNumber);
 #endif
 
    if (++i == num_queries) break;
 
 #ifdef AS_cpoll_cppsp_DO
-   USP_PUTS_CHAR(',');
+	USP_PUTS_CHAR(',');
 #endif
-   }
+	}
 
-(void) rc->mset(u_buffer, pbuffer-u_buffer);
+(void) rc->mset(buffer, pbuffer-buffer);
 
 #ifdef AS_cpoll_cppsp_DO
 USP_PUTS_CHAR(']');

+ 13 - 11
frameworks/C++/ulib/src/update.usp

@@ -19,12 +19,12 @@ static void usp_fork_update()
 {
    U_TRACE(5, "::usp_fork_update()")
 
-   psql_update = U_NEW(UOrmSession(U_CONSTANT_TO_PARAM("hello_world")));
+   U_NEW(UOrmSession, psql_update, UOrmSession(U_CONSTANT_TO_PARAM("hello_world")));
 
    if (psql_update->isReady())
       {
-      pstmt1 = U_NEW(UOrmStatement(*psql_update, U_CONSTANT_TO_PARAM("SELECT randomNumber FROM World WHERE id = ?")));
-      pstmt2 = U_NEW(UOrmStatement(*psql_update, U_CONSTANT_TO_PARAM("UPDATE World SET randomNumber = ? WHERE id = ?")));
+      U_NEW(UOrmStatement, pstmt1, UOrmStatement(*psql_update, U_CONSTANT_TO_PARAM("SELECT randomNumber FROM World WHERE id = ?")));
+      U_NEW(UOrmStatement, pstmt2, UOrmStatement(*psql_update, U_CONSTANT_TO_PARAM("UPDATE World SET randomNumber = ? WHERE id = ?")));
 
       if (pstmt1 == 0 ||
           pstmt2 == 0)
@@ -32,17 +32,15 @@ static void usp_fork_update()
          U_WARNING("usp_fork_update(): we cound't connect to db");
          }
 
-		if (UOrmDriver::isPGSQL()) *psql_update << "SET synchronous_commit TO OFF";
-
-      pworld_update = U_NEW(World);
+      U_NEW(World, pworld_update, World);
 
       pstmt1->use( pworld_update->id);
       pstmt1->into(pworld_update->randomNumber);
       pstmt2->use( pworld_update->randomNumber, pworld_update->id);
 
 #  ifndef AS_cpoll_cppsp_DO
-      pvalue         = U_NEW(UValue(ARRAY_VALUE));
-      pvworld_update = U_NEW(UVector<World*>(500));
+      U_NEW(UValue, pvalue, UValue(ARRAY_VALUE));
+      U_NEW(UVector<World*>, pvworld_update, UVector<World*>(500));
 #  endif
       }
 }
@@ -80,18 +78,22 @@ USP_PUTS_CHAR('[');
 
 while (true)
    {
-   pworld_update->id = u_get_num_random(10000);
+   pworld_update->id = u_get_num_random(10000-1);
 
    pstmt1->execute();
 
-   pworld_update->randomNumber = u_get_num_random(10000);
+   pworld_update->randomNumber = u_get_num_random(10000-1);
 
    pstmt2->execute();
 
 #ifdef AS_cpoll_cppsp_DO
    USP_PRINTF("{\"id\":%u,\"randomNumber\":%u}", pworld_update->id, pworld_update->randomNumber);
 #else
-   pvworld_update->push_back(U_NEW(World(*pworld_update)));
+   World* pworld;
+
+   U_NEW(World, pworld, World(*pworld_update));
+
+   pvworld_update->push_back(pworld);
 #endif
 
    if (++i == num_queries) break;

+ 2 - 4
frameworks/C++/ulib/src/world.h

@@ -27,11 +27,9 @@ public:
 #  endif
       }
 
-   World(const World& w) : id(w.id), randomNumber(w.randomNumber)
+   World(uint32_t _id, uint32_t _randomNumber) : id(_id), randomNumber(_randomNumber)
       {
-      U_TRACE_REGISTER_OBJECT(5, World, "%p", &w)
-
-      U_MEMORY_TEST_COPY(w)
+      U_TRACE_REGISTER_OBJECT(5, World, "%u,%u", _id, _randomNumber)
       }
 
    ~World()

+ 23 - 0
frameworks/C/h2o/CMakeLists.txt

@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 2.8.0)
+project(h2o_app)
+find_library(H2O_LIB h2o-evloop)
+find_library(MUSTACHE_C_LIB mustache_c)
+find_library(YAJL_LIB yajl)
+find_path(H2O_INCLUDE h2o.h)
+find_path(MUSTACHE_C_INCLUDE mustache.h)
+find_path(YAJL_INCLUDE yajl/yajl_gen.h)
+set(COMMON_OPTIONS -flto -pthread)
+add_compile_options(-std=gnu11 -pedantic -Wall -Wextra ${COMMON_OPTIONS})
+set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address")
+set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fstack-protector-all -D_FORTIFY_SOURCE=2")
+set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Ofast")
+set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -Ofast")
+add_definitions(-DH2O_USE_LIBUV=0)
+include_directories(src ${H2O_INCLUDE} ${MUSTACHE_C_INCLUDE} ${YAJL_INCLUDE})
+file(GLOB SOURCES "src/*.c")
+add_executable(${PROJECT_NAME} ${SOURCES})
+target_link_libraries(${PROJECT_NAME} ${COMMON_OPTIONS})
+target_link_libraries(${PROJECT_NAME} ${H2O_LIB} ssl crypto pq ${MUSTACHE_C_LIB} ${YAJL_LIB})
+install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
+file(GLOB TEMPLATES "template/*")
+install(FILES ${TEMPLATES} DESTINATION share/${PROJECT_NAME}/template)

+ 11 - 0
frameworks/C/h2o/README.md

@@ -0,0 +1,11 @@
+# h2o
+
+This is a framework implementation using the [H2O](https://h2o.examp1e.net/) HTTP server.
+
+## Requirements
+
+CMake, H2O, libpq, mustache-c, OpenSSL, YAJL
+
+## Contact
+
+Anton Kirilov <[email protected]>

+ 27 - 0
frameworks/C/h2o/benchmark_config.json

@@ -0,0 +1,27 @@
+{
+  "framework": "h2o",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "Postgres",
+      "framework": "H2O",
+      "language": "C",
+      "orm": "Raw",
+      "platform": "H2O",
+      "webserver": "H2O",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "h2o",
+      "notes": ""
+    }
+  }]
+}

+ 57 - 0
frameworks/C/h2o/setup.sh

@@ -0,0 +1,57 @@
+#!/bin/bash
+
+fw_depends h2o mustache-c yajl
+
+H2O_APP_HOME="${IROOT}/h2o_app"
+BUILD_DIR="${H2O_APP_HOME}_build"
+H2O_APP_PROFILE_PORT="54321"
+H2O_APP_PROFILE_URL="http://127.0.0.1:$H2O_APP_PROFILE_PORT"
+
+build_h2o_app()
+{
+	cmake -DCMAKE_INSTALL_PREFIX="$H2O_APP_HOME" -DCMAKE_BUILD_TYPE=Release \
+		-DCMAKE_LIBRARY_PATH="${H2O_HOME}/lib;${MUSTACHE_C_HOME}/lib;${YAJL_HOME}/lib" \
+		-DCMAKE_INCLUDE_PATH="${H2O_HOME}/include;${MUSTACHE_C_HOME}/include;${YAJL_HOME}/include" \
+		-DCMAKE_C_FLAGS="-march=native $1" "$TROOT"
+	make -j "$(nproc)"
+}
+
+run_curl()
+{
+	for ((i = 0; i < 10; i++)); do
+		curl "${H2O_APP_PROFILE_URL}/$1" > /dev/null 2>&1
+	done
+}
+
+run_h2o_app()
+{
+	"$1/h2o_app" -a2 -f "$2/template/fortunes.mustache" -m2 "$3" "$4" \
+		-d "host=$DBHOST dbname=hello_world user=benchmarkdbuser password=benchmarkdbpass" &
+}
+
+generate_profile_data()
+{
+	run_h2o_app . "${TROOT}" -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
+	kill -s SIGTERM $H2O_APP_PROFILE_PID
+	wait $H2O_APP_PROFILE_PID
+}
+
+install -d "$BUILD_DIR"
+pushd "$BUILD_DIR"
+build_h2o_app "-fprofile-generate"
+generate_profile_data
+make clean
+rm -f CMakeCache.txt
+build_h2o_app "-fprofile-use"
+make -j "$(nproc)" install
+popd
+rm -rf "$BUILD_DIR"
+run_h2o_app "${H2O_APP_HOME}/bin" "${H2O_APP_HOME}/share/h2o_app"

+ 23 - 0
frameworks/C/h2o/source_code

@@ -0,0 +1,23 @@
+./src/bitset.h
+./src/database.c
+./src/database.h
+./src/error.c
+./src/error.h
+./src/event_loop.c
+./src/event_loop.h
+./src/fortune.c
+./src/fortune.h
+./src/list.h
+./src/main.c
+./src/request_handler.c
+./src/request_handler.h
+./src/template.c
+./src/template.h
+./src/thread.c
+./src/thread.h
+./src/tls.c
+./src/tls.h
+./src/utility.c
+./src/utility.h
+./src/world.c
+./src/world.h

+ 60 - 0
frameworks/C/h2o/src/bitset.h

@@ -0,0 +1,60 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef BITSET_H_
+
+#define BITSET_H_
+
+#include <assert.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "utility.h"
+
+typedef uint_fast32_t bitset_base_t;
+
+#define BITSET_ISSET(i, b) bitset_isset((i), (b), sizeof(b) * CHAR_BIT)
+#define BITSET_SET(i, b) bitset_set((i), (b), sizeof(b) * CHAR_BIT)
+// Use a designated initializer to set all array elements to zero.
+#define DEFINE_BITSET(b, s) \
+	assert(s); \
+	bitset_base_t (b)[((s) - 1) / (sizeof(bitset_base_t) * CHAR_BIT) + 1] = {[0] = 0}
+
+static inline void bitset_set(size_t i, bitset_base_t *b, size_t num)
+{
+	assert(i < num);
+
+	IGNORE_FUNCTION_PARAMETER(num);
+
+	const bitset_base_t mask = ((bitset_base_t) 1) << (i % (sizeof(*b) * CHAR_BIT));
+
+	b[i / (sizeof(*b) * CHAR_BIT)] |= mask;
+}
+
+static inline bool bitset_isset(size_t i, bitset_base_t *b, size_t num)
+{
+	assert(i < num);
+
+	IGNORE_FUNCTION_PARAMETER(num);
+
+	return (b[i / (sizeof(*b) * CHAR_BIT)] >> (i % (sizeof(*b) * CHAR_BIT))) & (bitset_base_t) 1;
+}
+
+#endif // BITSET_H_

+ 518 - 0
frameworks/C/h2o/src/database.c

@@ -0,0 +1,518 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <h2o.h>
+#include <stdlib.h>
+#include <string.h>
+#include <postgresql/libpq-fe.h>
+
+#include "database.h"
+#include "error.h"
+#include "list.h"
+#include "thread.h"
+#include "utility.h"
+
+#define IS_RESETTING 1
+#define IS_WRITING 2
+
+typedef struct {
+	list_t l;
+	PGconn *conn;
+	thread_context_t *ctx;
+	void (*on_write_ready)(void *);
+	db_query_param_t *param;
+	h2o_socket_t *sock;
+	size_t prep_stmt_idx;
+	uint_fast32_t flags;
+	h2o_timeout_entry_t h2o_timeout_entry;
+} db_conn_t;
+
+static int do_database_write(db_conn_t *db_conn);
+static void do_execute_query(db_conn_t *db_conn);
+static void error_notification(thread_context_t *ctx, bool timeout, const char *error_string);
+static void on_database_connect_error(db_conn_t *db_conn, bool timeout, const char *error_string);
+static void on_database_connect_timeout(h2o_timeout_entry_t *entry);
+static void on_database_error(db_conn_t *db_conn, const char *error_string);
+static void on_database_read_ready(h2o_socket_t *db_sock, const char *err);
+static void on_database_timeout(h2o_timeout_entry_t *entry);
+static void on_database_write_ready(void *data);
+static void poll_database_connection(h2o_socket_t *db_sock, const char *err);
+static void process_query(db_conn_t *db_conn);
+static void start_database_connect(thread_context_t *ctx, db_conn_t *db_conn);
+static int start_database_write_polling(db_conn_t *db_conn);
+static void stop_database_write_polling(db_conn_t *db_conn);
+
+static const struct {
+	const char *name;
+	const char *query;
+} prepared_statement[] = {
+	{FORTUNE_TABLE_NAME, "SELECT * FROM " FORTUNE_TABLE_NAME ";"},
+	{WORLD_TABLE_NAME,
+	 "SELECT * FROM " WORLD_TABLE_NAME " "
+	 "WHERE " WORLD_TABLE_NAME "." ID_FIELD_NAME " = $1::integer;"},
+};
+
+static void do_execute_query(db_conn_t *db_conn)
+{
+	const int ec = db_conn->param->flags & IS_PREPARED ?
+	               PQsendQueryPrepared(db_conn->conn,
+	                                   db_conn->param->command,
+	                                   db_conn->param->nParams,
+	                                   db_conn->param->paramValues,
+	                                   db_conn->param->paramLengths,
+	                                   db_conn->param->paramFormats,
+	                                   db_conn->param->resultFormat) :
+	               PQsendQuery(db_conn->conn, db_conn->param->command);
+
+	if (ec) {
+		if (db_conn->param->flags & IS_SINGLE_ROW)
+			PQsetSingleRowMode(db_conn->conn);
+
+		db_conn->h2o_timeout_entry.cb = on_database_timeout;
+		h2o_timeout_link(db_conn->ctx->event_loop.h2o_ctx.loop,
+		                 &db_conn->ctx->db_state.h2o_timeout,
+		                 &db_conn->h2o_timeout_entry);
+		h2o_socket_read_start(db_conn->sock, on_database_read_ready);
+		on_database_write_ready(&db_conn->on_write_ready);
+	}
+	else
+		on_database_error(db_conn, PQerrorMessage(db_conn->conn));
+}
+
+static void error_notification(thread_context_t *ctx, bool timeout, const char *error_string)
+{
+	if (!--ctx->db_state.db_conn_num) {
+		// We don't want to keep requests waiting for an unbounded amount of time.
+		list_t *iter = ctx->db_state.queries.head;
+
+		ctx->db_state.queries.head = NULL;
+		ctx->db_state.queries.tail = &ctx->db_state.queries.head;
+		ctx->db_state.query_num = 0;
+
+		if (iter)
+			do {
+				db_query_param_t * const param = H2O_STRUCT_FROM_MEMBER(db_query_param_t, l, iter);
+
+				// The callback may free the db_query_param_t structure.
+				iter = iter->next;
+
+				if (timeout)
+					param->on_timeout(param);
+				else
+					param->on_error(param, error_string);
+			} while (iter);
+	}
+}
+
+static void on_database_connect_error(db_conn_t *db_conn, bool timeout, const char *error_string)
+{
+	thread_context_t * const ctx = db_conn->ctx;
+
+	error_notification(ctx, timeout, error_string);
+	h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+	h2o_socket_read_stop(db_conn->sock);
+	h2o_socket_close(db_conn->sock);
+	PQfinish(db_conn->conn);
+	free(db_conn);
+}
+
+static void on_database_connect_timeout(h2o_timeout_entry_t *entry)
+{
+	db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, h2o_timeout_entry, entry);
+
+	on_database_connect_error(db_conn, true, DB_TIMEOUT_ERROR);
+}
+
+static void on_database_error(db_conn_t *db_conn, const char *error_string)
+{
+	if (db_conn->prep_stmt_idx < ARRAY_SIZE(prepared_statement))
+		on_database_connect_error(db_conn, false, error_string);
+	else {
+		if (db_conn->param) {
+			db_conn->param->on_error(db_conn->param, error_string);
+			db_conn->param = NULL;
+		}
+
+		if (PQstatus(db_conn->conn) == CONNECTION_OK) {
+			h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+			h2o_socket_read_stop(db_conn->sock);
+			process_query(db_conn);
+		}
+		else
+			start_database_connect(db_conn->ctx, db_conn);
+	}
+}
+
+static void on_database_read_ready(h2o_socket_t *db_sock, const char *err)
+{
+	db_conn_t * const db_conn = db_sock->data;
+
+	if (!err) {
+		if (PQconsumeInput(db_conn->conn)) {
+			const int send_status = PQflush(db_conn->conn);
+
+			if (send_status > 0 && start_database_write_polling(db_conn)) {
+				on_database_error(db_conn, EPOLL_ERR_MSG);
+				return;
+			}
+
+			if (send_status >= 0) {
+				while (!PQisBusy(db_conn->conn)) {
+					PGresult * const result = PQgetResult(db_conn->conn);
+
+					if (db_conn->param)
+						switch (db_conn->param->on_result(db_conn->param, result)) {
+							case WANT_WRITE:
+								db_conn->flags |= IS_WRITING;
+
+								if (do_database_write(db_conn))
+									return;
+
+								break;
+							case DONE:
+								db_conn->param = NULL;
+								h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+								break;
+							default:
+								break;
+						}
+					else if (result)
+						PQclear(result);
+
+					if (!result) {
+						assert(!db_conn->param);
+						h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+						h2o_socket_read_stop(db_conn->sock);
+						process_query(db_conn);
+						break;
+					}
+				}
+
+				return;
+			}
+		}
+
+		err = PQerrorMessage(db_conn->conn);
+	}
+
+	on_database_error(db_conn, err);
+}
+
+static void on_database_timeout(h2o_timeout_entry_t *entry)
+{
+	db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, h2o_timeout_entry, entry);
+
+	if (db_conn->param) {
+		db_conn->param->on_timeout(db_conn->param);
+		db_conn->param = NULL;
+	}
+
+	start_database_connect(db_conn->ctx, db_conn);
+}
+
+static void on_database_write_ready(void *data)
+{
+	db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, on_write_ready, data);
+
+	if (db_conn->prep_stmt_idx) {
+		const int send_status = PQflush(db_conn->conn);
+
+		if (!send_status) {
+			if (db_conn->flags & IS_WRITING && db_conn->param)
+				do_database_write(db_conn);
+		}
+		else if (send_status < 0)
+			on_database_error(db_conn, PQerrorMessage(db_conn->conn));
+		else if (send_status > 0 && start_database_write_polling(db_conn))
+			on_database_error(db_conn, EPOLL_ERR_MSG);
+	}
+	else
+		poll_database_connection(db_conn->sock, NULL);
+}
+
+static void poll_database_connection(h2o_socket_t *db_sock, const char *err)
+{
+	db_conn_t * const db_conn = db_sock->data;
+
+	if (!err) {
+		const PostgresPollingStatusType status = db_conn->flags & IS_RESETTING ?
+		                                         PQresetPoll(db_conn->conn) :
+		                                         PQconnectPoll(db_conn->conn);
+
+		switch (status) {
+			case PGRES_POLLING_WRITING:
+				if (start_database_write_polling(db_conn)) {
+					err = EPOLL_ERR_MSG;
+					break;
+				}
+
+				return;
+			case PGRES_POLLING_OK:
+				if (PQsetnonblocking(db_conn->conn, 1)) {
+					err = PQerrorMessage(db_conn->conn);
+					break;
+				}
+
+				h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+				h2o_socket_read_stop(db_conn->sock);
+				process_query(db_conn);
+				return;
+			case PGRES_POLLING_READING:
+				return;
+			default:
+				err = PQerrorMessage(db_conn->conn);
+		}
+	}
+
+	on_database_connect_error(db_conn, false, err);
+}
+
+static void process_query(db_conn_t *db_conn)
+{
+	if (db_conn->prep_stmt_idx < ARRAY_SIZE(prepared_statement)) {
+		if (PQsendPrepare(db_conn->conn,
+		                  prepared_statement[db_conn->prep_stmt_idx].name,
+		                  prepared_statement[db_conn->prep_stmt_idx].query,
+		                  0,
+		                  NULL)) {
+			db_conn->prep_stmt_idx++;
+			db_conn->h2o_timeout_entry.cb = on_database_connect_timeout;
+			h2o_timeout_link(db_conn->ctx->event_loop.h2o_ctx.loop,
+			                 &db_conn->ctx->db_state.h2o_timeout,
+			                 &db_conn->h2o_timeout_entry);
+			h2o_socket_read_start(db_conn->sock, on_database_read_ready);
+			on_database_write_ready(&db_conn->on_write_ready);
+		}
+		else
+			on_database_connect_error(db_conn, false, PQerrorMessage(db_conn->conn));
+	}
+	else if (db_conn->ctx->db_state.query_num) {
+		db_conn->ctx->db_state.query_num--;
+
+		if (db_conn->ctx->db_state.queries.tail == &db_conn->ctx->db_state.queries.head->next) {
+			assert(!db_conn->ctx->db_state.query_num);
+			db_conn->ctx->db_state.queries.tail = &db_conn->ctx->db_state.queries.head;
+		}
+
+		db_conn->param = H2O_STRUCT_FROM_MEMBER(db_query_param_t,
+		                                        l,
+		                                        db_conn->ctx->db_state.queries.head);
+		db_conn->ctx->db_state.queries.head = db_conn->ctx->db_state.queries.head->next;
+		do_execute_query(db_conn);
+	}
+	else {
+		db_conn->l.next = db_conn->ctx->db_state.db_conn;
+		db_conn->ctx->db_state.db_conn = &db_conn->l;
+		db_conn->ctx->db_state.free_db_conn_num++;
+	}
+}
+
+static void start_database_connect(thread_context_t *ctx, db_conn_t *db_conn)
+{
+	char buf[128] = "";
+	const char *error_string = buf;
+
+	if (db_conn) {
+		db_conn->prep_stmt_idx = 0;
+		db_conn->flags = IS_RESETTING;
+		h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+		stop_database_write_polling(db_conn);
+		h2o_socket_read_stop(db_conn->sock);
+		h2o_socket_close(db_conn->sock);
+
+		if (!PQresetStart(db_conn->conn)) {
+			strncpy(buf, PQerrorMessage(db_conn->conn), sizeof(buf));
+			buf[sizeof(buf) - 1] = '\0';
+			goto error_dup;
+		}
+	}
+	else {
+		ctx->db_state.db_conn_num++;
+		db_conn = calloc(1, sizeof(*db_conn));
+
+		if (!db_conn) {
+			error_string = MEM_ALLOC_ERR_MSG;
+			goto error;
+		}
+
+		const char * const conninfo =
+			ctx->global_data->config->db_host ? ctx->global_data->config->db_host : "";
+
+		db_conn->conn = PQconnectStart(conninfo);
+
+		if (!db_conn->conn) {
+			error_string = MEM_ALLOC_ERR_MSG;
+			goto error_connect;
+		}
+
+		if (PQstatus(db_conn->conn) == CONNECTION_BAD) {
+			strncpy(buf, PQerrorMessage(db_conn->conn), sizeof(buf));
+			buf[sizeof(buf) - 1] = '\0';
+			goto error_dup;
+		}
+	}
+
+	const int sd = dup(PQsocket(db_conn->conn));
+
+	if (sd < 0) {
+		if (strerror_r(errno, buf, sizeof(buf)))
+			*buf = '\0';
+
+		goto error_dup;
+	}
+
+	const int flags = fcntl(sd, F_GETFD);
+
+	if (flags < 0 || fcntl(sd, F_SETFD, flags | FD_CLOEXEC)) {
+		if (strerror_r(errno, buf, sizeof(buf)))
+			*buf = '\0';
+
+		goto error_dup;
+	}
+
+	db_conn->sock = h2o_evloop_socket_create(ctx->event_loop.h2o_ctx.loop,
+	                                         sd,
+	                                         H2O_SOCKET_FLAG_DONT_READ);
+
+	if (db_conn->sock) {
+		db_conn->sock->data = db_conn;
+		db_conn->ctx = ctx;
+		db_conn->h2o_timeout_entry.cb = on_database_connect_timeout;
+		h2o_timeout_link(ctx->event_loop.h2o_ctx.loop,
+		                 &ctx->db_state.h2o_timeout,
+		                 &db_conn->h2o_timeout_entry);
+		h2o_socket_read_start(db_conn->sock, poll_database_connection);
+
+		if (!start_database_write_polling(db_conn))
+			return;
+
+		h2o_socket_read_stop(db_conn->sock);
+		h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+		h2o_socket_close(db_conn->sock);
+		error_string = "socket write polling failure";
+	}
+	else {
+		error_string = "could not allocate H2O socket";
+		close(sd);
+	}
+
+error_dup:
+	PQfinish(db_conn->conn);
+error_connect:
+	free(db_conn);
+error:
+	error_notification(ctx, false, error_string);
+}
+
+static int start_database_write_polling(db_conn_t *db_conn)
+{
+	const bool rearm = !!db_conn->on_write_ready;
+
+	db_conn->on_write_ready = on_database_write_ready;
+	return start_write_polling(PQsocket(db_conn->conn),
+	                           &db_conn->on_write_ready,
+	                           rearm,
+	                           &db_conn->ctx->event_loop);
+}
+
+static void stop_database_write_polling(db_conn_t *db_conn)
+{
+	db_conn->on_write_ready = NULL;
+	stop_write_polling(PQsocket(db_conn->conn), &db_conn->ctx->event_loop);
+}
+
+void connect_to_database(thread_context_t *ctx)
+{
+	for (size_t i = ctx->db_state.db_conn_num; i < ctx->global_data->config->max_db_conn_num; i++)
+		start_database_connect(ctx, NULL);
+}
+
+static int do_database_write(db_conn_t *db_conn)
+{
+	assert(db_conn->param);
+
+	int ret = db_conn->param->on_write_ready(db_conn->param, db_conn->conn);
+
+	if (!ret)
+		db_conn->flags &= ~IS_WRITING;
+	else if (ret < 0)
+		on_database_error(db_conn, PQerrorMessage(db_conn->conn));
+	else if (start_database_write_polling(db_conn))
+		on_database_error(db_conn, EPOLL_ERR_MSG);
+	else
+		ret = 0;
+
+	return ret;
+}
+
+int execute_query(thread_context_t *ctx, db_query_param_t *param)
+{
+	int ret = EXIT_SUCCESS;
+
+	if (ctx->db_state.free_db_conn_num) {
+		db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, l, ctx->db_state.db_conn);
+
+		ctx->db_state.db_conn = db_conn->l.next;
+		ctx->db_state.free_db_conn_num--;
+		db_conn->param = param;
+		do_execute_query(db_conn);
+	}
+	else if (ctx->db_state.query_num < ctx->global_data->config->max_query_num) {
+		param->l.next = NULL;
+		*ctx->db_state.queries.tail = &param->l;
+		ctx->db_state.queries.tail = &param->l.next;
+		ctx->db_state.query_num++;
+
+		if (ctx->db_state.db_conn_num < ctx->global_data->config->max_db_conn_num)
+			start_database_connect(ctx, NULL);
+	}
+	else
+		ret = EXIT_FAILURE;
+
+	return ret;
+}
+
+void free_database_state(h2o_loop_t *loop, db_state_t *db_state)
+{
+	assert(!db_state->query_num && db_state->free_db_conn_num == db_state->db_conn_num);
+
+	list_t *iter = db_state->db_conn;
+
+	if (iter)
+		do {
+			db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, l, iter);
+
+			iter = iter->next;
+			assert(!db_conn->param && !h2o_timeout_is_linked(&db_conn->h2o_timeout_entry));
+			h2o_socket_close(db_conn->sock);
+			PQfinish(db_conn->conn);
+			free(db_conn);
+		} while (iter);
+
+	h2o_timeout_dispose(loop, &db_state->h2o_timeout);
+}
+
+void initialize_database_state(h2o_loop_t *loop, db_state_t *db_state)
+{
+	memset(db_state, 0, sizeof(*db_state));
+	db_state->queries.tail = &db_state->queries.head;
+	h2o_timeout_init(loop, &db_state->h2o_timeout, H2O_DEFAULT_HTTP1_REQ_TIMEOUT);
+}

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

@@ -0,0 +1,88 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef DATABASE_H_
+
+#define DATABASE_H_
+
+#include <h2o.h>
+#include <stdint.h>
+#include <postgresql/libpq-fe.h>
+
+#include "list.h"
+
+#define COPY_HEADER "PGCOPY\n\377\r\n\0\0\0\0\0\0\0\0\0"
+#define DB_REQ_ERROR "too many concurrent database requests\n"
+#define DB_TIMEOUT_ERROR "database timeout\n"
+#define FORTUNE_TABLE_NAME "Fortune"
+#define ID_FIELD_NAME "id"
+#define IS_PREPARED 1
+#define IS_SINGLE_ROW 2
+#define MAX_ID 10000
+#define MESSAGE_FIELD_NAME "message"
+#define WORLD_TABLE_NAME "World"
+
+#define UPDATE_QUERY \
+	"CREATE TEMP TABLE input(LIKE World INCLUDING ALL) ON COMMIT DROP;" \
+	"COPY input FROM STDIN WITH (FORMAT binary);" \
+	"UPDATE World SET randomNumber = input.randomNumber FROM input " \
+	"WHERE World." ID_FIELD_NAME " = input." ID_FIELD_NAME ";"
+
+typedef enum {
+	SUCCESS,
+	DONE,
+	WANT_WRITE
+} result_return_t;
+
+typedef struct thread_context_t thread_context_t;
+
+typedef struct db_query_param_t db_query_param_t;
+
+typedef result_return_t (*on_result_t)(db_query_param_t *, PGresult *);
+
+struct db_query_param_t {
+	list_t l;
+	void (*on_error)(db_query_param_t *, const char *);
+	on_result_t on_result;
+	void (*on_timeout)(db_query_param_t *);
+	int (*on_write_ready)(db_query_param_t *, PGconn *);
+	const char *command;
+	const char * const *paramValues;
+	const int *paramLengths;
+	const int *paramFormats;
+	size_t nParams;
+	uint_fast32_t flags;
+	int resultFormat;
+};
+
+typedef struct {
+	list_t *db_conn;
+	queue_t queries;
+	size_t db_conn_num;
+	size_t free_db_conn_num;
+	size_t query_num;
+	h2o_timeout_t h2o_timeout;
+} db_state_t;
+
+void connect_to_database(thread_context_t *ctx);
+int execute_query(thread_context_t *ctx, db_query_param_t *param);
+void free_database_state(h2o_loop_t *loop, db_state_t *db_state);
+void initialize_database_state(h2o_loop_t *loop, db_state_t *db_state);
+
+#endif // DATABASE_H_

+ 66 - 0
frameworks/C/h2o/src/error.c

@@ -0,0 +1,66 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <libgen.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+
+#include "error.h"
+
+void print_error(const char *file,
+                 unsigned line,
+                 const char *function,
+                 const char *error_string, ...)
+{
+	char * const file_name = strdup(file);
+
+	if (file_name)
+		file = basename(file_name);
+
+	flockfile(stderr);
+	fprintf(stderr, "[%d] %s: %u: %s(): ", (int) syscall(SYS_gettid), file, line, function);
+
+	va_list arg;
+
+	va_start(arg, error_string);
+	vfprintf(stderr, error_string, arg);
+	va_end(arg);
+	putc_unlocked('\n', stderr);
+	funlockfile(stderr);
+
+	if (file_name)
+		free(file_name);
+}
+
+void print_library_error(const char *file,
+                         unsigned line,
+                         const char *function,
+                         int error_code)
+{
+	char error_string[128];
+
+	if (strerror_r(error_code, error_string, sizeof(error_string)))
+		*error_string = '\0';
+
+	print_error(file, line, function, "%s (%d)", error_string, error_code);
+}

+ 85 - 0
frameworks/C/h2o/src/error.h

@@ -0,0 +1,85 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef ERROR_H_
+
+#define ERROR_H_
+
+#include <errno.h>
+#include <stdarg.h>
+#include <yajl/yajl_gen.h>
+
+#define EPOLL_ERR_MSG "epoll_ctl() failure\n"
+#define MEM_ALLOC_ERR_MSG "memory allocation failure\n"
+
+#define CHECK_ERRNO(function, ...) \
+	do { \
+		const int error_code = (function)(__VA_ARGS__); \
+		\
+		if (error_code) { \
+			print_library_error(__FILE__, __LINE__, #function, errno); \
+			abort(); \
+		} \
+	} while(0)
+
+#define CHECK_ERRNO_RETURN(out, function, ...) \
+	do { \
+		const int return_value = (function)(__VA_ARGS__); \
+		\
+		if (return_value == -1) { \
+			print_library_error(__FILE__, __LINE__, #function, errno); \
+			abort(); \
+		} \
+		\
+		(out) = return_value; \
+	} while(0)
+
+#define CHECK_ERROR(function, ...) \
+	do { \
+		const int error_code = (function)(__VA_ARGS__); \
+		\
+		if (error_code) { \
+			print_library_error(__FILE__, __LINE__, #function, error_code); \
+			abort(); \
+		} \
+	} while(0)
+
+#define CHECK_YAJL_STATUS(function, ...) \
+	do { \
+		const yajl_gen_status status_code = (function)(__VA_ARGS__); \
+		\
+		if (status_code != yajl_gen_status_ok) { \
+			print_error(__FILE__, __LINE__, #function, "Error (%d)", (int) status_code); \
+			goto error_yajl; \
+		} \
+	} while(0)
+
+#define ERROR(...) print_error(__FILE__, __LINE__, __func__, __VA_ARGS__)
+#define LIBRARY_ERROR(function) print_library_error(__FILE__, __LINE__, (function), errno)
+
+void print_error(const char *file,
+                 unsigned line,
+                 const char *function,
+                 const char *error_string, ...);
+void print_library_error(const char *file,
+                         unsigned line,
+                         const char *function,
+                         int error_code);
+
+#endif // ERROR_H_

+ 205 - 0
frameworks/C/h2o/src/event_loop.c

@@ -0,0 +1,205 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <h2o.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <sys/syscall.h>
+
+#include "error.h"
+#include "event_loop.h"
+#include "thread.h"
+#include "utility.h"
+
+static void accept_connection(h2o_socket_t *listener, const char *err);
+static void do_epoll_wait(h2o_socket_t *epoll_sock, const char *err);
+static void on_close_connection(void *data);
+static void process_messages(h2o_multithread_receiver_t *receiver, h2o_linklist_t *messages);
+static void shutdown_server(h2o_socket_t *listener, const char *err);
+
+static void accept_connection(h2o_socket_t *listener, const char *err)
+{
+	if (!err) {
+		thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+		                                                      event_loop,
+		                                                      listener->data);
+
+		if (!ctx->global_data->shutdown) {
+			size_t accepted = 0;
+
+			do {
+				h2o_socket_t * const sock = h2o_evloop_socket_accept(listener);
+
+				if (!sock)
+					break;
+
+				ctx->event_loop.conn_num++;
+				sock->on_close.cb = on_close_connection;
+				sock->on_close.data = &ctx->event_loop.conn_num;
+				h2o_accept(&ctx->event_loop.h2o_accept_ctx, sock);
+			} while (++accepted < ctx->global_data->config->max_accept);
+		}
+	}
+}
+
+static void do_epoll_wait(h2o_socket_t *epoll_sock, const char *err)
+{
+	if (!err) {
+		thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+		                                                      event_loop,
+		                                                      epoll_sock->data);
+		const size_t num_event = ctx->db_state.db_conn_num - ctx->db_state.free_db_conn_num;
+		int ready;
+		struct epoll_event event[num_event];
+
+		do
+			ready = epoll_wait(ctx->event_loop.epoll_fd, event, num_event, 0);
+		while (ready < 0 && errno == EINTR);
+
+		if (ready > 0)
+			for (size_t i = 0; i < (size_t) ready; i++) {
+				void (** const on_write_ready)(void *) = event[i].data.ptr;
+
+				(*on_write_ready)(on_write_ready);
+			}
+	}
+}
+
+static void on_close_connection(void *data)
+{
+	size_t * const conn_num = data;
+
+	(*conn_num)--;
+}
+
+static void process_messages(h2o_multithread_receiver_t *receiver, h2o_linklist_t *messages)
+{
+	IGNORE_FUNCTION_PARAMETER(messages);
+
+	thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+	                                                      event_loop.h2o_receiver,
+	                                                      receiver);
+
+	h2o_socket_read_stop(ctx->event_loop.h2o_socket);
+}
+
+static void shutdown_server(h2o_socket_t *listener, const char *err)
+{
+	if (!err) {
+		thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+		                                                      event_loop,
+		                                                      listener->data);
+
+		ctx->global_data->shutdown = true;
+		h2o_socket_read_stop(ctx->event_loop.h2o_socket);
+
+		for (size_t i = 1; i < ctx->global_data->config->thread_num; i++)
+			h2o_multithread_send_message(&ctx[i].event_loop.h2o_receiver, NULL);
+	}
+}
+
+void event_loop(thread_context_t *ctx)
+{
+	ctx->tid = syscall(SYS_gettid);
+	ctx->random_seed = ctx->tid;
+
+	while (!ctx->global_data->shutdown || ctx->event_loop.conn_num)
+		h2o_evloop_run(ctx->event_loop.h2o_ctx.loop);
+}
+
+void free_event_loop(event_loop_t *event_loop)
+{
+	h2o_multithread_unregister_receiver(event_loop->h2o_ctx.queue, &event_loop->h2o_receiver);
+	h2o_socket_close(event_loop->h2o_socket);
+	h2o_socket_close(event_loop->epoll_socket);
+	h2o_context_dispose(&event_loop->h2o_ctx);
+}
+
+void initialize_event_loop(bool is_main_thread,
+                           global_data_t *global_data,
+                           event_loop_t *loop)
+{
+	memset(loop, 0, sizeof(*loop));
+	h2o_context_init(&loop->h2o_ctx, h2o_evloop_create(), &global_data->h2o_config);
+	loop->h2o_accept_ctx.ctx = &loop->h2o_ctx;
+	loop->h2o_accept_ctx.hosts = global_data->h2o_config.hosts;
+	loop->h2o_accept_ctx.ssl_ctx = global_data->ssl_ctx;
+
+	int listener_sd;
+
+	if (is_main_thread)
+		listener_sd = global_data->listener_sd;
+	else {
+		int flags;
+
+		CHECK_ERRNO_RETURN(listener_sd, dup, global_data->listener_sd);
+		CHECK_ERRNO_RETURN(flags, fcntl, listener_sd, F_GETFD);
+		CHECK_ERRNO(fcntl, listener_sd, F_SETFD, flags | FD_CLOEXEC);
+	}
+
+	// Let all the threads race to call accept() on the socket; since the latter is
+	// non-blocking, that will effectively act as load balancing.
+	loop->h2o_socket = h2o_evloop_socket_create(loop->h2o_ctx.loop,
+	                                            listener_sd,
+	                                            H2O_SOCKET_FLAG_DONT_READ);
+	loop->h2o_socket->data = loop;
+	h2o_socket_read_start(loop->h2o_socket, accept_connection);
+	h2o_multithread_register_receiver(loop->h2o_ctx.queue,
+	                                  &loop->h2o_receiver,
+	                                  process_messages);
+	// libh2o's event loop does not support write polling unless it
+	// controls sending the data as well, so do read polling on the
+	// epoll file descriptor as a work-around.
+	CHECK_ERRNO_RETURN(loop->epoll_fd, epoll_create1, EPOLL_CLOEXEC);
+	loop->epoll_socket = h2o_evloop_socket_create(loop->h2o_ctx.loop,
+	                                              loop->epoll_fd,
+	                                              H2O_SOCKET_FLAG_DONT_READ);
+	loop->epoll_socket->data = loop;
+	h2o_socket_read_start(loop->epoll_socket, do_epoll_wait);
+
+	if (is_main_thread) {
+		global_data->signals = h2o_evloop_socket_create(loop->h2o_ctx.loop,
+		                                                global_data->signal_fd,
+		                                                H2O_SOCKET_FLAG_DONT_READ);
+		global_data->signals->data = loop;
+		h2o_socket_read_start(global_data->signals, shutdown_server);
+	}
+}
+
+int start_write_polling(int fd,
+                        void (**on_write_ready)(void *),
+                        bool rearm,
+                        event_loop_t *event_loop)
+{
+	struct epoll_event event;
+
+	memset(&event, 0, sizeof(event));
+	event.data.ptr = on_write_ready;
+	event.events = EPOLLET | EPOLLONESHOT | EPOLLOUT | EPOLLRDHUP;
+	return epoll_ctl(event_loop->epoll_fd, rearm ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, fd, &event);
+}
+
+void stop_write_polling(int fd, event_loop_t *event_loop)
+{
+	epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_DEL, fd, NULL);
+}

+ 52 - 0
frameworks/C/h2o/src/event_loop.h

@@ -0,0 +1,52 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef EVENT_LOOP_H_
+
+#define EVENT_LOOP_H_
+
+#include <h2o.h>
+#include <stdbool.h>
+
+#include "utility.h"
+
+typedef struct thread_context_t thread_context_t;
+
+typedef struct {
+	h2o_socket_t *epoll_socket;
+	h2o_socket_t *h2o_socket;
+	size_t conn_num;
+	int epoll_fd;
+	h2o_accept_ctx_t h2o_accept_ctx;
+	h2o_context_t h2o_ctx;
+	h2o_multithread_receiver_t h2o_receiver;
+} event_loop_t;
+
+void event_loop(thread_context_t *ctx);
+void free_event_loop(event_loop_t *event_loop);
+void initialize_event_loop(bool is_main_thread,
+                           global_data_t *global_data,
+                           event_loop_t *loop);
+int start_write_polling(int fd,
+                        void (**on_write_ready)(void *),
+                        bool rearm,
+                        event_loop_t *event_loop);
+void stop_write_polling(int fd, event_loop_t *event_loop);
+
+#endif // EVENT_LOOP_H_

+ 422 - 0
frameworks/C/h2o/src/fortune.c

@@ -0,0 +1,422 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <assert.h>
+#include <h2o.h>
+#include <postgresql/libpq-fe.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <mustache.h>
+
+#include "database.h"
+#include "error.h"
+#include "fortune.h"
+#include "list.h"
+#include "request_handler.h"
+#include "thread.h"
+#include "utility.h"
+
+#define MAX_IOVEC 64
+#define NEW_FORTUNE_ID "0"
+#define NEW_FORTUNE_MESSAGE "Additional fortune added at request time."
+
+typedef struct {
+	list_t l;
+	size_t iovcnt;
+	size_t max_iovcnt;
+	h2o_iovec_t iov[];
+} iovec_list_t;
+
+typedef struct {
+	const fortune_t *fortune_iter;
+	list_t *iovec_list;
+	iovec_list_t *iovec_list_iter;
+	h2o_req_t *req;
+	list_t *result;
+	size_t num_result;
+	db_query_param_t param;
+	h2o_generator_t generator;
+} fortune_ctx_t;
+
+static uintmax_t add_iovec(mustache_api_t *api,
+                           void *userdata,
+                           char *buffer,
+                           uintmax_t buffer_size);
+static void cleanup_fortunes(struct st_h2o_generator_t *self, h2o_req_t *req);
+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 list_t *get_sorted_sublist(list_t *head);
+static list_t *merge_lists(list_t *head1, list_t *head2);
+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);
+static uintmax_t on_fortune_section(mustache_api_t *api,
+                                    void *userdata,
+                                    mustache_token_section_t *token);
+static void on_fortune_timeout(db_query_param_t *param);
+static uintmax_t on_fortune_variable(mustache_api_t *api,
+                                     void *userdata,
+                                     mustache_token_variable_t *token);
+static list_t *sort_fortunes(list_t *head);
+
+static uintmax_t add_iovec(mustache_api_t *api,
+                           void *userdata,
+                           char *buffer,
+                           uintmax_t buffer_size)
+{
+	IGNORE_FUNCTION_PARAMETER(api);
+
+	fortune_ctx_t * const fortune_ctx = userdata;
+	iovec_list_t *iovec_list = fortune_ctx->iovec_list_iter;
+	uintmax_t ret = 1;
+
+	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);
+
+		if (iovec_list) {
+			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;
+		}
+		else
+			ret = 0;
+	}
+
+	if (ret) {
+		iovec_list->iov[iovec_list->iovcnt].base = buffer;
+		iovec_list->iov[iovec_list->iovcnt++].len = buffer_size;
+	}
+
+	return ret;
+}
+
+static void cleanup_fortunes(struct st_h2o_generator_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(req);
+
+	fortune_ctx_t * const fortune_ctx = H2O_STRUCT_FROM_MEMBER(fortune_ctx_t,
+	                                                           generator,
+	                                                           self);
+	const list_t *iter = fortune_ctx->result;
+
+	if (iter)
+		do {
+			const fortune_t * const fortune = H2O_STRUCT_FROM_MEMBER(fortune_t, l, iter);
+
+			if (fortune->data)
+				PQclear(fortune->data);
+
+			iter = iter->next;
+		} while (iter);
+}
+
+static int compare_fortunes(const list_t *x, const list_t *y)
+{
+	const fortune_t * const f1 = H2O_STRUCT_FROM_MEMBER(fortune_t, l, x);
+	const fortune_t * const f2 = H2O_STRUCT_FROM_MEMBER(fortune_t, l, y);
+	const size_t sz = MIN(f1->message.len, f2->message.len);
+	int ret = memcmp(f1->message.base, f2->message.base, sz);
+
+	if (!ret)
+		ret = f1->message.len < f2->message.len ? -1 : f1->message.len > f2->message.len;
+
+	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);
+
+	if (fortune_ctx->iovec_list) {
+		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;
+		h2o_send(fortune_ctx->req, iovec_list->iov, iovec_list->iovcnt, false);
+	}
+	else {
+		h2o_send(req, NULL, 0, true);
+		cleanup_fortunes(self, req);
+	}
+}
+
+static list_t *get_sorted_sublist(list_t *head)
+{
+	list_t *tail = head;
+
+	if (head) {
+		head = head->next;
+
+		while (head && compare_fortunes(tail, head) < 0) {
+			tail = head;
+			head = head->next;
+		}
+
+		while (head && !compare_fortunes(tail, head)) {
+			tail = head;
+			head = head->next;
+		}
+	}
+
+	return tail;
+}
+
+static list_t *merge_lists(list_t *head1, list_t *head2)
+{
+	list_t *ret = NULL;
+	list_t **current = &ret;
+
+	while (1) {
+		if (!head1) {
+			*current = head2;
+			break;
+		}
+		else if (!head2) {
+			*current = head1;
+			break;
+		}
+		// Checking for equality makes this algorithm a stable sort.
+		else if (compare_fortunes(head1, head2) <= 0) {
+			*current = head1;
+			current = &head1->next;
+			head1 = head1->next;
+		}
+		else {
+			*current = head2;
+			current = &head2->next;
+			head2 = head2->next;
+		}
+	}
+
+	return ret;
+}
+
+static void on_fortune_error(db_query_param_t *param, const char *error_string)
+{
+	fortune_ctx_t * const fortune_ctx = H2O_STRUCT_FROM_MEMBER(fortune_ctx_t,
+	                                                           param,
+	                                                           param);
+
+	cleanup_fortunes(&fortune_ctx->generator, fortune_ctx->req);
+	send_error(BAD_GATEWAY, error_string, fortune_ctx->req);
+}
+
+static result_return_t on_fortune_result(db_query_param_t *param, PGresult *result)
+{
+	fortune_ctx_t * const fortune_ctx = H2O_STRUCT_FROM_MEMBER(fortune_ctx_t,
+	                                                           param,
+	                                                           param);
+	int ret = DONE;
+	const ExecStatusType status = PQresultStatus(result);
+
+	if (status == PGRES_TUPLES_OK) {
+		const size_t num_rows = PQntuples(result);
+
+		ret = SUCCESS;
+
+		for (size_t i = 0; i < num_rows; i++) {
+			fortune_t * const fortune = h2o_mem_alloc_pool(&fortune_ctx->req->pool,
+			                                               sizeof(*fortune));
+
+			if (fortune) {
+				memset(fortune, 0, sizeof(*fortune));
+				fortune->id.base = PQgetvalue(result, i, 0);
+				fortune->id.len = PQgetlength(result, i, 0);
+				fortune->message = h2o_htmlescape(&fortune_ctx->req->pool,
+				                                  PQgetvalue(result, i, 1),
+				                                  PQgetlength(result, i, 1));
+				fortune->l.next = fortune_ctx->result;
+				fortune_ctx->result = &fortune->l;
+				fortune_ctx->num_result++;
+
+				if (!i)
+					fortune->data = result;
+			}
+			else {
+				cleanup_fortunes(&fortune_ctx->generator, fortune_ctx->req);
+				send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, fortune_ctx->req);
+				ret = DONE;
+
+				if (!i)
+					PQclear(result);
+
+				break;
+			}
+		}
+	}
+	else if (result)
+		PQclear(result);
+	else {
+		mustache_api_t api = {.sectget = on_fortune_section,
+		                      .varget = on_fortune_variable,
+		                      .write = add_iovec};
+		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);
+		char _Alignas(iovec_list_t) mem[sz];
+		iovec_list_t * const iovec_list = (iovec_list_t *) mem;
+
+		memset(iovec_list, 0, offsetof(iovec_list_t, iov));
+		iovec_list->max_iovcnt = iovcnt;
+		fortune_ctx->iovec_list_iter = iovec_list;
+		fortune_ctx->result = sort_fortunes(fortune_ctx->result);
+
+		if (mustache_render(&api, fortune_ctx, ctx->global_data->fortunes_template)) {
+			fortune_ctx->iovec_list = iovec_list->l.next;
+			set_default_response_param(HTML, fortune_ctx->req);
+			h2o_start_response(fortune_ctx->req, &fortune_ctx->generator);
+			h2o_send(fortune_ctx->req,
+			         iovec_list->iov,
+			         iovec_list->iovcnt,
+			         false);
+		}
+		else {
+			cleanup_fortunes(&fortune_ctx->generator, fortune_ctx->req);
+			send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, fortune_ctx->req);
+			ret = DONE;
+		}
+	}
+
+	return ret;
+}
+
+static uintmax_t on_fortune_section(mustache_api_t *api,
+                                    void *userdata,
+                                    mustache_token_section_t *token)
+{
+	fortune_ctx_t * const fortune_ctx = userdata;
+	uintmax_t ret = 1;
+
+	if (fortune_ctx->num_result) {
+		assert(fortune_ctx->result);
+
+		const list_t *iter = fortune_ctx->result;
+
+		do {
+			fortune_ctx->fortune_iter = H2O_STRUCT_FROM_MEMBER(fortune_t, l, iter);
+			iter = iter->next;
+
+			if (!mustache_render(api, fortune_ctx, token->section)) {
+				ret = 0;
+				break;
+			}
+		} while (iter);
+	}
+
+	return ret;
+}
+
+static void on_fortune_timeout(db_query_param_t *param)
+{
+	fortune_ctx_t * const fortune_ctx = H2O_STRUCT_FROM_MEMBER(fortune_ctx_t,
+	                                                           param,
+	                                                           param);
+
+	cleanup_fortunes(&fortune_ctx->generator, fortune_ctx->req);
+	send_error(GATEWAY_TIMEOUT, DB_TIMEOUT_ERROR, fortune_ctx->req);
+}
+
+static uintmax_t on_fortune_variable(mustache_api_t *api,
+                                     void *userdata,
+                                     mustache_token_variable_t *token)
+{
+	fortune_ctx_t * const fortune_ctx = userdata;
+	const h2o_iovec_t * const iovec = (const h2o_iovec_t *)
+		((const char *) fortune_ctx->fortune_iter + (size_t) token->userdata);
+
+	return add_iovec(api, fortune_ctx, iovec->base, iovec->len);
+}
+
+// merge sort
+static list_t *sort_fortunes(list_t *head)
+{
+	list_t **new_head;
+
+	do {
+		new_head = &head;
+
+		for (list_t *iter = head; iter;) {
+			list_t * const tail1 = get_sorted_sublist(iter);
+			list_t * const head2 = tail1->next;
+
+			if (!head2) {
+				*new_head = iter;
+				break;
+			}
+
+			list_t * const tail2 = get_sorted_sublist(head2);
+			list_t * const head1 = iter;
+
+			iter = tail2->next;
+			tail1->next = NULL;
+			tail2->next = NULL;
+			*new_head = merge_lists(head1, head2);
+			new_head = tail1->next ? &tail2->next : &tail1->next;
+		}
+	} while (new_head != &head);
+
+	return head;
+}
+
+int fortunes(struct st_h2o_handler_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(self);
+
+	thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+	                                                      event_loop.h2o_ctx,
+	                                                      req->conn->ctx);
+	fortune_ctx_t * const fortune_ctx = h2o_mem_alloc_pool(&req->pool, sizeof(*fortune_ctx));
+
+	if (fortune_ctx) {
+		fortune_t * const fortune = h2o_mem_alloc_pool(&req->pool, sizeof(*fortune));
+
+		if (fortune) {
+			memset(fortune, 0, sizeof(*fortune));
+			fortune->id.base = NEW_FORTUNE_ID;
+			fortune->id.len = sizeof(NEW_FORTUNE_ID) - 1;
+			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->generator.stop = cleanup_fortunes;
+			fortune_ctx->num_result = 1;
+			fortune_ctx->param.command = FORTUNE_TABLE_NAME;
+			fortune_ctx->param.on_error = on_fortune_error;
+			fortune_ctx->param.on_result = on_fortune_result;
+			fortune_ctx->param.on_timeout = on_fortune_timeout;
+			fortune_ctx->param.flags = IS_PREPARED;
+			fortune_ctx->req = req;
+			fortune_ctx->result = &fortune->l;
+
+			if (execute_query(ctx, &fortune_ctx->param))
+				send_service_unavailable_error(DB_REQ_ERROR, req);
+		}
+	}
+	else
+		send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, req);
+
+	return 0;
+}

+ 38 - 0
frameworks/C/h2o/src/fortune.h

@@ -0,0 +1,38 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef FORTUNE_H_
+
+#define FORTUNE_H_
+
+#include <h2o.h>
+#include <postgresql/libpq-fe.h>
+
+#include "list.h"
+
+typedef struct {
+	list_t l;
+	PGresult *data;
+	h2o_iovec_t id;
+	h2o_iovec_t message;
+} fortune_t;
+
+int fortunes(struct st_h2o_handler_t *self, h2o_req_t *req);
+
+#endif // FORTUNE_H_

+ 35 - 0
frameworks/C/h2o/src/list.h

@@ -0,0 +1,35 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef LIST_H_
+
+#define LIST_H_
+
+typedef struct list_t list_t;
+
+struct list_t {
+	list_t *next;
+};
+
+typedef struct {
+	list_t *head;
+	list_t **tail;
+} queue_t;
+
+#endif // LIST_H_

+ 366 - 0
frameworks/C/h2o/src/main.c

@@ -0,0 +1,366 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <assert.h>
+#include <errno.h>
+#include <h2o.h>
+#include <mustache.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <h2o/serverutil.h>
+#include <netinet/tcp.h>
+#include <sys/resource.h>
+#include <sys/signalfd.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include "error.h"
+#include "event_loop.h"
+#include "request_handler.h"
+#include "template.h"
+#include "thread.h"
+#include "tls.h"
+#include "utility.h"
+
+#define DEFAULT_TCP_FASTOPEN_QUEUE_LEN 4096
+#define USAGE_MESSAGE \
+	"Usage:\n%s [-a <max connections accepted simultaneously>] [-b <bind address>] " \
+	"[-c <certificate file>] [-d <database connection string>] [-f fortunes template file path] " \
+	"[-k <private key file>] [-l <log path>] " \
+	"[-m <max database connections per thread>] [-p <port>] " \
+	"[-q <max enqueued database queries per thread>] [-r <root directory>] " \
+	"[-t <thread number>]\n"
+
+static void free_global_data(global_data_t *global_data);
+static int get_listener_socket(const config_t *config);
+static size_t get_maximum_cache_line_size(void);
+static int initialize_global_data(const config_t *config, global_data_t *global_data);
+static int parse_options(int argc, char *argv[], config_t *config);
+static void set_default_options(config_t *config);
+static void setup_process(void);
+
+static void free_global_data(global_data_t *global_data)
+{
+	if (global_data->ctx)
+		free_thread_contexts(global_data);
+
+	if (global_data->file_logger)
+		global_data->file_logger->dispose(global_data->file_logger);
+
+	if (global_data->fortunes_template) {
+		mustache_api_t api = {.freedata = NULL};
+
+		mustache_free(&api, global_data->fortunes_template);
+	}
+
+	h2o_config_dispose(&global_data->h2o_config);
+
+	if (global_data->ssl_ctx)
+		cleanup_openssl(global_data);
+}
+
+static int get_listener_socket(const config_t *config)
+{
+	int ret = -1;
+	char port[16];
+
+	if ((size_t) snprintf(port, sizeof(port), "%u", (unsigned) config->port) >= sizeof(port)) {
+		print_error(__FILE__, __LINE__, "snprintf", "Truncated output.");
+		return ret;
+	}
+
+	struct addrinfo *res = NULL;
+	struct addrinfo hints = {.ai_socktype = SOCK_STREAM, .ai_flags = AI_PASSIVE};
+	const int error_code = getaddrinfo(config->bind_address, port, &hints, &res);
+
+	if (error_code) {
+		print_error(__FILE__, __LINE__, "getaddrinfo", gai_strerror(error_code));
+		return ret;
+	}
+
+	struct addrinfo *iter = res;
+
+	for (; iter; iter = iter->ai_next) {
+		const int s = socket(iter->ai_family,
+		                     iter->ai_socktype | SOCK_NONBLOCK | SOCK_CLOEXEC,
+		                     iter->ai_protocol);
+
+		if (s == -1) {
+			LIBRARY_ERROR("socket");
+			continue;
+		}
+
+#define LOCAL_CHECK_ERRNO(function, ...) \
+	do { \
+		const int error_code = (function)(__VA_ARGS__); \
+		\
+		if (error_code) { \
+			print_library_error(__FILE__, __LINE__, #function, errno); \
+			goto error; \
+		} \
+	} while(0)
+
+		int option = 1;
+
+		LOCAL_CHECK_ERRNO(setsockopt, s, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
+		LOCAL_CHECK_ERRNO(setsockopt, s, IPPROTO_TCP, TCP_QUICKACK, &option, sizeof(option));
+		option = H2O_DEFAULT_HANDSHAKE_TIMEOUT_IN_SECS;
+		LOCAL_CHECK_ERRNO(setsockopt, s, IPPROTO_TCP, TCP_DEFER_ACCEPT, &option, sizeof(option));
+		option = DEFAULT_TCP_FASTOPEN_QUEUE_LEN;
+		LOCAL_CHECK_ERRNO(setsockopt, s, IPPROTO_TCP, TCP_FASTOPEN, &option, sizeof(option));
+		LOCAL_CHECK_ERRNO(bind, s, iter->ai_addr, iter->ai_addrlen);
+		LOCAL_CHECK_ERRNO(listen, s, INT_MAX);
+		ret = s;
+		break;
+
+#undef LOCAL_CHECK_ERRNO
+
+error:
+		close(s);
+	}
+
+	freeaddrinfo(res);
+	return ret;
+}
+
+static size_t get_maximum_cache_line_size(void)
+{
+	const int name[] = {_SC_LEVEL1_DCACHE_LINESIZE,
+	                    _SC_LEVEL2_CACHE_LINESIZE,
+	                    _SC_LEVEL3_CACHE_LINESIZE,
+	                    _SC_LEVEL4_CACHE_LINESIZE};
+	size_t ret = 0;
+
+	for (size_t i = 0; i < ARRAY_SIZE(name); i++) {
+		errno = 0;
+
+		const long rc = sysconf(name[i]);
+
+		if (rc < 0) {
+			if (errno)
+				LIBRARY_ERROR("sysconf");
+		}
+		else if ((size_t) rc > ret)
+			ret = rc;
+	}
+
+	if (!ret)
+		ret = DEFAULT_CACHE_LINE_SIZE;
+
+	return ret;
+}
+
+static int initialize_global_data(const config_t *config, global_data_t *global_data)
+{
+	sigset_t signals;
+
+	memset(global_data, 0, sizeof(*global_data));
+	global_data->config = config;
+	global_data->memory_alignment = get_maximum_cache_line_size();
+	assert(global_data->memory_alignment <= DEFAULT_CACHE_LINE_SIZE);
+	CHECK_ERRNO(sigemptyset, &signals);
+#ifdef NDEBUG
+	CHECK_ERRNO(sigaddset, &signals, SIGINT);
+#endif // NDEBUG
+	CHECK_ERRNO(sigaddset, &signals, SIGTERM);
+	CHECK_ERRNO_RETURN(global_data->signal_fd, signalfd, -1, &signals, SFD_NONBLOCK | SFD_CLOEXEC);
+	global_data->fortunes_template = get_fortunes_template(config->template_path);
+	h2o_config_init(&global_data->h2o_config);
+	global_data->listener_sd = get_listener_socket(config);
+
+	if (global_data->listener_sd == -1)
+		goto error;
+
+	if (config->cert && config->key)
+		initialize_openssl(global_data);
+
+	const h2o_iovec_t host = h2o_iovec_init(H2O_STRLIT("default"));
+	h2o_hostconf_t * const hostconf = h2o_config_register_host(&global_data->h2o_config,
+	                                                           host,
+	                                                           config->port);
+	h2o_access_log_filehandle_t *log_handle = NULL;
+
+	if (config->log) {
+		log_handle = h2o_access_log_open_handle(config->log, NULL);
+
+		if (!log_handle)
+			goto error;
+	}
+
+	register_request_handlers(hostconf, log_handle);
+
+	// Must be registered after the rest of the request handlers.
+	if (config->root) {
+		h2o_pathconf_t * const pathconf = h2o_config_register_path(hostconf, "/", 0);
+		h2o_file_register(pathconf, config->root, NULL, NULL, 0);
+
+		if (log_handle)
+			global_data->file_logger = h2o_access_log_register(pathconf, log_handle);
+	}
+
+	global_data->ctx = initialize_thread_contexts(global_data);
+
+	if (global_data->ctx)
+		return EXIT_SUCCESS;
+
+error:
+	free_global_data(global_data);
+	return EXIT_FAILURE;
+}
+
+static int parse_options(int argc, char *argv[], config_t *config)
+{
+	memset(config, 0, sizeof(*config));
+	opterr = 0;
+
+	while (1) {
+		const int opt = getopt(argc, argv, "?a:b:c:d:f:k:l:m:p:q:r:t:");
+
+		if (opt == -1)
+			break;
+
+		switch (opt) {
+
+#define PARSE_NUMBER(out) \
+	do { \
+		errno = 0; \
+		\
+		const long long n = strtoll(optarg, NULL, 10); \
+		\
+		if (errno) { \
+			print_library_error(__FILE__, __LINE__, "strtoll", errno); \
+			return EXIT_FAILURE; \
+		} \
+		\
+		(out) = n; \
+	} while(0)
+
+			case 'a':
+				PARSE_NUMBER(config->max_accept);
+				break;
+			case 'b':
+				config->bind_address = optarg;
+				break;
+			case 'c':
+				config->cert = optarg;
+				break;
+			case 'd':
+				config->db_host = optarg;
+				break;
+			case 'f':
+				config->template_path = optarg;
+				break;
+			case 'k':
+				config->key = optarg;
+				break;
+			case 'l':
+				config->log = optarg;
+				break;
+			case 'm':
+				PARSE_NUMBER(config->max_db_conn_num);
+				break;
+			case 'p':
+				PARSE_NUMBER(config->port);
+				break;
+			case 'q':
+				PARSE_NUMBER(config->max_query_num);
+				break;
+			case 'r':
+				config->root = optarg;
+				break;
+			case 't':
+				PARSE_NUMBER(config->thread_num);
+				break;
+			default:
+				fprintf(stderr, USAGE_MESSAGE, *argv);
+				return EXIT_FAILURE;
+
+#undef PARSE_NUMBER
+		}
+	}
+
+	set_default_options(config);
+	return EXIT_SUCCESS;
+}
+
+static void set_default_options(config_t *config)
+{
+	if (!config->max_accept)
+		config->max_accept = 10;
+
+	if (!config->max_db_conn_num)
+		config->max_db_conn_num = 10;
+
+	if (!config->max_query_num)
+		config->max_query_num = 10000;
+
+	if (!config->port)
+		config->port = 8080;
+
+	if (!config->thread_num)
+		config->thread_num = h2o_numproc();
+}
+
+static void setup_process(void)
+{
+	sigset_t signals;
+
+	CHECK_ERRNO(sigfillset, &signals);
+	CHECK_ERRNO(sigdelset, &signals, SIGBUS);
+	CHECK_ERRNO(sigdelset, &signals, SIGFPE);
+	CHECK_ERRNO(sigdelset, &signals, SIGILL);
+	CHECK_ERRNO(sigdelset, &signals, SIGSEGV);
+#ifndef NDEBUG
+	CHECK_ERRNO(sigdelset, &signals, SIGINT);
+#endif // NDEBUG
+	CHECK_ERROR(pthread_sigmask, SIG_BLOCK, &signals, NULL);
+
+	struct rlimit rlim = {.rlim_cur = 0};
+
+	CHECK_ERRNO(getrlimit, RLIMIT_NOFILE, &rlim);
+	rlim.rlim_cur = rlim.rlim_max;
+	CHECK_ERRNO(setrlimit, RLIMIT_NOFILE, &rlim);
+}
+
+int main(int argc, char *argv[])
+{
+	config_t config;
+	int rc = EXIT_FAILURE;
+
+	if (parse_options(argc, argv, &config) == EXIT_SUCCESS) {
+		global_data_t global_data;
+
+		if (initialize_global_data(&config, &global_data) == EXIT_SUCCESS) {
+			setup_process();
+			start_threads(global_data.ctx);
+			connect_to_database(global_data.ctx);
+			event_loop(global_data.ctx);
+			free_global_data(&global_data);
+			rc = EXIT_SUCCESS;
+		}
+	}
+
+	return rc;
+}

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

@@ -0,0 +1,255 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <assert.h>
+#include <h2o.h>
+#include <stdalign.h>
+#include <string.h>
+#include <yajl/yajl_gen.h>
+
+#include "error.h"
+#include "fortune.h"
+#include "request_handler.h"
+#include "utility.h"
+#include "world.h"
+
+typedef struct {
+	yajl_gen gen;
+	h2o_generator_t h2o_generator;
+} json_ctx_t;
+
+static void cleanup_json_response(struct st_h2o_generator_t *self, h2o_req_t *req);
+static void complete_json_response(struct st_h2o_generator_t *self, h2o_req_t *req);
+static int json_serializer(struct st_h2o_handler_t *self, h2o_req_t *req);
+static int plaintext(struct st_h2o_handler_t *self, h2o_req_t *req);
+static const char *status_code_to_string(http_status_code_t status_code);
+
+static void cleanup_json_response(struct st_h2o_generator_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(req);
+
+	json_ctx_t * const json_ctx = H2O_STRUCT_FROM_MEMBER(json_ctx_t, h2o_generator, self);
+
+	yajl_gen_free(json_ctx->gen);
+}
+
+static void complete_json_response(struct st_h2o_generator_t *self, h2o_req_t *req)
+{
+	h2o_send(req, NULL, 0, true);
+	cleanup_json_response(self, req);
+}
+
+static int json_serializer(struct st_h2o_handler_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(self);
+
+	json_ctx_t * const json_ctx = h2o_mem_alloc_pool(&req->pool, sizeof(*json_ctx));
+
+	if (json_ctx) {
+		memset(json_ctx, 0, sizeof(*json_ctx));
+		json_ctx->gen = get_json_generator(&req->pool);
+		json_ctx->h2o_generator.proceed = complete_json_response;
+		json_ctx->h2o_generator.stop = cleanup_json_response;
+
+		if (json_ctx->gen) {
+			CHECK_YAJL_STATUS(yajl_gen_map_open, json_ctx->gen);
+			CHECK_YAJL_STATUS(yajl_gen_string, json_ctx->gen, YAJL_STRLIT("message"));
+			CHECK_YAJL_STATUS(yajl_gen_string, json_ctx->gen, YAJL_STRLIT("Hello, World!"));
+			CHECK_YAJL_STATUS(yajl_gen_map_close, json_ctx->gen);
+
+			if (!send_json_response(json_ctx->gen, &json_ctx->h2o_generator, req))
+				return 0;
+
+error_yajl:
+			yajl_gen_free(json_ctx->gen);
+		}
+	}
+
+	send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, req);
+	return 0;
+}
+
+static int plaintext(struct st_h2o_handler_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(self);
+	set_default_response_param(PLAIN, req);
+	h2o_send_inline(req, H2O_STRLIT("Hello, World!"));
+	return 0;
+}
+
+static const char *status_code_to_string(http_status_code_t status_code)
+{
+	const char *ret;
+
+	switch (status_code) {
+		case BAD_GATEWAY:
+			ret = "Bad Gateway";
+			break;
+		case GATEWAY_TIMEOUT:
+			ret = "Gateway Timeout";
+			break;
+		case INTERNAL_SERVER_ERROR:
+			ret = "Internal Server Error";
+			break;
+		case OK:
+			ret = "OK";
+			break;
+		case SERVICE_UNAVAILABLE:
+			ret = "Service Unavailable";
+			break;
+		default:
+			ret = "";
+	}
+
+	return ret;
+}
+
+const char *get_query_param(const char *query,
+                            size_t query_len,
+                            const char *param,
+                            size_t param_len)
+{
+	const char *ret = NULL;
+
+	while (param_len < query_len) {
+		if (!memcmp(param, query, param_len)) {
+			ret = query + param_len;
+			break;
+		}
+
+		const char * const next = memchr(query, '&', query_len);
+
+		if (!next)
+			break;
+
+		query_len -= next + 1 - query;
+		query = next + 1;
+	}
+
+	return ret;
+}
+
+void register_request_handlers(h2o_hostconf_t *hostconf, h2o_access_log_filehandle_t *log_handle)
+{
+	h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, "/json", 0);
+	h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler));
+
+	if (log_handle)
+		h2o_access_log_register(pathconf, log_handle);
+
+	handler->on_req = json_serializer;
+	pathconf = h2o_config_register_path(hostconf, "/db", 0);
+	handler = h2o_create_handler(pathconf, sizeof(*handler));
+	handler->on_req = single_query;
+
+	if (log_handle)
+		h2o_access_log_register(pathconf, log_handle);
+
+	pathconf = h2o_config_register_path(hostconf, "/queries", 0);
+	handler = h2o_create_handler(pathconf, sizeof(*handler));
+	handler->on_req = multiple_queries;
+
+	if (log_handle)
+		h2o_access_log_register(pathconf, log_handle);
+
+	pathconf = h2o_config_register_path(hostconf, "/fortunes", 0);
+	handler = h2o_create_handler(pathconf, sizeof(*handler));
+	handler->on_req = fortunes;
+
+	if (log_handle)
+		h2o_access_log_register(pathconf, log_handle);
+
+	pathconf = h2o_config_register_path(hostconf, "/updates", 0);
+	handler = h2o_create_handler(pathconf, sizeof(*handler));
+	handler->on_req = updates;
+
+	if (log_handle)
+		h2o_access_log_register(pathconf, log_handle);
+
+	pathconf = h2o_config_register_path(hostconf, "/plaintext", 0);
+	handler = h2o_create_handler(pathconf, sizeof(*handler));
+	handler->on_req = plaintext;
+
+	if (log_handle)
+		h2o_access_log_register(pathconf, log_handle);
+}
+
+void send_error(http_status_code_t status_code, const char *body, h2o_req_t *req)
+{
+	h2o_send_error_generic(req, status_code, status_code_to_string(status_code), body, 0);
+}
+
+int send_json_response(yajl_gen gen, h2o_generator_t *h2o_generator, h2o_req_t *req)
+{
+	h2o_iovec_t h2o_iovec = {.len = 0};
+	int ret = EXIT_FAILURE;
+
+	static_assert(sizeof(h2o_iovec.base) == sizeof(const unsigned char *) &&
+	              alignof(h2o_iovec.base) == alignof(const unsigned char *),
+	              "Types must be compatible.");
+
+	if (yajl_gen_get_buf(gen,
+	                     (const unsigned char **) &h2o_iovec.base,
+	                     &h2o_iovec.len) == yajl_gen_status_ok) {
+		set_default_response_param(JSON, req);
+		h2o_start_response(req, h2o_generator);
+		h2o_send(req, &h2o_iovec, 1, false);
+		ret = EXIT_SUCCESS;
+	}
+
+	return ret;
+}
+
+void send_service_unavailable_error(const char *body, h2o_req_t *req)
+{
+	h2o_add_header(&req->pool,
+	               &req->res.headers,
+	               H2O_TOKEN_RETRY_AFTER,
+	               H2O_STRLIT(MKSTR(H2O_DEFAULT_HTTP1_REQ_TIMEOUT_IN_SECS)));
+	h2o_send_error_503(req,
+	                   status_code_to_string(SERVICE_UNAVAILABLE),
+	                   body,
+	                   H2O_SEND_ERROR_KEEP_HEADERS);
+}
+
+void set_default_response_param(content_type_t content_type, h2o_req_t *req)
+{
+	req->res.status = OK;
+	req->res.reason = status_code_to_string(req->res.status);
+
+	switch (content_type) {
+		case JSON:
+			h2o_add_header(&req->pool,
+			               &req->res.headers,
+			               H2O_TOKEN_CONTENT_TYPE,
+			               H2O_STRLIT("application/json"));
+			break;
+		case PLAIN:
+			h2o_add_header(&req->pool,
+			               &req->res.headers,
+			               H2O_TOKEN_CONTENT_TYPE,
+			               H2O_STRLIT("text/plain"));
+			break;
+		default:
+			h2o_add_header(&req->pool,
+			               &req->res.headers,
+			               H2O_TOKEN_CONTENT_TYPE,
+			               H2O_STRLIT("text/html; charset=utf-8"));
+	}
+}

+ 51 - 0
frameworks/C/h2o/src/request_handler.h

@@ -0,0 +1,51 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef REQUEST_H_
+
+#define REQUEST_H_
+
+#include <h2o.h>
+#include <yajl/yajl_gen.h>
+
+typedef enum {
+	HTML,
+	JSON,
+	PLAIN
+} content_type_t;
+
+typedef enum {
+	OK = 200,
+	INTERNAL_SERVER_ERROR = 500,
+	BAD_GATEWAY = 502,
+	SERVICE_UNAVAILABLE = 503,
+	GATEWAY_TIMEOUT = 504
+} http_status_code_t;
+
+const char *get_query_param(const char *query,
+                            size_t query_len,
+                            const char *param,
+                            size_t param_len);
+void register_request_handlers(h2o_hostconf_t *hostconf, h2o_access_log_filehandle_t *log_handle);
+void send_error(http_status_code_t status_code, const char *body, h2o_req_t *req);
+int send_json_response(yajl_gen gen, h2o_generator_t *h2o_generator, h2o_req_t *req);
+void send_service_unavailable_error(const char *body, h2o_req_t *req);
+void set_default_response_param(content_type_t content_type, h2o_req_t *req);
+
+#endif // REQUEST_H_

+ 138 - 0
frameworks/C/h2o/src/template.c

@@ -0,0 +1,138 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <mustache.h>
+
+#include "database.h"
+#include "error.h"
+#include "fortune.h"
+#include "template.h"
+#include "utility.h"
+
+typedef struct {
+	FILE *input;
+	const char *name;
+} template_input_t;
+
+static uintmax_t prerender_section(mustache_api_t *api,
+                                   void *userdata,
+                                   mustache_token_section_t *token);
+static uintmax_t prerender_variable(mustache_api_t *api,
+                                    void *userdata,
+                                    mustache_token_variable_t *token);
+static uintmax_t read_template(mustache_api_t *api,
+                               void *userdata,
+                               char *buffer,
+                               uintmax_t buffer_size);
+static void template_error(mustache_api_t *api, void *userdata, uintmax_t lineno, char *error);
+
+static uintmax_t prerender_section(mustache_api_t *api,
+                                   void *userdata,
+                                   mustache_token_section_t *token)
+{
+	bool * const in_section = userdata;
+	uintmax_t ret = 0;
+
+	if (!*in_section && !strcmp(token->name, FORTUNE_TABLE_NAME)) {
+		*in_section = true;
+		ret = mustache_prerender(api, userdata, token->section);
+		*in_section = false;
+	}
+
+	return ret;
+}
+
+static uintmax_t prerender_variable(mustache_api_t *api,
+                                    void *userdata,
+                                    mustache_token_variable_t *token)
+{
+	IGNORE_FUNCTION_PARAMETER(api);
+
+	bool * const in_section = userdata;
+	uintmax_t ret = 0;
+
+	if (*in_section) {
+		if (token->text_length == sizeof(ID_FIELD_NAME) - 1 &&
+		    !memcmp(token->text, ID_FIELD_NAME, sizeof(ID_FIELD_NAME) - 1)) {
+			token->userdata = (void *) offsetof(fortune_t, id);
+			ret = 1;
+		}
+		else if (token->text_length == sizeof(MESSAGE_FIELD_NAME) - 1 &&
+		         !memcmp(token->text, MESSAGE_FIELD_NAME, sizeof(MESSAGE_FIELD_NAME) - 1)) {
+			token->userdata = (void *) offsetof(fortune_t, message);
+			ret = 1;
+		}
+	}
+
+	return ret;
+}
+
+static uintmax_t read_template(mustache_api_t *api,
+                               void *userdata,
+                               char *buffer,
+                               uintmax_t buffer_size)
+{
+	IGNORE_FUNCTION_PARAMETER(api);
+
+	const template_input_t * const template_input = userdata;
+
+	return fread(buffer, sizeof(*buffer), buffer_size, template_input->input);
+}
+
+static void template_error(mustache_api_t *api, void *userdata, uintmax_t lineno, char *error)
+{
+	IGNORE_FUNCTION_PARAMETER(api);
+
+	const template_input_t * const template_input = userdata;
+
+	print_error(template_input->name, lineno, "mustache_compile", error);
+}
+
+mustache_template_t *get_fortunes_template(const char *path)
+{
+	mustache_template_t *ret = NULL;
+	template_input_t template_input = {.input = fopen(path, "rb"), .name = path};
+
+	if (template_input.input) {
+		mustache_api_t api = {.error = template_error,
+		                      .read = read_template,
+		                      .sectget = prerender_section,
+		                      .varget = prerender_variable};
+		bool in_section = false;
+
+		ret = mustache_compile(&api, &template_input);
+
+		if (ret && !mustache_prerender(&api, &in_section, ret)) {
+			mustache_free(&api, ret);
+			ret = NULL;
+		}
+
+		fclose(template_input.input);
+	}
+	else
+		LIBRARY_ERROR("fopen");
+
+	return ret;
+}

+ 28 - 0
frameworks/C/h2o/src/template.h

@@ -0,0 +1,28 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef TEMPLATE_H_
+
+#define TEMPLATE_H_
+
+#include <mustache.h>
+
+mustache_template_t *get_fortunes_template(const char *path);
+
+#endif // TEMPLATE_H_

+ 107 - 0
frameworks/C/h2o/src/thread.c

@@ -0,0 +1,107 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <h2o.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <h2o/serverutil.h>
+#include <sys/epoll.h>
+
+#include "database.h"
+#include "error.h"
+#include "event_loop.h"
+#include "thread.h"
+
+static void *run_thread(void *arg);
+
+static void *run_thread(void *arg)
+{
+	connect_to_database(arg);
+	event_loop(arg);
+	pthread_exit(NULL);
+}
+
+void free_thread_contexts(global_data_t *global_data)
+{
+	thread_context_t * const ctx = global_data->ctx;
+
+	for (size_t i = 0; i < ctx->global_data->config->thread_num; i++) {
+		if (i)
+			CHECK_ERROR(pthread_join, ctx[i].thread, NULL);
+		else
+			// Even though this is global data, we need to close
+			// it before the associated event loop is cleaned up.
+			h2o_socket_close(global_data->signals);
+
+		free_database_state(ctx[i].event_loop.h2o_ctx.loop, &ctx[i].db_state);
+		free_event_loop(&ctx[i].event_loop);
+	}
+
+	free(ctx);
+}
+
+thread_context_t *initialize_thread_contexts(global_data_t *global_data)
+{
+	const size_t sz = global_data->config->thread_num * sizeof(thread_context_t);
+	thread_context_t * const ret = aligned_alloc(global_data->memory_alignment, sz);
+
+	if (ret) {
+		memset(ret, 0, sz);
+
+		for (size_t i = 0; i < global_data->config->thread_num; i++) {
+			ret[i].global_data = global_data;
+			initialize_event_loop(!i, global_data, &ret[i].event_loop);
+			initialize_database_state(ret[i].event_loop.h2o_ctx.loop, &ret[i].db_state);
+		}
+	}
+
+	return ret;
+}
+
+void start_threads(thread_context_t *ctx)
+{
+	const size_t num_cpus = h2o_numproc();
+
+	// The first thread context is used by the main thread.
+	ctx->thread = pthread_self();
+
+	for (size_t i = 1; i < ctx->global_data->config->thread_num; i++)
+		CHECK_ERROR(pthread_create, &ctx[i].thread, NULL, run_thread, ctx + i);
+
+	// If the number of threads is not equal to the number of processors, then let the scheduler
+	// decide how to balance the load.
+	if (ctx->global_data->config->thread_num == num_cpus) {
+		const size_t cpusetsize = CPU_ALLOC_SIZE(num_cpus);
+		cpu_set_t * const cpuset = CPU_ALLOC(num_cpus);
+
+		if (!cpuset)
+			abort();
+
+		for (size_t i = 0; i < ctx->global_data->config->thread_num; i++) {
+			CPU_ZERO_S(cpusetsize, cpuset);
+			CPU_SET_S(i, cpusetsize, cpuset);
+			CHECK_ERROR(pthread_setaffinity_np, ctx[i].thread, cpusetsize, cpuset);
+		}
+
+		CPU_FREE(cpuset);
+	}
+}

+ 56 - 0
frameworks/C/h2o/src/thread.h

@@ -0,0 +1,56 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef THREAD_H_
+
+#define THREAD_H_
+
+#include <assert.h>
+#include <h2o.h>
+#include <pthread.h>
+#include <sys/types.h>
+
+#include "database.h"
+#include "event_loop.h"
+#include "utility.h"
+
+#define DEFAULT_CACHE_LINE_SIZE 64
+
+typedef struct thread_context_t thread_context_t;
+
+struct thread_context_t {
+	global_data_t *global_data;
+	unsigned random_seed;
+	pid_t tid;
+	db_state_t db_state;
+	event_loop_t event_loop;
+	pthread_t thread;
+	// Align on the cache line size to prevent false sharing.
+	char padding[49];
+};
+
+static_assert(!(sizeof(thread_context_t) % DEFAULT_CACHE_LINE_SIZE),
+              "The size of the thread_context_t structure must be a "
+              "multiple of the cache line size.");
+
+void free_thread_contexts(global_data_t *global_data);
+thread_context_t *initialize_thread_contexts(global_data_t *global_data);
+void start_threads(thread_context_t *ctx);
+
+#endif // THREAD_H_

+ 169 - 0
frameworks/C/h2o/src/tls.c

@@ -0,0 +1,169 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <assert.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <openssl/conf.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+#include "error.h"
+#include "tls.h"
+#include "utility.h"
+
+#define CHECK_OPENSSL_ERROR(function, ...) \
+	do { \
+		const int error_code = (function)(__VA_ARGS__); \
+		\
+		if (error_code != 1) { \
+			const unsigned long openssl_error = ERR_get_error(); \
+			char buf[128] = ""; \
+			\
+			ERR_error_string_n(openssl_error, buf, sizeof(buf)); \
+			print_error(__FILE__, __LINE__, #function, "%s (%lu)", buf, openssl_error); \
+			abort(); \
+		} \
+	} while(0)
+
+struct CRYPTO_dynlock_value {
+	pthread_mutex_t mutex;
+};
+
+static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line);
+static void dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line);
+static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line);
+static void locking_function(int mode, int n, const char *file, int line);
+
+static struct {
+	pthread_mutex_t *lock;
+	size_t num_lock;
+	pthread_mutexattr_t lock_attr;
+} openssl_global_data;
+
+static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line)
+{
+	struct CRYPTO_dynlock_value *ret = malloc(sizeof(*ret));
+
+	if (ret) {
+		const int error_code = pthread_mutex_init(&ret->mutex, &openssl_global_data.lock_attr);
+
+		if (error_code) {
+			print_library_error(file, line, "pthread_mutex_init", error_code);
+			free(ret);
+			ret = NULL;
+		}
+	}
+
+	return ret;
+}
+
+static void dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line)
+{
+	const int error_code = pthread_mutex_destroy(&l->mutex);
+
+	if (error_code)
+		print_library_error(file, line, "pthread_mutex_destroy", error_code);
+
+	free(l);
+}
+
+static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
+{
+	const char *function;
+	int error_code;
+
+	if (mode & CRYPTO_LOCK) {
+		function = "pthread_mutex_lock";
+		error_code = pthread_mutex_lock(&l->mutex);
+	}
+	else {
+		function = "pthread_mutex_unlock";
+		error_code = pthread_mutex_unlock(&l->mutex);
+	}
+
+	if (error_code) {
+		print_library_error(file, line, function, error_code);
+		abort();
+	}
+}
+
+static void locking_function(int mode, int n, const char *file, int line)
+{
+	assert((size_t) n < openssl_global_data.num_lock);
+	static_assert(!offsetof(struct CRYPTO_dynlock_value, mutex),
+	              "The mutex must be the first field in struct CRYPTO_dynlock_value.");
+
+	dyn_lock_function(mode, (struct CRYPTO_dynlock_value *) (openssl_global_data.lock + n), file, line);
+}
+
+void cleanup_openssl(global_data_t *global_data)
+{
+	SSL_CTX_free(global_data->ssl_ctx);
+	CRYPTO_set_locking_callback(NULL);
+	CRYPTO_set_id_callback(NULL);
+	CRYPTO_set_dynlock_create_callback(NULL);
+	CRYPTO_set_dynlock_destroy_callback(NULL);
+	CRYPTO_set_dynlock_lock_callback(NULL);
+	ERR_remove_state(0);
+	ERR_free_strings();
+	CONF_modules_unload(1);
+	EVP_cleanup();
+	CRYPTO_cleanup_all_ex_data();
+
+	for (size_t i = 0; i < openssl_global_data.num_lock; i++)
+		CHECK_ERROR(pthread_mutex_destroy, openssl_global_data.lock + i);
+
+	free(openssl_global_data.lock);
+	CHECK_ERROR(pthread_mutexattr_destroy, &openssl_global_data.lock_attr);
+}
+
+void initialize_openssl(global_data_t *global_data)
+{
+	SSL_library_init();
+	SSL_load_error_strings();
+	openssl_global_data.num_lock = CRYPTO_num_locks();
+	openssl_global_data.lock = malloc(openssl_global_data.num_lock *
+	                                  sizeof(*openssl_global_data.lock));
+	CHECK_ERROR(pthread_mutexattr_init, &openssl_global_data.lock_attr);
+	CHECK_ERROR(pthread_mutexattr_settype,
+	            &openssl_global_data.lock_attr,
+	            PTHREAD_MUTEX_ADAPTIVE_NP);
+
+	for (size_t i = 0; i < openssl_global_data.num_lock; i++)
+		CHECK_ERROR(pthread_mutex_init,
+		            openssl_global_data.lock + i,
+		            &openssl_global_data.lock_attr);
+
+	CRYPTO_set_locking_callback(locking_function);
+	CRYPTO_set_dynlock_create_callback(dyn_create_function);
+	CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
+	CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
+	global_data->ssl_ctx = SSL_CTX_new(TLSv1_2_server_method());
+	CHECK_OPENSSL_ERROR(SSL_CTX_use_certificate_file,
+	                    global_data->ssl_ctx,
+	                    global_data->config->cert,
+	                    SSL_FILETYPE_PEM);
+	CHECK_OPENSSL_ERROR(SSL_CTX_use_PrivateKey_file,
+	                    global_data->ssl_ctx,
+	                    global_data->config->key,
+	                    SSL_FILETYPE_PEM);
+}

+ 29 - 0
frameworks/C/h2o/src/tls.h

@@ -0,0 +1,29 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef TLS_H_
+
+#define TLS_H_
+
+#include "utility.h"
+
+void cleanup_openssl(global_data_t *global_data);
+void initialize_openssl(global_data_t *global_data);
+
+#endif // TLS_H_

+ 95 - 0
frameworks/C/h2o/src/utility.c

@@ -0,0 +1,95 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <assert.h>
+#include <h2o.h>
+#include <stdalign.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <yajl/yajl_gen.h>
+
+#include "utility.h"
+
+static void mem_pool_free(void *ctx, void *ptr);
+static void *mem_pool_malloc(void *ctx, size_t sz);
+static void *mem_pool_realloc(void *ctx, void *ptr, size_t sz);
+
+static void mem_pool_free(void *ctx, void *ptr)
+{
+	// The memory pool will free all allocations in one go.
+	IGNORE_FUNCTION_PARAMETER(ctx);
+	IGNORE_FUNCTION_PARAMETER(ptr);
+}
+
+static void *mem_pool_malloc(void *ctx, size_t sz)
+{
+	size_t * const p = h2o_mem_alloc_pool(ctx, sz + sizeof(*p));
+	void * const ret = p + 1;
+
+	*p = sz;
+	// check alignment
+	assert(!(((uintptr_t) ret) & (alignof(void *) - 1)));
+	return ret;
+}
+
+static void *mem_pool_realloc(void *ctx, void *ptr, size_t sz)
+{
+	void *ret;
+
+	if (ptr) {
+		const size_t old_sz = ((const size_t *) ptr)[-1];
+
+		if (sz > old_sz) {
+			ret = mem_pool_malloc(ctx, sz);
+			memcpy(ret, ptr, old_sz);
+		}
+		else
+			ret = ptr;
+	}
+	else
+		ret = mem_pool_malloc(ctx, sz);
+
+	return ret;
+}
+
+yajl_gen get_json_generator(h2o_mem_pool_t *pool)
+{
+	const yajl_alloc_funcs mem_pool_alloc_funcs = {mem_pool_malloc,
+	                                               mem_pool_realloc,
+	                                               mem_pool_free,
+	                                               pool};
+
+	return yajl_gen_alloc(&mem_pool_alloc_funcs);
+}
+
+uint32_t get_random_number(uint32_t max_rand, unsigned int *seed)
+{
+	// In general, RAND_MAX + 1 is not a multiple of max_rand,
+	// so rand_r() % max_rand would be biased.
+	const unsigned bucket_size = (RAND_MAX + 1U) / max_rand;
+	const unsigned unbiased_rand_max = bucket_size * max_rand;
+	unsigned ret;
+
+	do
+		ret = rand_r(seed);
+	while (ret >= unbiased_rand_max);
+
+	return ret / bucket_size;
+}

+ 73 - 0
frameworks/C/h2o/src/utility.h

@@ -0,0 +1,73 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef UTILITY_H_
+
+#define UTILITY_H_
+
+#include <h2o.h>
+#include <stdint.h>
+#include <openssl/ssl.h>
+#include <stdbool.h>
+#include <yajl/yajl_gen.h>
+#include <mustache.h>
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
+// mainly used to silence compiler warnings about unused function parameters
+#define IGNORE_FUNCTION_PARAMETER(p) ((void) (p))
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#define MKSTR(x) TOSTRING(x)
+#define TOSTRING(x) # x
+#define YAJL_STRLIT(s) (const unsigned char *) (s), sizeof(s) - 1
+
+typedef struct thread_context_t thread_context_t;
+
+typedef struct {
+	const char *bind_address;
+	const char *cert;
+	const char *db_host;
+	const char *key;
+	const char *log;
+	const char *root;
+	const char *template_path;
+	size_t max_accept;
+	size_t max_db_conn_num;
+	size_t max_query_num;
+	size_t thread_num;
+	uint16_t port;
+} config_t;
+
+typedef struct {
+	const config_t *config;
+	thread_context_t *ctx;
+	h2o_logger_t *file_logger;
+	mustache_template_t *fortunes_template;
+	h2o_socket_t *signals;
+	SSL_CTX *ssl_ctx;
+	size_t memory_alignment;
+	int listener_sd;
+	int signal_fd;
+	bool shutdown;
+	h2o_globalconf_t h2o_config;
+} global_data_t;
+
+yajl_gen get_json_generator(h2o_mem_pool_t *pool);
+uint32_t get_random_number(uint32_t max_rand, unsigned int *seed);
+
+#endif // UTILITY_H_

+ 497 - 0
frameworks/C/h2o/src/world.c

@@ -0,0 +1,497 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <assert.h>
+#include <h2o.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <postgresql/libpq-fe.h>
+#include <yajl/yajl_gen.h>
+
+#include "bitset.h"
+#include "database.h"
+#include "error.h"
+#include "request_handler.h"
+#include "thread.h"
+#include "utility.h"
+#include "world.h"
+
+#define ID_KEY "id"
+#define MAX_QUERIES 500
+#define QUERIES_PARAMETER "queries="
+#define RANDOM_NUM_KEY "randomNumber"
+
+typedef enum {
+	NO_UPDATE = 0,
+	CREATE,
+	COPY_1,
+	COPY_2,
+	UPDATE
+} update_state_t;
+
+typedef struct {
+	uint32_t id;
+	uint32_t random_number;
+} query_result_t;
+
+typedef struct {
+	db_query_param_t param;
+	yajl_gen gen;
+	h2o_generator_t h2o_generator;
+	const char *id_pointer;
+	h2o_req_t *req;
+	uint32_t id;
+	int id_format;
+	int id_len;
+} single_query_ctx_t;
+
+typedef struct {
+	single_query_ctx_t single;
+	size_t num_query;
+	size_t num_result;
+	update_state_t update_state;
+	query_result_t res[];
+} multiple_query_ctx_t;
+
+static void complete_request(struct st_h2o_generator_t *self, h2o_req_t *req);
+static void cleanup_request(struct st_h2o_generator_t *self, h2o_req_t *req);
+static int do_multiple_queries(update_state_t update_state, h2o_req_t *req);
+static int initialize_single_query_context(h2o_req_t *req,
+                                           on_result_t on_result,
+                                           single_query_ctx_t *query_ctx);
+static void on_database_error(db_query_param_t *param, const char *error_string);
+static void on_database_timeout(db_query_param_t *param);
+static result_return_t on_multiple_query_result(db_query_param_t *param, PGresult *result);
+static result_return_t on_single_query_result(db_query_param_t *param, PGresult *result);
+static result_return_t on_update_result(db_query_param_t *param, PGresult *result);
+static int on_update_write_ready(db_query_param_t *param, PGconn *db_conn);
+static int serialize_item(uint32_t id, uint32_t random_number, yajl_gen gen);
+static void serialize_items(const query_result_t *res,
+                            size_t num_result,
+                            h2o_generator_t *h2o_generator,
+                            yajl_gen gen,
+                            h2o_req_t *req);
+
+static void cleanup_request(struct st_h2o_generator_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(req);
+
+	single_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(single_query_ctx_t,
+	                                                              h2o_generator,
+	                                                              self);
+
+	yajl_gen_free(query_ctx->gen);
+}
+
+static void complete_request(struct st_h2o_generator_t *self, h2o_req_t *req)
+{
+	h2o_send(req, NULL, 0, true);
+	cleanup_request(self, req);
+}
+
+static int do_multiple_queries(update_state_t update_state, h2o_req_t *req)
+{
+	thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+	                                                      event_loop.h2o_ctx,
+	                                                      req->conn->ctx);
+	int num_query = 0;
+
+	if (req->query_at < SIZE_MAX) {
+		const char * const n = get_query_param(req->path.base + req->query_at + 1,
+		                                       req->path.len - req->query_at - 1,
+		                                       QUERIES_PARAMETER,
+		                                       sizeof(QUERIES_PARAMETER) - 1);
+
+		if (n)
+			num_query = atoi(n);
+	}
+
+	if (num_query < 1)
+		num_query = 1;
+	else if (num_query > MAX_QUERIES)
+		num_query = MAX_QUERIES;
+
+	const size_t sz = offsetof(multiple_query_ctx_t, res) + num_query * sizeof(query_result_t);
+	multiple_query_ctx_t * const query_ctx = h2o_mem_alloc_pool(&req->pool, sz);
+
+	if (!query_ctx || initialize_single_query_context(req,
+	                                                  on_multiple_query_result,
+	                                                  &query_ctx->single))
+		send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, req);
+	else {
+		// MAX_ID is a relatively small number, so allocate on the stack.
+		DEFINE_BITSET(bitset, MAX_ID);
+
+		memset(&query_ctx->single + 1,
+		       0,
+		       offsetof(multiple_query_ctx_t, res) - sizeof(query_ctx->single));
+		query_ctx->num_query = num_query;
+		query_ctx->update_state = update_state;
+
+		size_t max_rand = MAX_ID - query_ctx->num_query + 1;
+
+		for (size_t i = 0; i < query_ctx->num_query; i++) {
+			query_ctx->res[i].id = get_random_number(max_rand, &ctx->random_seed);
+
+			if (BITSET_ISSET(query_ctx->res[i].id, bitset))
+				query_ctx->res[i].id = max_rand - 1;
+
+			BITSET_SET(query_ctx->res[i].id++, bitset);
+			max_rand++;
+		}
+
+		query_ctx->single.id = htonl(query_ctx->res->id);
+
+		if (execute_query(ctx, &query_ctx->single.param)) {
+			yajl_gen_free(query_ctx->single.gen);
+			send_service_unavailable_error(DB_REQ_ERROR, req);
+		}
+	}
+
+	return 0;
+}
+
+static int initialize_single_query_context(h2o_req_t *req,
+                                           on_result_t on_result,
+                                           single_query_ctx_t *query_ctx)
+{
+	int ret = EXIT_FAILURE;
+
+	memset(query_ctx, 0, sizeof(*query_ctx));
+	query_ctx->gen = get_json_generator(&req->pool);
+
+	if (query_ctx->gen) {
+		query_ctx->h2o_generator.proceed = complete_request;
+		query_ctx->h2o_generator.stop = cleanup_request;
+		query_ctx->id_format = 1;
+		query_ctx->id_len = sizeof(query_ctx->id);
+		query_ctx->id_pointer = (const char *) &query_ctx->id;
+		query_ctx->param.command = WORLD_TABLE_NAME;
+		query_ctx->param.nParams = 1;
+		query_ctx->param.on_error = on_database_error;
+		query_ctx->param.on_result = on_result;
+		query_ctx->param.on_timeout = on_database_timeout;
+		query_ctx->param.paramFormats = &query_ctx->id_format;
+		query_ctx->param.paramLengths = &query_ctx->id_len;
+		query_ctx->param.paramValues = &query_ctx->id_pointer;
+		query_ctx->param.flags = IS_PREPARED;
+		query_ctx->param.resultFormat = 1;
+		query_ctx->req = req;
+		ret = EXIT_SUCCESS;
+	}
+
+	return ret;
+}
+
+static void on_database_error(db_query_param_t *param, const char *error_string)
+{
+	single_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(single_query_ctx_t,
+	                                                              param,
+	                                                              param);
+
+	yajl_gen_free(query_ctx->gen);
+	send_error(BAD_GATEWAY, error_string, query_ctx->req);
+}
+
+static void on_database_timeout(db_query_param_t *param)
+{
+	single_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(single_query_ctx_t,
+	                                                              param,
+	                                                              param);
+
+	yajl_gen_free(query_ctx->gen);
+	send_error(GATEWAY_TIMEOUT, DB_TIMEOUT_ERROR, query_ctx->req);
+}
+
+static result_return_t on_multiple_query_result(db_query_param_t *param, PGresult *result)
+{
+	multiple_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(multiple_query_ctx_t,
+	                                                                single.param,
+	                                                                param);
+
+	if (PQresultStatus(result) == PGRES_TUPLES_OK) {
+		thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+		                                                      event_loop.h2o_ctx,
+		                                                      query_ctx->single.req->conn->ctx);
+		uint32_t * const random_number = &query_ctx->res[query_ctx->num_result++].random_number;
+
+		assert(PQnfields(result) == 2);
+		assert(PQntuples(result) == 1);
+		assert(PQgetlength(result, 0, 1) == sizeof(*random_number));
+		// Use memcpy() in case the result is not aligned.
+		memcpy(random_number, PQgetvalue(result, 0, 1), sizeof(*random_number));
+		*random_number = ntohl(*random_number);
+		PQclear(result);
+
+		if (query_ctx->num_result < query_ctx->num_query) {
+			query_ctx->single.id = htonl(query_ctx->res[query_ctx->num_result].id);
+
+			if (!execute_query(ctx, &query_ctx->single.param))
+				return DONE;
+
+			send_service_unavailable_error(DB_REQ_ERROR, query_ctx->single.req);
+		}
+		else if (query_ctx->update_state == NO_UPDATE) {
+			serialize_items(query_ctx->res,
+			                query_ctx->num_result,
+			                &query_ctx->single.h2o_generator,
+			                query_ctx->single.gen,
+			                query_ctx->single.req);
+			return DONE;
+		}
+		else {
+			query_ctx->single.param.command = UPDATE_QUERY;
+			query_ctx->single.param.nParams = 0;
+			query_ctx->single.param.on_result = on_update_result;
+			query_ctx->single.param.on_write_ready = on_update_write_ready;
+			query_ctx->single.param.paramFormats = NULL;
+			query_ctx->single.param.paramLengths = NULL;
+			query_ctx->single.param.paramValues = NULL;
+			query_ctx->single.param.flags = 0;
+
+			if (!execute_query(ctx, &query_ctx->single.param))
+				return DONE;
+
+			send_service_unavailable_error(DB_REQ_ERROR, query_ctx->single.req);
+		}
+	}
+	else {
+		send_error(BAD_GATEWAY, PQresultErrorMessage(result), query_ctx->single.req);
+		PQclear(result);
+	}
+
+	yajl_gen_free(query_ctx->single.gen);
+	return DONE;
+}
+
+static result_return_t on_single_query_result(db_query_param_t *param, PGresult *result)
+{
+	single_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(single_query_ctx_t,
+	                                                              param,
+	                                                              param);
+
+	if (PQresultStatus(result) == PGRES_TUPLES_OK) {
+		uint32_t random_number;
+
+		assert(PQnfields(result) == 2);
+		assert(PQntuples(result) == 1);
+		assert(PQgetlength(result, 0, 1) == sizeof(random_number));
+
+		// Use memcpy() in case the result is not aligned.
+		memcpy(&random_number, PQgetvalue(result, 0, 1), sizeof(random_number));
+		random_number = ntohl(random_number);
+		PQclear(result);
+
+		if (!serialize_item(ntohl(query_ctx->id), random_number, query_ctx->gen) &&
+		    !send_json_response(query_ctx->gen, &query_ctx->h2o_generator, query_ctx->req))
+			return DONE;
+
+		send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, query_ctx->req);
+	}
+	else {
+		send_error(BAD_GATEWAY, PQresultErrorMessage(result), query_ctx->req);
+		PQclear(result);
+	}
+
+	yajl_gen_free(query_ctx->gen);
+	return DONE;
+}
+
+static result_return_t on_update_result(db_query_param_t *param, PGresult *result)
+{
+	multiple_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(multiple_query_ctx_t,
+	                                                                single.param,
+	                                                                param);
+	result_return_t ret = SUCCESS;
+	const ExecStatusType status = PQresultStatus(result);
+
+	switch (query_ctx->update_state) {
+		case CREATE:
+		case COPY_2:
+			if (status != PGRES_COMMAND_OK)
+				goto error;
+
+			query_ctx->update_state++;
+			break;
+		case COPY_1:
+			if (status != PGRES_COPY_IN)
+				goto error;
+
+			ret = WANT_WRITE;
+			break;
+		case UPDATE:
+			if (status != PGRES_COMMAND_OK)
+				goto error;
+
+			serialize_items(query_ctx->res,
+			                query_ctx->num_result,
+			                &query_ctx->single.h2o_generator,
+			                query_ctx->single.gen,
+			                query_ctx->single.req);
+			ret = DONE;
+			break;
+		default:
+			goto error;
+	}
+
+	PQclear(result);
+	return ret;
+error:
+	send_error(BAD_GATEWAY, PQresultErrorMessage(result), query_ctx->single.req);
+	PQclear(result);
+	return DONE;
+}
+
+static int on_update_write_ready(db_query_param_t *param, PGconn *db_conn)
+{
+	multiple_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(multiple_query_ctx_t,
+	                                                                single.param,
+	                                                                param);
+	thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+	                                                      event_loop.h2o_ctx,
+	                                                      query_ctx->single.req->conn->ctx);
+
+	if (query_ctx->num_result == query_ctx->num_query && query_ctx->update_state != COPY_2) {
+		const int rc = PQputCopyData(db_conn, H2O_STRLIT(COPY_HEADER));
+
+		if (!rc)
+			return 1;
+		else if (rc < 0)
+			return rc;
+
+		query_ctx->num_result = 0;
+		query_ctx->update_state = COPY_2;
+	}
+
+	if (query_ctx->num_result < query_ctx->num_query) {
+		assert(query_ctx->num_query - query_ctx->num_result <= MAX_QUERIES);
+
+		// There are at most MAX_QUERIES elements, so allocate on the stack.
+		struct __attribute__ ((__packed__)) {
+			uint16_t field_count;
+			uint32_t id_size;
+			uint32_t id;
+			uint32_t random_number_size;
+			uint32_t random_number;
+		} data[query_ctx->num_query - query_ctx->num_result];
+
+		memset(&data, 0, sizeof(data));
+
+		for (size_t i = 0; i < query_ctx->num_query; i++) {
+			query_ctx->res[i].random_number = get_random_number(MAX_ID, &ctx->random_seed) + 1;
+			data[i].field_count = htons(2);
+			data[i].id_size = htonl(sizeof(data->id));
+			data[i].id = htonl(query_ctx->res[i].id);
+			data[i].random_number_size = htonl(sizeof(data->random_number));
+			data[i].random_number = htonl(query_ctx->res[i].random_number);
+		}
+
+		const int rc = PQputCopyData(db_conn, (const char *) &data, sizeof(data));
+
+		if (!rc)
+			return 1;
+		else if (rc < 0)
+			return rc;
+
+		query_ctx->num_result = query_ctx->num_query;
+	}
+
+	if (query_ctx->num_result == query_ctx->num_query) {
+		const int rc = PQputCopyEnd(db_conn, NULL);
+
+		if (!rc)
+			return 1;
+		else if (rc < 0)
+			return rc;
+	}
+
+	return PQflush(db_conn);
+}
+
+static int serialize_item(uint32_t id, uint32_t random_number, yajl_gen gen)
+{
+	CHECK_YAJL_STATUS(yajl_gen_map_open, gen);
+	CHECK_YAJL_STATUS(yajl_gen_string, gen, YAJL_STRLIT(ID_KEY));
+	CHECK_YAJL_STATUS(yajl_gen_integer, gen, id);
+	CHECK_YAJL_STATUS(yajl_gen_string, gen, YAJL_STRLIT(RANDOM_NUM_KEY));
+	CHECK_YAJL_STATUS(yajl_gen_integer, gen, random_number);
+	CHECK_YAJL_STATUS(yajl_gen_map_close, gen);
+	return EXIT_SUCCESS;
+error_yajl:
+	return EXIT_FAILURE;
+}
+
+static void serialize_items(const query_result_t *res,
+                            size_t num_result,
+                            h2o_generator_t *h2o_generator,
+                            yajl_gen gen,
+                            h2o_req_t *req)
+{
+	CHECK_YAJL_STATUS(yajl_gen_array_open, gen);
+
+	for (size_t i = 0; i < num_result; i++)
+		if (serialize_item(res[i].id, res[i].random_number, gen))
+			goto error_yajl;
+
+	CHECK_YAJL_STATUS(yajl_gen_array_close, gen);
+
+	if (send_json_response(gen, h2o_generator, req))
+error_yajl:
+		send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, req);
+}
+
+int multiple_queries(struct st_h2o_handler_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(self);
+
+	return do_multiple_queries(NO_UPDATE, req);
+}
+
+int single_query(struct st_h2o_handler_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(self);
+
+	thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+	                                                      event_loop.h2o_ctx,
+	                                                      req->conn->ctx);
+	single_query_ctx_t * const query_ctx = h2o_mem_alloc_pool(&req->pool, sizeof(*query_ctx));
+
+	if (!query_ctx || initialize_single_query_context(req,
+	                                                  on_single_query_result,
+	                                                  query_ctx))
+		send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, req);
+	else {
+		query_ctx->id = htonl(get_random_number(MAX_ID, &ctx->random_seed) + 1);
+
+		if (execute_query(ctx, &query_ctx->param)) {
+			yajl_gen_free(query_ctx->gen);
+			send_service_unavailable_error(DB_REQ_ERROR, req);
+		}
+	}
+
+	return 0;
+}
+
+int updates(struct st_h2o_handler_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(self);
+
+	return do_multiple_queries(CREATE, req);
+}

+ 30 - 0
frameworks/C/h2o/src/world.h

@@ -0,0 +1,30 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef WORLD_H_
+
+#define WORLD_H_
+
+#include <h2o.h>
+
+int multiple_queries(struct st_h2o_handler_t *self, h2o_req_t *req);
+int single_query(struct st_h2o_handler_t *self, h2o_req_t *req);
+int updates(struct st_h2o_handler_t *self, h2o_req_t *req);
+
+#endif // WORLD_H_

+ 10 - 0
frameworks/C/h2o/template/fortunes.mustache

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+<tr><th>id</th><th>message</th></tr>
+{{#Fortune}}<tr><td>{{id}}</td><td>{{message}}</td></tr>{{/Fortune}}
+</table>
+</body>
+</html>

+ 1 - 1
frameworks/D/vibed/README.md

@@ -12,7 +12,7 @@ This is the Vibe.D portion of a [benchmarking test suite](../) comparing a varie
 
 ## Infrastructure Software Versions
 The tests were run with:
-* [Vibe.D v0.7.19](http://vibed.org/)
+* [Vibe.D v0.7.29](http://vibed.org/)
 
 ## Test URLs
 ### JSON Encoding Test

+ 23 - 0
frameworks/D/vibed/benchmark_config.json

@@ -23,6 +23,29 @@
       "display_name": "vibe.d",
       "notes": "",
       "versus": "vibed"
+    },
+    "ldc": {
+      "setup_file": "setup_ldc",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "plaintext_url": "/plaintext",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "MongoDB",
+      "framework": "vibed",
+      "language": "D",
+      "orm": "Micro",
+      "platform": "D",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "vibe.d-ldc",
+      "notes": "",
+      "versus": "vibed"
     }
   }]
 }

+ 3 - 4
frameworks/D/vibed/dub.selections.json

@@ -1,11 +1,10 @@
 {
 	"fileVersion": 1,
 	"versions": {
-		"memutils": "0.4.4",
-		"vibe-d": "0.7.28",
+		"libasync": "0.7.9",
 		"libevent": "2.0.1+2.0.16",
+		"memutils": "0.4.6",
 		"openssl": "1.1.4+1.0.1g",
-		"libasync": "0.7.8",
-		"libev": "5.0.0+4.04"
+		"vibe-d": "0.7.29"
 	}
 }

+ 11 - 0
frameworks/D/vibed/setup_ldc.sh

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+fw_depends dlang dub
+
+# Clean any files from last run
+rm -f fwb
+rm -rf .dub
+
+dub build -b release --compiler=ldc2
+
+./fwb &

+ 1 - 1
frameworks/D/vibed/source/mongodb.d

@@ -5,7 +5,7 @@ import vibe.web.web;
 
 import std.conv : ConvException, to;
 import std.random : uniform;
-
+import std.array;
 
 enum worldSize = 10000;
 

+ 2 - 5
frameworks/Go/beego/src/hello/hello.go

@@ -3,16 +3,15 @@ package main
 import (
 	"log"
 	"math/rand"
+
 	"github.com/astaxie/beego"
 	"github.com/astaxie/beego/orm"
-
 	_ "github.com/go-sql-driver/mysql"
-	//"runtime"
 )
 
 const (
 	// Database
-	connectionString   = "benchmarkdbuser:benchmarkdbpass@tcp(localhost:3306)/hello_world"
+	connectionString   = "benchmarkdbuser:benchmarkdbpass@tcp(localhost:3306)/hello_world?collation=utf8mb4_bin&interpolateParams=true"
 	worldRowCount      = 10000
 	macIdleConnection  = 30
 	maxConnectionCount = 256
@@ -68,8 +67,6 @@ func (this *DBController) Get() {
 }
 
 func main() {
-	//don't need this set, beego default set it
-	//runtime.GOMAXPROCS(runtime.NumCPU())
 	beego.BConfig.RunMode = "prod"
 	beego.Router("/json", &JsonController{})
 	beego.Router("/db", &DBController{})

+ 8 - 8
frameworks/Go/echo/README.md

@@ -1,14 +1,14 @@
-# [ECHO](http://echo.labstack.com) (GoLang) Benchmarking Test
+# [Echo](https://github.com/labstack/echo) (Go) Benchmarking Test
 
 This is the go portion of a [benchmarking test suite](https://www.techempower.com/benchmarks/) comparing a variety of web development platforms.
 
-"Echo is a fast and unfancy micro web framework for Go."
+> Echo is a fast and unfancy micro web framework for Go
 
 ## Test URLs
 
-    http://localhost:8080/json
-    http://localhost:8080/db
-    http://localhost:8080/queries?queries=[1-500]
-    http://localhost:8080/fortunes
-    http://localhost:8080/updates?queries=[1-500]
-    http://localhost:8080/plaintext
+- http://localhost:8080/json
+- http://localhost:8080/db
+- http://localhost:8080/queries/:n[1-500]
+- http://localhost:8080/fortunes
+- http://localhost:8080/updates/:n[1-500]
+- http://localhost:8080/plaintext

+ 50 - 4
frameworks/Go/echo/benchmark_config.json

@@ -11,16 +11,62 @@
       "plaintext_url": "/plaintext",
       "port": 8080,
       "approach": "Realistic",
-      "classification": "Platform",
-      "database": "MySQL",
-      "framework": "ECHO",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "echo",
       "language": "Go",
       "orm": "Raw",
       "platform": "Go",
       "webserver": "None",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "ECHO",
+      "display_name": "echo",
+      "notes": "",
+      "versus": "go"
+    },
+    "std": {
+      "setup_file": "setup_std",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries/",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates/",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "echo",
+      "language": "Go",
+      "orm": "Raw",
+      "platform": "Go",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "echo-std",
+      "notes": "",
+      "versus": "go"
+    },
+    "prefork": {
+      "setup_file": "setup_prefork",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries/",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates/",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "echo",
+      "language": "Go",
+      "orm": "Raw",
+      "platform": "Go",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "echo-prefork",
       "notes": "",
       "versus": "go"
     }

+ 0 - 20
frameworks/Go/echo/fortune.html

@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>Fortunes</title>
-</head>
-<body>
-<table>
-<tr>
-<th>id</th>
-<th>message</th>
-</tr>
-{{range .}}
-<tr>
-<td>{{.Id}}</td>
-<td>{{.Message}}</td>
-</tr>
-{{end}}
-</table>
-</body>
-</html>

+ 0 - 204
frameworks/Go/echo/server.go

@@ -1,204 +0,0 @@
-package main
-
-import (
-	"database/sql"
-	"fmt"
-	"html/template"
-	"io"
-	"log"
-	"math/rand"
-	"net/http"
-	"sort"
-	"strconv"
-
-	_ "github.com/go-sql-driver/mysql"
-	"github.com/labstack/echo"
-)
-
-const (
-	// Database
-	connectionString   = "benchmarkdbuser:benchmarkdbpass@tcp(localhost:3306)/hello_world"
-	worldRowCount      = 10000
-	macIdleConnection  = 30
-	maxConnectionCount = 256
-
-	helloWorldString = "Hello, World!"
-	worldSelect      = "SELECT id, randomNumber FROM World WHERE id = ?"
-	worldUpdate      = "UPDATE World SET randomNumber = ? WHERE id = ?"
-	fortuneSelect    = "SELECT id, message FROM Fortune;"
-)
-
-var (
-	// Database
-	worldStatement   *sql.Stmt
-	fortuneStatement *sql.Stmt
-	updateStatement  *sql.Stmt
-)
-
-type Template struct {
-	templates *template.Template
-}
-
-type MessageStruct struct {
-	Message string `json:"message"`
-}
-
-type World struct {
-	Id           uint16 `json:"id"`
-	RandomNumber uint16 `json:"randomNumber"`
-}
-
-func randomRow() *sql.Row {
-	return worldStatement.QueryRow(rand.Intn(worldRowCount) + 1)
-}
-
-type Fortune struct {
-	Id      uint16 `json:"id"`
-	Message string `json:"message"`
-}
-
-type Fortunes []*Fortune
-
-func (s Fortunes) Len() int      { return len(s) }
-func (s Fortunes) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-
-type ByMessage struct{ Fortunes }
-
-func (s ByMessage) Less(i, j int) bool { return s.Fortunes[i].Message < s.Fortunes[j].Message }
-
-// Render HTML
-func (t *Template) Render(w io.Writer, name string, data interface{}) error {
-	return t.templates.ExecuteTemplate(w, name, data)
-}
-
-// queries parameter between 1 and 500.
-func sanitizeQueryParam(param string) int {
-	queries, err := strconv.Atoi(param)
-	if err != nil || queries < 1 {
-		return 1
-	}
-	if queries > 500 {
-		return 500
-	}
-	return queries
-}
-
-func json(c *echo.Context) error {
-	return c.JSON(http.StatusOK, MessageStruct{"Hello, World!"})
-}
-
-func plaintext(c *echo.Context) error {
-	return c.String(http.StatusOK, "Hello, World!")
-}
-
-func fortunes(c *echo.Context) error {
-	rows, err := fortuneStatement.Query()
-	if err != nil {
-		log.Fatalf("Error preparing statement: %v", err)
-	}
-
-	fortunes := make(Fortunes, 0, 16)
-
-	for rows.Next() {
-		fortune := Fortune{}
-		if err := rows.Scan(&fortune.Id, &fortune.Message); err != nil {
-			log.Fatalf("Error scanning fortune row: %s", err.Error())
-		}
-		fortunes = append(fortunes, &fortune)
-	}
-	fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
-
-	sort.Sort(ByMessage{fortunes})
-	return c.Render(http.StatusOK, "fortune.html", fortunes)
-}
-
-func singleQuery(c *echo.Context) error {
-	world := World{}
-	if err := randomRow().Scan(&world.Id, &world.RandomNumber); err != nil {
-		log.Fatalf("Error scanning world row: %s", err.Error())
-	}
-	return c.JSON(http.StatusOK, world)
-}
-
-func multipleQueries(c *echo.Context) error {
-	// Get Param
-	queries := sanitizeQueryParam(c.Param("queries"))
-	worlds := make([]World, queries)
-	for i := 0; i < queries; i++ {
-		if err := randomRow().Scan(&worlds[i].Id, &worlds[i].RandomNumber); err != nil {
-			log.Fatalf("Error scanning world row: %s", err.Error())
-		}
-	}
-	return c.JSON(http.StatusOK, worlds)
-}
-
-func updates(c *echo.Context) error {
-	// Get Param
-	queries := sanitizeQueryParam(c.Param("queries"))
-	worlds := make([]World, queries)
-
-	for i := 0; i < queries; i++ {
-		if err := randomRow().Scan(&worlds[i].Id, &worlds[i].RandomNumber); err != nil {
-			log.Fatalf("Error scanning world row: %s", err.Error())
-		}
-		worlds[i].RandomNumber = uint16(rand.Intn(worldRowCount) + 1)
-		if _, err := updateStatement.Exec(worlds[i].RandomNumber, worlds[i].Id); err != nil {
-			log.Fatalf("Error updating world row: %s", err.Error())
-		}
-	}
-	return c.JSON(http.StatusOK, worlds)
-}
-
-func main() {
-	e := echo.New()
-
-	// Set Templates
-	tmpl := &Template{
-		templates: template.Must(template.ParseFiles("fortune.html", "layout.html")),
-	}
-	e.SetRenderer(tmpl)
-
-	// Middleware
-	e.Use(ServerHeader())
-
-	// Routes
-	e.Get("/json", json)
-	e.Get("/db", singleQuery)
-	e.Get("/queries/:queries", multipleQueries)
-	e.Get("/fortunes", fortunes)
-	e.Get("/updates/:queries", updates)
-	e.Get("/plaintext", plaintext)
-
-	// Start server
-	e.Run(":8080")
-}
-
-func ServerHeader() echo.MiddlewareFunc {
-	return func(h echo.HandlerFunc) echo.HandlerFunc {
-		return func(c *echo.Context) error {
-			c.Response().Header().Add("Server", "ECHO")
-			return h(c)
-		}
-	}
-}
-
-func init() {
-	db, err := sql.Open("mysql", fmt.Sprintf(connectionString))
-	if err != nil {
-		log.Fatalf("Error opening database: %v", err)
-	}
-	db.SetMaxIdleConns(maxConnectionCount)
-
-	worldStatement, err = db.Prepare(worldSelect)
-	if err != nil {
-		log.Fatal(err)
-	}
-	fortuneStatement, err = db.Prepare(fortuneSelect)
-	if err != nil {
-		log.Fatal(err)
-	}
-	updateStatement, err = db.Prepare(worldUpdate)
-	if err != nil {
-		log.Fatal(err)
-	}
-}

+ 4 - 5
frameworks/Go/echo/setup.sh

@@ -1,10 +1,9 @@
 #!/bin/bash
 
-sed -i 's|tcp(.*:3306)|tcp('"${DBHOST}"':3306)|g' server.go
-
 fw_depends go
 
-go get github.com/go-sql-driver/mysql
-go get github.com/labstack/echo
+go get github.com/labstack/echo/...
+go get github.com/lib/pq
+go install standard fasthttp
 
-go run server.go &
+fasthttp &

+ 9 - 0
frameworks/Go/echo/setup_prefork.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+fw_depends go
+
+go get github.com/labstack/echo/...
+go get github.com/lib/pq
+go install standard fasthttp
+
+fasthttp -prefork &

+ 9 - 0
frameworks/Go/echo/setup_std.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+fw_depends go
+
+go get github.com/labstack/echo/...
+go get github.com/lib/pq
+go install standard fasthttp
+
+standard &

+ 3 - 0
frameworks/Go/echo/source_code

@@ -0,0 +1,3 @@
+./echo/src/common/common.go
+./echo/src/standard/main.go
+./echo/src/fasthttp/main.go

+ 293 - 0
frameworks/Go/echo/src/common/common.go

@@ -0,0 +1,293 @@
+package common
+
+import (
+	"database/sql"
+	"encoding/json"
+	"fmt"
+	"html/template"
+	"io"
+	"log"
+	"math/rand"
+	"net/http"
+	"os"
+	"sort"
+	"strconv"
+
+	"github.com/labstack/echo"
+	_ "github.com/lib/pq"
+)
+
+type (
+	handler struct{}
+
+	StdTemplate struct {
+		templates *template.Template
+	}
+
+	Message struct {
+		Message string `json:"message"`
+	}
+
+	World struct {
+		ID           uint16 `json:"id"`
+		RandomNumber uint16 `json:"randomNumber"`
+	}
+
+	Fortune struct {
+		ID      uint16 `json:"id"`
+		Message string `json:"message"`
+	}
+
+	Fortunes []*Fortune
+
+	FortunesByMessage struct {
+		Fortunes
+	}
+)
+
+const (
+	// Template
+	fortuneHTML = `
+    <!doctype html>
+    <html>
+    <head>
+      <title>Fortunes</title>
+    </head>
+    <body>
+      <table>
+        <tr>
+          <th>id</th>
+          <th>message</th>
+          </tr>
+        {{range .}}
+        <tr>
+          <td>{{ .ID }}</td>
+          <td>{{ .Message }}</td>
+        </tr>
+        {{end}}
+      </table>
+    </body>
+    </html>
+  `
+	// Database
+	connectionString = "postgres://benchmarkdbuser:benchmarkdbpass@%s/hello_world?sslmode=disable"
+	worldSelect      = "SELECT id, randomNumber FROM World WHERE id = $1"
+	worldUpdate      = "UPDATE World SET randomNumber = $1 WHERE id = $2"
+	fortuneSelect    = "SELECT id, message FROM Fortune"
+	worldRowCount    = 10000
+	maxConnections   = 256
+)
+
+var (
+	// Database
+	db                *sql.DB
+	worldSelectStmt   *sql.Stmt
+	worldUpdateStmt   *sql.Stmt
+	fortuneSelectStmt *sql.Stmt
+
+	// Template
+	Template = &StdTemplate{
+		templates: template.Must(template.New("fortune").Parse(fortuneHTML)),
+	}
+
+	helloWorld = []byte("Hello, World!")
+)
+
+func (t *StdTemplate) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
+	return t.templates.ExecuteTemplate(w, name, data)
+}
+
+func (f Fortunes) Len() int {
+	return len(f)
+}
+
+func (f Fortunes) Swap(i, j int) {
+	f[i], f[j] = f[j], f[i]
+}
+
+func (f FortunesByMessage) Less(i, j int) bool {
+	return f.Fortunes[i].Message < f.Fortunes[j].Message
+}
+
+// Test 1: JSON serialization
+func (h *handler) json() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		c.Response().Header().Set("Server", "Echo")
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+		return json.NewEncoder(c.Response()).Encode(Message{"Hello, World!"})
+	}
+}
+
+// Test 2: Single database query
+func (h *handler) db() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		world := new(World)
+		if err := fetchRandomWorld(world); err != nil {
+			return err
+		}
+
+		c.Response().Header().Set("Server", "Echo")
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+		return json.NewEncoder(c.Response()).Encode(world)
+	}
+}
+
+// Test 3: Multiple database queries
+func (h *handler) queries() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		n := getQueryCount(c.P(0))
+		worlds := make([]World, n)
+		for i := 0; i < n; i++ {
+			if err := fetchRandomWorld(&worlds[i]); err != nil {
+				return err
+			}
+		}
+
+		c.Response().Header().Set("Server", "Echo")
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+		return json.NewEncoder(c.Response()).Encode(worlds)
+	}
+}
+
+// Test 4: Fortunes
+func (h *handler) fortunes() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		rows, err := fortuneSelectStmt.Query()
+		if err != nil {
+			return fmt.Errorf("Error preparing statement: %v", err)
+		}
+		defer rows.Close()
+
+		fortunes, err := fetchFortunes(rows)
+		if err != nil {
+			return err
+		}
+		fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
+		sort.Sort(FortunesByMessage{fortunes})
+
+		c.Response().Header().Set("Server", "Echo")
+		return c.Render(http.StatusOK, "fortune", fortunes)
+	}
+}
+
+// Test 5: Database updates
+func (h *handler) updates() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		n := getQueryCount(c.P(0))
+		worlds := make([]World, n)
+		for i := 0; i < n; i++ {
+			// Fetch and modify
+			w := &worlds[i]
+			if err := fetchRandomWorld(&worlds[i]); err != nil {
+				return err
+			}
+			w.RandomNumber = uint16(randomWorldNum())
+
+			// Update
+			if _, err := worldUpdateStmt.Exec(w.RandomNumber, w.ID); err != nil {
+				return fmt.Errorf("Error updating world row: %v", err)
+			}
+		}
+
+		// sorting is required for insert deadlock prevention.
+		// sort.Sort(WorldsByID(worlds))
+		// Update
+		// tx, err := db.Begin()
+		// if err != nil {
+		// 	return fmt.Errorf("Error starting transaction: %s", err)
+		// }
+		// for i := 0; i < n; i++ {
+		// 	w := &worlds[i]
+		// 	if _, err = tx.Stmt(worldUpdateStmt).Exec(w.RandomNumber, w.ID); err != nil {
+		// 		return fmt.Errorf("Error updating world row %d: %s", i, err)
+		// 	}
+		// }
+		// if err = tx.Commit(); err != nil {
+		// 	return fmt.Errorf("Error when commiting world rows: %s", err)
+		// }
+
+		c.Response().Header().Set("Server", "Echo")
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+		return json.NewEncoder(c.Response()).Encode(worlds)
+	}
+}
+
+// Test 6: Plaintext
+func (h *handler) plaintext() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		c.Response().Header().Set("Server", "Echo")
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextPlain)
+		_, err := c.Response().Write(helloWorld)
+		return err
+	}
+}
+
+func fetchRandomWorld(w *World) error {
+	n := randomWorldNum()
+	return worldSelectStmt.QueryRow(n).Scan(&w.ID, &w.RandomNumber)
+}
+
+func randomWorldNum() int {
+	return rand.Intn(worldRowCount) + 1
+}
+
+func getQueryCount(q string) int {
+	n, err := strconv.Atoi(q)
+	if err != nil || n < 1 {
+		return 1
+	}
+	if n > 500 {
+		return 500
+	}
+	return n
+}
+
+func fetchFortunes(rows *sql.Rows) (Fortunes, error) {
+	fortunes := make(Fortunes, 0, 16)
+	for rows.Next() { // Fetch rows
+		f := new(Fortune)
+		if err := rows.Scan(&f.ID, &f.Message); err != nil {
+			return nil, fmt.Errorf("Error scanning fortune row: %s", err.Error())
+		}
+		fortunes = append(fortunes, f)
+	}
+	return fortunes, nil
+}
+
+func InitRoutes(e *echo.Echo) {
+	h := new(handler)
+	e.Get("/json", h.json())
+	e.Get("/db", h.db())
+	e.Get("/queries/*", h.queries())
+	e.Get("/fortunes", h.fortunes())
+	e.Get("/updates/*", h.updates())
+	e.Get("/plaintext", h.plaintext())
+}
+
+func InitPostgres() {
+	host := os.Getenv("DBHOST")
+	if host == "" {
+		host = "localhost"
+	}
+
+	var err error
+	db, err = sql.Open("postgres", fmt.Sprintf(connectionString, host))
+	if err != nil {
+		log.Fatalf("Error opening database: %v", err)
+	}
+	db.SetMaxIdleConns(maxConnections)
+	db.SetMaxOpenConns(maxConnections)
+
+	worldSelectStmt, err = db.Prepare(worldSelect)
+	if err != nil {
+		log.Fatal(err)
+	}
+	worldUpdateStmt, err = db.Prepare(worldUpdate)
+	if err != nil {
+		log.Fatal(err)
+	}
+	fortuneSelectStmt, err = db.Prepare(fortuneSelect)
+	if err != nil {
+		log.Fatal(err)
+	}
+}

+ 69 - 0
frameworks/Go/echo/src/fasthttp/main.go

@@ -0,0 +1,69 @@
+package main
+
+import (
+	"common"
+	"flag"
+	"log"
+	"net"
+	"os"
+	"os/exec"
+	"runtime"
+
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/engine"
+	"github.com/labstack/echo/engine/fasthttp"
+	"github.com/valyala/fasthttp/reuseport"
+)
+
+var (
+	prefork = flag.Bool("prefork", false, "use prefork")
+	child   = flag.Bool("child", false, "is child proc")
+)
+
+func getListener() net.Listener {
+	if !*prefork {
+		runtime.GOMAXPROCS(runtime.NumCPU())
+		ln, err := net.Listen("tcp4", ":8080")
+		if err != nil {
+			log.Fatal(err)
+		}
+		return ln
+	}
+
+	if !*child {
+		children := make([]*exec.Cmd, runtime.NumCPU())
+		for i := range children {
+			children[i] = exec.Command(os.Args[0], "-prefork", "-child")
+			children[i].Stdout = os.Stdout
+			children[i].Stderr = os.Stderr
+			if err := children[i].Start(); err != nil {
+				log.Fatal(err)
+			}
+		}
+		for _, ch := range children {
+			if err := ch.Wait(); err != nil {
+				log.Print(err)
+			}
+		}
+		os.Exit(0)
+		panic("unreachable")
+	}
+
+	runtime.GOMAXPROCS(1)
+	ln, err := reuseport.Listen("tcp4", ":8080")
+	if err != nil {
+		log.Fatal(err)
+	}
+	return ln
+}
+
+func main() {
+	flag.Parse()
+	e := echo.New()
+	e.SetRenderer(common.Template)
+	common.InitRoutes(e)
+	common.InitPostgres()
+	e.Run(fasthttp.WithConfig(engine.Config{
+		Listener: getListener(),
+	}))
+}

+ 16 - 0
frameworks/Go/echo/src/standard/main.go

@@ -0,0 +1,16 @@
+package main
+
+import (
+	"common"
+
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/engine/standard"
+)
+
+func main() {
+	e := echo.New()
+	e.SetRenderer(common.Template)
+	common.InitRoutes(e)
+	common.InitPostgres()
+	e.Run(standard.New(":8080"))
+}

+ 5 - 8
frameworks/Go/falcore/src/framework_benchmarks/falcore.go

@@ -2,19 +2,18 @@ package main
 
 import (
 	"database/sql"
-	"github.com/fitstar/falcore"
-	"github.com/fitstar/falcore/responder"
 	"html/template"
 	"io"
 	"log"
 	"math/rand"
 	"net/http"
-	"runtime"
 	"sort"
 	"strconv"
 	"sync"
 	"time"
 
+	"github.com/fitstar/falcore"
+	"github.com/fitstar/falcore/responder"
 	_ "github.com/go-sql-driver/mysql"
 )
 
@@ -34,7 +33,7 @@ type Fortune struct {
 
 const (
 	// Database
-	connectionString   = "benchmarkdbuser:benchmarkdbpass@tcp(localhost:3306)/hello_world"
+	connectionString   = "benchmarkdbuser:benchmarkdbpass@tcp(localhost:3306)/hello_world?collation=utf8mb4_bin"
 	worldSelect        = "SELECT id, randomNumber FROM World WHERE id = ?"
 	worldUpdate        = "UPDATE World SET randomNumber = ? WHERE id = ?"
 	fortuneSelect      = "SELECT id, message FROM Fortune;"
@@ -96,8 +95,6 @@ func incrStat(statMap map[string]*stats, name string, dur time.Duration) {
 }
 
 func main() {
-	runtime.GOMAXPROCS(runtime.NumCPU())
-
 	db, err := sql.Open("mysql", connectionString)
 	if err != nil {
 		log.Fatalf("Error opening database: %v", err)
@@ -181,11 +178,11 @@ var dbFilter = falcore.NewRequestFilter(func(req *falcore.Request) *http.Respons
 
 // Test 3: Multiple database queries
 var queriesFilter = falcore.NewRequestFilter(func(req *falcore.Request) *http.Response {
-	
+
 	if req.HttpRequest.URL.Path == "/queries" {
 
 		n := 1
-	
+
 		if nStr := req.HttpRequest.URL.Query().Get("queries"); len(nStr) > 0 {
 			n, _ = strconv.Atoi(nStr) // rvalue is 0 if nStr is non-number.
 		}

+ 5 - 0
frameworks/Haskell/servant/ChangeLog.md

@@ -0,0 +1,5 @@
+# Revision history for servant-bench
+
+## 0.1.0.0  -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.

+ 30 - 0
frameworks/Haskell/servant/LICENSE

@@ -0,0 +1,30 @@
+Copyright (c) 2016, Julian K. Arni
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Julian K. Arni nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно