mirror of
https://github.com/h2o/h2o.git
synced 2025-05-14 09:42:33 +08:00
Merge branch 'master' into hfujita/docker-noble
This commit is contained in:
commit
b438f61c37
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@ -5,7 +5,7 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
name: "${{ matrix.name }}"
|
||||
runs-on: [ubuntu-20.04]
|
||||
runs-on: [ubuntu-24.04]
|
||||
|
||||
# We want to run on external PRs, but not on our own internal PRs as they'll be run
|
||||
# by the push to the branch.
|
||||
@ -25,7 +25,7 @@ jobs:
|
||||
command: make -f misc/docker-ci/check.mk ossl3.0 BUILD_ARGS=-j6 TEST_ENV='TEST_JOBS=4 TEST_PLATFORM=github-actions'
|
||||
- name: boringssl
|
||||
command: make -f misc/docker-ci/check.mk boringssl BUILD_ARGS=-j6 TEST_ENV='TEST_JOBS=4 TEST_PLATFORM=github-actions'
|
||||
- name: ASan (Ubuntu 20.04)
|
||||
- name: ASan (Ubuntu 24.04)
|
||||
command: make -f misc/docker-ci/check.mk asan BUILD_ARGS=-j6 TEST_ENV='TEST_JOBS=4 TEST_PLATFORM=github-actions'
|
||||
- name: Coverage
|
||||
command: make -f misc/docker-ci/check.mk coverage BUILD_ARGS=-j6 TEST_ENV='TEST_JOBS=4 TEST_PLATFORM=github-actions'
|
||||
@ -35,6 +35,15 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: setup-docker
|
||||
uses: docker/setup-docker-action@v4
|
||||
with:
|
||||
daemon-config: |
|
||||
{
|
||||
"ipv6": true,
|
||||
"fixed-cidr-v6": "2001:db8:1::/64"
|
||||
}
|
||||
channel: stable
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
@ -46,8 +55,6 @@ jobs:
|
||||
shell: 'script -q -e -c "bash -xe {0}"'
|
||||
run: |
|
||||
chmod -R ugo+w .
|
||||
# restart docker with ipv6 support
|
||||
cat /etc/docker/daemon.json | perl -pe 's| }|, "ipv6": true, "fixed-cidr-v6": "2001:db8:1::/64" }|' | sudo tee /etc/docker/daemon.json > /dev/null; sudo systemctl restart docker; sleep 10
|
||||
${{ matrix.command }}
|
||||
# if there's a summary markdown in the container, show it.
|
||||
container_id=$(docker container ls -al --format '{{ .ID }}')
|
||||
|
2
.github/workflows/daily_coverity.yml
vendored
2
.github/workflows/daily_coverity.yml
vendored
@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
submit:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
@ -165,6 +165,16 @@ ELSE ()
|
||||
ENDIF ()
|
||||
OPTION(WITH_AEGIS "enable AEGIS (requires libaegis)" ${WITH_AEGIS_DEFAULT})
|
||||
|
||||
IF (PKG_CONFIG_FOUND)
|
||||
PKG_CHECK_MODULES(LIBURING liburing)
|
||||
ENDIF ()
|
||||
IF (LIBURING_FOUND)
|
||||
SET(WITH_IO_URING_DEFAULT "ON")
|
||||
ELSE ()
|
||||
SET(WITH_IO_URING_DEFAULT "OFF")
|
||||
ENDIF ()
|
||||
OPTION(WITH_IO_URING "whether or not to use io_uring" ${WITH_IO_URING_DEFAULT})
|
||||
|
||||
INCLUDE_DIRECTORIES(
|
||||
include
|
||||
deps/cloexec
|
||||
@ -781,6 +791,11 @@ ENDIF ()
|
||||
IF (WITH_AEGIS)
|
||||
SET(STANDALONE_COMPILE_FLAGS "${STANDALONE_COMPILE_FLAGS} -DPTLS_HAVE_AEGIS=1")
|
||||
ENDIF ()
|
||||
IF (WITH_IO_URING)
|
||||
# io_uring is enabled only for standalone server, as it is impossible to cleanly shutdown synchronously
|
||||
SET(STANDALONE_COMPILE_FLAGS "${STANDALONE_COMPILE_FLAGS} -DH2O_USE_IO_URING=1")
|
||||
LIST(APPEND STANDALONE_SOURCE_FILES lib/common/io_uring.c)
|
||||
ENDIF ()
|
||||
ADD_EXECUTABLE(h2o ${STANDALONE_SOURCE_FILES})
|
||||
SET_TARGET_PROPERTIES(h2o PROPERTIES COMPILE_FLAGS "${STANDALONE_COMPILE_FLAGS}")
|
||||
TARGET_INCLUDE_DIRECTORIES(h2o PUBLIC ${OPENSSL_INCLUDE_DIR})
|
||||
@ -798,6 +813,11 @@ IF (WITH_AEGIS)
|
||||
TARGET_INCLUDE_DIRECTORIES(h2o PUBLIC ${aegis_INCLUDE_DIR})
|
||||
TARGET_LINK_LIBRARIES(h2o ${aegis_LIBRARIES})
|
||||
ENDIF ()
|
||||
IF (WITH_IO_URING)
|
||||
TARGET_INCLUDE_DIRECTORIES(h2o PUBLIC ${LIBURING_INCLUDE_DIRS})
|
||||
TARGET_LINK_DIRECTORIES(h2o PUBLIC ${LIBURING_LIBRARY_DIRS})
|
||||
TARGET_LINK_LIBRARIES(h2o ${LIBURING_LIBRARIES})
|
||||
ENDIF ()
|
||||
TARGET_LINK_LIBRARIES(h2o ${EXTRA_LIBS})
|
||||
|
||||
INSTALL(TARGETS h2o
|
||||
|
@ -88,6 +88,9 @@ This document describes the configuration directives common to all the protocols
|
||||
<li><a href="configure/base_directives.html#handshake-timeout">
|
||||
<code>handshake-timeout</code>
|
||||
</a></li>
|
||||
<li><a href="configure/base_directives.html#io_uring-batch-size">
|
||||
<code>io_uring-batch-size</code>
|
||||
</a></li>
|
||||
<li><a href="configure/base_directives.html#limit-request-body">
|
||||
<code>limit-request-body</code>
|
||||
</a></li>
|
||||
@ -103,6 +106,9 @@ This document describes the configuration directives common to all the protocols
|
||||
<li><a href="configure/base_directives.html#max-reprocesses">
|
||||
<code>max-reprocesses</code>
|
||||
</a></li>
|
||||
<li><a href="configure/base_directives.html#max-spare-pipes">
|
||||
<code>max-spare-pipes</code>
|
||||
</a></li>
|
||||
<li><a href="configure/base_directives.html#neverbleed-offload">
|
||||
<code>neverbleed-offload</code>
|
||||
</a></li>
|
||||
@ -711,6 +717,29 @@ Times spent for receiving <a href="configure/base_directives.html#listen-proxy-p
|
||||
<dd><code><pre>handshake-timeout: 10</pre></code>
|
||||
</dl>
|
||||
|
||||
<div id="io_uring-batch-size" class="directive-head">
|
||||
<div class="directive-since">experimental</div>
|
||||
<h3><a href="configure/base_directives.html#io_uring-batch-size"><code>"io_uring-batch-size"</code></a></h3>
|
||||
</div>
|
||||
|
||||
<dl class="directive-desc">
|
||||
<dt>Description:</dt>
|
||||
<dd>
|
||||
<p>
|
||||
Number of io_uring calls to issue at once. Increasing this number might reduce overhead.
|
||||
</p>
|
||||
|
||||
</dd>
|
||||
<dt><a href="configure/syntax_and_structure.html#config_levels">Level</a>:</dt>
|
||||
<dd>global</dd>
|
||||
<dt>Default:</dt>
|
||||
<dd><code><pre>io_uring-batch-size: 1</pre></code>
|
||||
<dt>See also:</dt>
|
||||
<dd><a href="configure/file_directives.html#file.io_uring"><code>file.io_uring</code></a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
<div id="limit-request-body" class="directive-head">
|
||||
<h3><a href="configure/base_directives.html#limit-request-body"><code>"limit-request-body"</code></a></h3>
|
||||
</div>
|
||||
@ -816,6 +845,41 @@ Limits the number of internal redirects.
|
||||
<dt>Default:</dt>
|
||||
<dd><code><pre>max-reprocesses: 5</pre></code>
|
||||
</dl>
|
||||
<div id="max-spare-pipes" class="directive-head">
|
||||
<h3><a href="configure/base_directives.html#max-spare-pipes"><code>"max-spare-pipes"</code></a></h3>
|
||||
</div>
|
||||
|
||||
<dl class="directive-desc">
|
||||
<dt>Description:</dt>
|
||||
<dd>
|
||||
<p>
|
||||
This setting specifies the maximum number of pipes retained for reuse, when <code>file.io_uring</code> or <code>proxy.zerocopy</code> is used.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The setting can be used to reduce lock contention in the kernel under high load.
|
||||
</p>
|
||||
<p>
|
||||
This maximum is applied per each worker thread.
|
||||
</p>
|
||||
<p>
|
||||
Setting this value to 0 will cause no pipes to be retained by h2o; the pipes will be closed after they are used.
|
||||
In this case, h2o will create new pipes each time they are needed.
|
||||
</p>
|
||||
<p>
|
||||
In previous versions, this configuration directive was called <code>proxy.max-spare-pipes</code>.
|
||||
</p>
|
||||
|
||||
</dd>
|
||||
<dt><a href="configure/syntax_and_structure.html#config_levels">Level</a>:</dt>
|
||||
<dd>global</dd>
|
||||
<dt>Default:</dt>
|
||||
<dd><code><pre>0</pre></code>
|
||||
<dt>See also:</dt>
|
||||
<dd><a href="configure/file_directives.html#file.io_uring"><code>file.io_uring</code></a>, <a href="configure/proxy_directives.html#proxy.zerocopy"><code>proxy.zerocopy</code></a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<div id="neverbleed-offload" class="directive-head">
|
||||
<div class="directive-since">experimental</div>
|
||||
<h3><a href="configure/base_directives.html#neverbleed-offload"><code>"neverbleed-offload"</code></a></h3>
|
||||
|
@ -86,6 +86,9 @@ Other directives modify the behavior of the mappings defined by the two.
|
||||
<li><a href="configure/file_directives.html#file.index">
|
||||
<code>file.index</code>
|
||||
</a></li>
|
||||
<li><a href="configure/file_directives.html#file.io_uring">
|
||||
<code>file.io_uring</code>
|
||||
</a></li>
|
||||
<li><a href="configure/file_directives.html#file.mime.addtypes">
|
||||
<code>file.mime.addtypes</code>
|
||||
</a></li>
|
||||
@ -267,6 +270,35 @@ The sequence of filenames are searched from left to right, and the first file th
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<div id="file.io_uring" class="directive-head">
|
||||
<div class="directive-since">experimental</div>
|
||||
<h3><a href="configure/file_directives.html#file.io_uring"><code>"file.io_uring"</code></a></h3>
|
||||
</div>
|
||||
|
||||
<dl class="directive-desc">
|
||||
<dt>Description:</dt>
|
||||
<dd>
|
||||
<p>
|
||||
If io_uring should be used for serving files.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
By default, H2O uses system calls such as pread (2) or sendfile (2), which block if the file being read is not in the page cache.
|
||||
This can prevent H2O's worker threads from making progress on any connection handled by the affected thread.
|
||||
When this flag is enabled, H2O is no longer blocked by the I/O calls, as the data is asynchronously copied from disk to the page cache before h2o attempts to access it.
|
||||
</p>
|
||||
|
||||
</dd>
|
||||
<dt><a href="configure/syntax_and_structure.html#config_levels">Level</a>:</dt>
|
||||
<dd>global, host, path</dd>
|
||||
<dt>Default:</dt>
|
||||
<dd><code><pre>file.io_uring: OFF</pre></code>
|
||||
<dt>See also:</dt>
|
||||
<dd><a href="configure/base_directives.html#io_uring-batch-size"><code>io_uring-batch-size</code></a>,
|
||||
<a href="configure/base_directives.html#max-spare-pipes"><code>max-spare-pipes</code></a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<div id="file.mime.addtypes" class="directive-head">
|
||||
<h3><a href="configure/file_directives.html#file.mime.addtypes"><code>"file.mime.addtypes"</code></a></h3>
|
||||
</div>
|
||||
|
@ -161,9 +161,6 @@ Following sections describe the configuration directives defined for the module.
|
||||
<li><a href="configure/proxy_directives.html#proxy.max-buffer-size">
|
||||
<code>proxy.max-buffer-size</code>
|
||||
</a></li>
|
||||
<li><a href="configure/proxy_directives.html#proxy.max-spare-pipes">
|
||||
<code>proxy.max-spare-pipes</code>
|
||||
</a></li>
|
||||
<li><a href="configure/proxy_directives.html#proxy.preserve-host">
|
||||
<code>proxy.preserve-host</code>
|
||||
</a></li>
|
||||
@ -852,37 +849,6 @@ But if the backend server has enough concurrency, <code>proxy.max-buffer-size</c
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<div id="proxy.max-spare-pipes" class="directive-head">
|
||||
<h3><a href="configure/proxy_directives.html#proxy.max-spare-pipes"><code>"proxy.max-spare-pipes"</code></a></h3>
|
||||
</div>
|
||||
|
||||
<dl class="directive-desc">
|
||||
<dt>Description:</dt>
|
||||
<dd>
|
||||
<p>
|
||||
This setting specifies the maximum number of pipes retained for reuse, when <code>proxy.zerocopy</code> is used.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This maximum is applied per each worker thread.
|
||||
The intention of this setting is to reduce lock contention in the kernel under high load when zerocopy is used.
|
||||
When this setting is set to a non-zero value, specified number of pipes will be allocated upon startup for each worker thread.
|
||||
</p>
|
||||
<p>
|
||||
Setting this value to 0 will cause no pipes to be retained by h2o; the pipes will be closed after they are used.
|
||||
In this case, h2o will create new pipes each time they are needed.
|
||||
</p>
|
||||
|
||||
</dd>
|
||||
<dt><a href="configure/syntax_and_structure.html#config_levels">Level</a>:</dt>
|
||||
<dd>global</dd>
|
||||
<dt>Default:</dt>
|
||||
<dd><code><pre>0</pre></code>
|
||||
<dt>See also:</dt>
|
||||
<dd><a href="configure/proxy_directives.html#proxy.zerocopy"><code>proxy.zerocopy</code></a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<div id="proxy.preserve-host" class="directive-head">
|
||||
<h3><a href="configure/proxy_directives.html#proxy.preserve-host"><code>"proxy.preserve-host"</code></a></h3>
|
||||
</div>
|
||||
@ -1203,7 +1169,7 @@ This approach does not reduce the total amount of bytes flowing through the CPU,
|
||||
<dt>Default:</dt>
|
||||
<dd><code><pre>proxy.zerocopy: OFF</pre></code>
|
||||
<dt>See also:</dt>
|
||||
<dd><a href="configure/base_directives.html#ssl-offload"><code>ssl-offload</code></a>, <a href="configure/proxy_directives.html#proxy.max-spare-pipes"><code>proxy.max-spare-pipes</code></a>
|
||||
<dd><a href="configure/base_directives.html#max-spare-pipes"><code>proxy.max-spare-pipes</code></a>, <a href="configure/base_directives.html#ssl-offload"><code>ssl-offload</code></a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
@ -896,6 +896,19 @@ Times spent for receiving the PROXY protocol and TLS handshake are counted.
|
||||
.RE
|
||||
.RE
|
||||
|
||||
.SS io_uring-batch-size
|
||||
Number of io_uring calls to issue at once. Increasing this number might reduce overhead.
|
||||
|
||||
.PP
|
||||
|
||||
.PP
|
||||
.BR See\ also:
|
||||
file.io_uring
|
||||
|
||||
.RE
|
||||
.RE
|
||||
|
||||
|
||||
.SS limit-request-body
|
||||
Maximum size of request body in bytes (e.g. content of POST).
|
||||
|
||||
@ -956,6 +969,32 @@ Limits the number of internal redirects.
|
||||
|
||||
.RE
|
||||
.RE
|
||||
.SS max-spare-pipes
|
||||
This setting specifies the maximum number of pipes retained for reuse, when file.io_uring or proxy.zerocopy is used.
|
||||
|
||||
.PP
|
||||
|
||||
|
||||
The setting can be used to reduce lock contention in the kernel under high load.
|
||||
|
||||
|
||||
This maximum is applied per each worker thread.
|
||||
|
||||
|
||||
Setting this value to 0 will cause no pipes to be retained by h2o; the pipes will be closed after they are used.
|
||||
In this case, h2o will create new pipes each time they are needed.
|
||||
|
||||
|
||||
In previous versions, this configuration directive was called proxy.max-spare-pipes.
|
||||
|
||||
|
||||
.PP
|
||||
.BR See\ also:
|
||||
file.io_uring, proxy.zerocopy
|
||||
|
||||
.RE
|
||||
.RE
|
||||
|
||||
.SS neverbleed-offload
|
||||
Sets an offload engine to be used with neverbleed.
|
||||
|
||||
@ -2923,6 +2962,25 @@ file.dir
|
||||
.RE
|
||||
.RE
|
||||
|
||||
.SS file.io_uring
|
||||
If io_uring should be used for serving files.
|
||||
|
||||
.PP
|
||||
|
||||
|
||||
By default, H2O uses system calls such as pread (2) or sendfile (2), which block if the file being read is not in the page cache.
|
||||
This can prevent H2O's worker threads from making progress on any connection handled by the affected thread.
|
||||
When this flag is enabled, H2O is no longer blocked by the I/O calls, as the data is asynchronously copied from disk to the page cache before h2o attempts to access it.
|
||||
|
||||
|
||||
.PP
|
||||
.BR See\ also:
|
||||
io_uring-batch-size,
|
||||
max-spare-pipes
|
||||
|
||||
.RE
|
||||
.RE
|
||||
|
||||
.SS file.mime.addtypes
|
||||
The directive modifies the MIME mappings by adding the specified MIME type mappings.
|
||||
|
||||
@ -3821,28 +3879,6 @@ temp-buffer-threshold
|
||||
.RE
|
||||
.RE
|
||||
|
||||
.SS proxy.max-spare-pipes
|
||||
This setting specifies the maximum number of pipes retained for reuse, when proxy.zerocopy is used.
|
||||
|
||||
.PP
|
||||
|
||||
|
||||
This maximum is applied per each worker thread.
|
||||
The intention of this setting is to reduce lock contention in the kernel under high load when zerocopy is used.
|
||||
When this setting is set to a non-zero value, specified number of pipes will be allocated upon startup for each worker thread.
|
||||
|
||||
|
||||
Setting this value to 0 will cause no pipes to be retained by h2o; the pipes will be closed after they are used.
|
||||
In this case, h2o will create new pipes each time they are needed.
|
||||
|
||||
|
||||
.PP
|
||||
.BR See\ also:
|
||||
proxy.zerocopy
|
||||
|
||||
.RE
|
||||
.RE
|
||||
|
||||
.SS proxy.preserve-host
|
||||
A boolean flag (ON or OFF) designating whether or not to pass Host header from incoming request to upstream.
|
||||
|
||||
@ -4039,7 +4075,7 @@ This approach does not reduce the total amount of bytes flowing through the CPU,
|
||||
|
||||
.PP
|
||||
.BR See\ also:
|
||||
ssl-offload, proxy.max-spare-pipes
|
||||
proxy.max-spare-pipes, ssl-offload
|
||||
|
||||
.RE
|
||||
.RE
|
||||
|
File diff suppressed because one or more lines are too long
13
h2o-probes.d
13
h2o-probes.d
@ -46,6 +46,19 @@ provider h2o {
|
||||
*/
|
||||
probe socket_write_tls_record(struct st_h2o_socket_t *sock, size_t write_size, size_t bytes_buffered);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
probe io_uring_splice(struct st_h2o_io_uring_cmd_t *cmd);
|
||||
/**
|
||||
*
|
||||
*/
|
||||
probe io_uring_submit(struct st_h2o_io_uring_cmd_t *cmd);
|
||||
/**
|
||||
*
|
||||
*/
|
||||
probe io_uring_end(struct st_h2o_io_uring_cmd_t *cmd);
|
||||
|
||||
/**
|
||||
* HTTP-level event, indicating that a request has been received.
|
||||
*/
|
||||
|
@ -64,6 +64,7 @@
|
||||
08F320E01E7A9CBD0038FA5A /* net.c in Sources */ = {isa = PBXBuildFile; fileRef = 08790DD91D80153600A04BC1 /* net.c */; settings = {COMPILER_FLAGS = "-Wno-conversion"; }; };
|
||||
08F320E11E7A9CBF0038FA5A /* read.c in Sources */ = {isa = PBXBuildFile; fileRef = 0812AB291D7FD54700004F23 /* read.c */; settings = {COMPILER_FLAGS = "-Wno-conversion"; }; };
|
||||
08F320E21E7A9CC20038FA5A /* sds.c in Sources */ = {isa = PBXBuildFile; fileRef = 08790DDB1D80154C00A04BC1 /* sds.c */; settings = {COMPILER_FLAGS = "-Wno-conversion"; }; };
|
||||
08FE0365299F9F8B00141FA2 /* io_uring.h in Headers */ = {isa = PBXBuildFile; fileRef = 08FE0364299F9F8B00141FA2 /* io_uring.h */; };
|
||||
100A55101C2BB15600C4E3E0 /* http_request.c in Sources */ = {isa = PBXBuildFile; fileRef = 100A550E1C2BB15100C4E3E0 /* http_request.c */; };
|
||||
101788B219B561AA0084C6D8 /* socket.c in Sources */ = {isa = PBXBuildFile; fileRef = 101788B119B561AA0084C6D8 /* socket.c */; };
|
||||
101B670C19ADD3380084A351 /* access_log.c in Sources */ = {isa = PBXBuildFile; fileRef = 101B670B19ADD3380084A351 /* access_log.c */; };
|
||||
@ -714,6 +715,7 @@
|
||||
082E1CCD2692F52200603AED /* driver_common.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = driver_common.cc; sourceTree = "<group>"; };
|
||||
082E1CCE2692F52200603AED /* h3_header_generator.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = h3_header_generator.c; sourceTree = "<group>"; };
|
||||
082E224A2692F52E00603AED /* quicly_mock.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = quicly_mock.c; sourceTree = "<group>"; };
|
||||
083453D62DBF48CE00CAB789 /* 80issues3479.t */ = {isa = PBXFileReference; lastKnownFileType = text; path = 80issues3479.t; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.perl; };
|
||||
084FC7C01D54B90D00E89F66 /* http2_debug_state.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = http2_debug_state.c; sourceTree = "<group>"; };
|
||||
084FC7C31D54BB9200E89F66 /* http2_debug_state.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = http2_debug_state.c; sourceTree = "<group>"; };
|
||||
08529A932AD4F6FF00A2C76B /* 80http2-reset-flood.t */ = {isa = PBXFileReference; lastKnownFileType = text; path = "80http2-reset-flood.t"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.perl; };
|
||||
@ -761,6 +763,8 @@
|
||||
08AD2A3E2CCB840800301CD6 /* 90h2olog-qlog.t */ = {isa = PBXFileReference; lastKnownFileType = text; path = "90h2olog-qlog.t"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.perl; };
|
||||
08AD2A3F2CCB840800301CD6 /* 90h2olog-socket.t */ = {isa = PBXFileReference; lastKnownFileType = text; path = "90h2olog-socket.t"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.perl; };
|
||||
08AD2A402CCF219600301CD6 /* h2olog */ = {isa = PBXFileReference; lastKnownFileType = text; path = h2olog; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.perl; };
|
||||
08B2EBF42D9F5E4000715398 /* test.pl */ = {isa = PBXFileReference; lastKnownFileType = text.script.perl; path = test.pl; sourceTree = "<group>"; };
|
||||
08B2EBF62DA09FD000715398 /* 40http3-io-batch10.t */ = {isa = PBXFileReference; lastKnownFileType = text; path = "40http3-io-batch10.t"; sourceTree = "<group>"; };
|
||||
08B3297E29407664009D6766 /* hpke.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hpke.c; sourceTree = "<group>"; };
|
||||
08B329832941B407009D6766 /* 40tls-ech.t */ = {isa = PBXFileReference; lastKnownFileType = text; path = "40tls-ech.t"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.perl; };
|
||||
08B3D43A26042CAF002F195C /* 50connect-deadlock.t */ = {isa = PBXFileReference; lastKnownFileType = text; path = "50connect-deadlock.t"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.perl; };
|
||||
@ -780,6 +784,8 @@
|
||||
08E9CC4D1E41F6660049DD26 /* embedded.c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = embedded.c.h; sourceTree = "<group>"; };
|
||||
08EFC4CF27FD8FAA004C532C /* 50file-shrink.t */ = {isa = PBXFileReference; lastKnownFileType = text; path = "50file-shrink.t"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.perl; };
|
||||
08EFC4DF27FE9F96004C532C /* throttle_resp.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = throttle_resp.c; sourceTree = "<group>"; };
|
||||
08FE0364299F9F8B00141FA2 /* io_uring.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = io_uring.h; sourceTree = "<group>"; };
|
||||
08FE0366299FA1FE00141FA2 /* io_uring.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = io_uring.c; sourceTree = "<group>"; };
|
||||
100A550C1C22857B00C4E3E0 /* 50mruby-htpasswd.t */ = {isa = PBXFileReference; lastKnownFileType = text; path = "50mruby-htpasswd.t"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.perl; };
|
||||
100A550E1C2BB15100C4E3E0 /* http_request.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = http_request.c; sourceTree = "<group>"; };
|
||||
100A55131C2E5FAC00C4E3E0 /* 50mruby-http-request.t */ = {isa = PBXFileReference; lastKnownFileType = text; path = "50mruby-http-request.t"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.perl; };
|
||||
@ -1508,6 +1514,14 @@
|
||||
path = 50mruby;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
08B2EBF52D9F5E4000715398 /* 40http3 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
08B2EBF42D9F5E4000715398 /* test.pl */,
|
||||
);
|
||||
path = 40http3;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
08B4D4391D75A2950079DFB5 /* hiredis */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1574,6 +1588,7 @@
|
||||
10D0905419F102C70043D458 /* http1client.c */,
|
||||
7D0341FA1FE4D5B60052E0A1 /* http2client.c */,
|
||||
E9E50472214A5B8A004DC170 /* http3client.c */,
|
||||
08FE0366299FA1FE00141FA2 /* io_uring.c */,
|
||||
10A3D3D11B4CDBDC00327CF9 /* memcached.c */,
|
||||
08CEA9D126701D7600B4BB6B /* rand.c */,
|
||||
08790DE31D8276EA00A04BC1 /* redis.c */,
|
||||
@ -2007,6 +2022,7 @@
|
||||
E9E50475214B3D28004DC170 /* http3_common.h */,
|
||||
E9757708221BB78C00D1EF74 /* http3_internal.h */,
|
||||
E9E5049321501CA5004DC170 /* http3_server.h */,
|
||||
08FE0364299F9F8B00141FA2 /* io_uring.h */,
|
||||
10A3D3D01B4CDBC700327CF9 /* memcached.h */,
|
||||
10D0904219F0BA780043D458 /* linklist.h */,
|
||||
10D0903919F0A51C0043D458 /* memory.h */,
|
||||
@ -2248,12 +2264,14 @@
|
||||
E9F677CA1FF47BD0006476D3 /* 40http2-h2spec.t */,
|
||||
080590262A5E463A00B2B10E /* 40http2-header-softerror.t */,
|
||||
E9BCE6921FF344D4003CEA11 /* 40http2-request-window-size.t */,
|
||||
08B2EBF52D9F5E4000715398 /* 40http3 */,
|
||||
E98041C22230C45E008B9745 /* 40http3.t */,
|
||||
086001BE2730B6E30043886F /* 40http3-corrupted-scid-initial.t */,
|
||||
E90BB43B24F38937006507EA /* 40http3-concurrency.t */,
|
||||
E90BB43C24F4834D006507EA /* 40http3-forward.t */,
|
||||
E9D49A5025FB561A00F4A80D /* 40http3-forward-initial.t */,
|
||||
0810E6AA286DACB600333FA4 /* 40http3-invalid-token.t */,
|
||||
08B2EBF62DA09FD000715398 /* 40http3-io-batch10.t */,
|
||||
085BDAE72B1DB4C2002851EA /* 40http3-priority.t */,
|
||||
E9414F8D24EBFBDA00273C59 /* 40http3-reconnect.t */,
|
||||
E9FC525C234B0A630076F35D /* 40http3-retry.t */,
|
||||
@ -2398,6 +2416,7 @@
|
||||
105534FE1A46134A00627ECB /* 80issues61.t */,
|
||||
1092E0011BEB1DDC001074BF /* 80issues579.t */,
|
||||
104481271BFC05FC0007863F /* 80issues595.t */,
|
||||
083453D62DBF48CE00CAB789 /* 80issues3479.t */,
|
||||
10952D591C5082F7000D664C /* 80issues-from-proxy-reproxy-to-different-host.t */,
|
||||
E9BCE6911FF326AC003CEA11 /* 80no-handler-vs-h2-post.t */,
|
||||
109EEFD81D77B336001F11D1 /* 80one-byte-window.t */,
|
||||
@ -2966,6 +2985,7 @@
|
||||
0812AB211D7FCFEB00004F23 /* async.h in Headers */,
|
||||
10D0903A19F0A51C0043D458 /* memory.h in Headers */,
|
||||
7DA3F5AF20E0B0400000222F /* token_table.h in Headers */,
|
||||
08FE0365299F9F8B00141FA2 /* io_uring.h in Headers */,
|
||||
104B9A2D1A4BE029009EEE64 /* version.h in Headers */,
|
||||
E980423B22463044008B9745 /* cc.h in Headers */,
|
||||
E9E5049421501CA5004DC170 /* http3_server.h in Headers */,
|
||||
|
@ -400,6 +400,10 @@ struct st_h2o_globalconf_t {
|
||||
* SSL handshake timeout
|
||||
*/
|
||||
uint64_t handshake_timeout;
|
||||
/**
|
||||
* maximum number of pipes to retain for reuse
|
||||
*/
|
||||
size_t max_spare_pipes;
|
||||
|
||||
struct {
|
||||
/**
|
||||
@ -534,10 +538,6 @@ struct st_h2o_globalconf_t {
|
||||
* maximum size to buffer for the response
|
||||
*/
|
||||
size_t max_buffer_size;
|
||||
/**
|
||||
* maximum number of pipes to retain for reuse
|
||||
*/
|
||||
size_t max_spare_pipes;
|
||||
/**
|
||||
* a boolean flag if set to true, instructs to use zero copy (i.e., splice to pipe then splice to socket) if possible
|
||||
*/
|
||||
@ -685,6 +685,13 @@ struct st_h2o_context_t {
|
||||
* open file cache
|
||||
*/
|
||||
h2o_filecache_t *filecache;
|
||||
/**
|
||||
* the list of spare pipes currently retained for reuse
|
||||
*/
|
||||
struct {
|
||||
int (*pipes)[2];
|
||||
size_t count;
|
||||
} spare_pipes;
|
||||
/**
|
||||
* context scope storage for general use
|
||||
*/
|
||||
@ -787,13 +794,6 @@ struct st_h2o_context_t {
|
||||
* the default connection pool for proxy
|
||||
*/
|
||||
h2o_httpclient_connection_pool_t connpool;
|
||||
/**
|
||||
* the list of spare pipes currently retained for reuse
|
||||
*/
|
||||
struct {
|
||||
int (*pipes)[2];
|
||||
size_t count;
|
||||
} spare_pipes;
|
||||
} proxy;
|
||||
|
||||
struct {
|
||||
@ -1653,7 +1653,15 @@ static int h2o_send_state_is_in_progress(h2o_send_state_t s);
|
||||
* @param state describes if the output is final, has an error, or is in progress
|
||||
*/
|
||||
void h2o_send(h2o_req_t *req, h2o_iovec_t *bufs, size_t bufcnt, h2o_send_state_t state);
|
||||
/**
|
||||
* Same as `h2o_send` but sends `h2o_sendvec_t`s
|
||||
*/
|
||||
void h2o_sendvec(h2o_req_t *req, h2o_sendvec_t *vecs, size_t veccnt, h2o_send_state_t state);
|
||||
/**
|
||||
* Wrapper around `h2o_sendvec` that sends the contents of pipe
|
||||
*/
|
||||
void h2o_send_from_pipe(h2o_req_t *req, int pipefd, size_t len, h2o_send_state_t send_state);
|
||||
|
||||
/**
|
||||
* creates an uninitialized prefilter and returns pointer to it
|
||||
*/
|
||||
@ -1823,6 +1831,14 @@ static void *h2o_context_get_logger_context(h2o_context_t *ctx, h2o_logger_t *lo
|
||||
* return the address associated with the key in the context storage
|
||||
*/
|
||||
static void **h2o_context_get_storage(h2o_context_t *ctx, size_t *key, void (*dispose_cb)(void *));
|
||||
/**
|
||||
* provides a new pipe that has O_NONBLOCK set, possibly reusing a cached one that is empty; returns a boolean indicating success
|
||||
*/
|
||||
int h2o_context_new_pipe(h2o_context_t *ctx, int fds[2]);
|
||||
/**
|
||||
* returns a pipe to the spare pipe cache
|
||||
*/
|
||||
void h2o_context_return_spare_pipe(h2o_context_t *ctx, int fds[2]);
|
||||
|
||||
/* built-in generators */
|
||||
|
||||
@ -2169,7 +2185,8 @@ enum {
|
||||
H2O_FILE_FLAG_NO_ETAG = 0x1,
|
||||
H2O_FILE_FLAG_DIR_LISTING = 0x2,
|
||||
H2O_FILE_FLAG_SEND_COMPRESSED = 0x4,
|
||||
H2O_FILE_FLAG_GUNZIP = 0x8
|
||||
H2O_FILE_FLAG_GUNZIP = 0x8,
|
||||
H2O_FILE_FLAG_IO_URING = 0x16,
|
||||
};
|
||||
|
||||
typedef struct st_h2o_file_handler_t h2o_file_handler_t;
|
||||
|
81
include/h2o/io_uring.h
Normal file
81
include/h2o/io_uring.h
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2022,2023 Kazuho Oku, Fastly
|
||||
*
|
||||
* 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 h2o__io_uring_h
|
||||
#define h2o__io_uring_h
|
||||
|
||||
#if !H2O_USE_IO_URING
|
||||
#error "this file may be included only when io_uring support is available"
|
||||
#endif
|
||||
|
||||
#include <liburing.h>
|
||||
|
||||
typedef struct st_h2o_io_uring_cmd_t h2o_io_uring_cmd_t;
|
||||
typedef void (*h2o_io_uring_cb)(h2o_io_uring_cmd_t *);
|
||||
|
||||
struct st_h2o_io_uring_queue_t {
|
||||
struct st_h2o_io_uring_cmd_t *head;
|
||||
struct st_h2o_io_uring_cmd_t **tail;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
typedef struct st_h2o_io_uring_t {
|
||||
struct io_uring uring;
|
||||
h2o_socket_t *sock_notify;
|
||||
struct st_h2o_io_uring_queue_t submission, completion;
|
||||
h2o_timer_t delayed;
|
||||
} h2o_io_uring_t;
|
||||
|
||||
/**
|
||||
* Object used for buffering and tracking requests to io_uring. It is defined here as a public type, because h2o-probes.d referes to
|
||||
* the type, in addition to io_uring.c.
|
||||
*/
|
||||
struct st_h2o_io_uring_cmd_t {
|
||||
struct {
|
||||
h2o_io_uring_cb func;
|
||||
void *data;
|
||||
} cb;
|
||||
int result;
|
||||
struct st_h2o_io_uring_cmd_t *next;
|
||||
struct {
|
||||
int fd_in;
|
||||
int64_t off_in;
|
||||
int fd_out;
|
||||
int64_t off_out;
|
||||
unsigned nbytes;
|
||||
unsigned splice_flags;
|
||||
} splice_;
|
||||
};
|
||||
|
||||
extern size_t h2o_io_uring_batch_size;
|
||||
|
||||
/**
|
||||
* initializes structure related to async I/O of `loop`
|
||||
*/
|
||||
void h2o_io_uring_init(h2o_loop_t *loop);
|
||||
/**
|
||||
* Calls splice using io_uring. The callback might get called synchronously, depending on the condition (e.g., if data being read is
|
||||
* in page cache and h2o_io_uring_batch_size == 1).
|
||||
*/
|
||||
void h2o_io_uring_splice(h2o_loop_t *loop, int fd_in, int64_t off_in, int fd_out, int64_t off_out, unsigned nbytes,
|
||||
unsigned splice_flags, h2o_io_uring_cb cb, void *data);
|
||||
|
||||
#endif
|
@ -111,6 +111,9 @@ typedef struct st_h2o_socket_t h2o_socket_t;
|
||||
|
||||
typedef void (*h2o_socket_cb)(h2o_socket_t *sock, const char *err);
|
||||
|
||||
/* used by probes */
|
||||
struct st_h2o_io_uring_cmd_t;
|
||||
|
||||
#if H2O_USE_LIBUV
|
||||
#include "socket/uv-binding.h"
|
||||
#else
|
||||
|
@ -84,6 +84,11 @@ int h2o_evloop_run(h2o_evloop_t *loop, int32_t max_wait);
|
||||
static void h2o_timer_link(h2o_evloop_t *loop, uint64_t delay_ticks, h2o_timer_t *timer);
|
||||
#define h2o_timer_unlink h2o_timerwheel_unlink
|
||||
|
||||
#if H2O_USE_IO_URING
|
||||
struct st_h2o_io_uring_t;
|
||||
struct st_h2o_io_uring_t *h2o_evloop__io_uring(h2o_evloop_t *loop);
|
||||
#endif
|
||||
|
||||
/* inline definitions */
|
||||
|
||||
static inline struct timeval h2o_gettimeofday(h2o_evloop_t *loop)
|
||||
|
222
lib/common/io_uring.c
Normal file
222
lib/common/io_uring.c
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (c) 2022,2023 Kazuho Oku, Fastly
|
||||
*
|
||||
* 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 <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
#include "h2o/socket.h"
|
||||
#include "h2o/io_uring.h"
|
||||
#include "../probes_.h"
|
||||
|
||||
size_t h2o_io_uring_batch_size = 1;
|
||||
|
||||
static void init_queue(struct st_h2o_io_uring_queue_t *queue)
|
||||
{
|
||||
queue->head = NULL;
|
||||
queue->tail = &queue->head;
|
||||
queue->size = 0;
|
||||
}
|
||||
|
||||
static void insert_queue(struct st_h2o_io_uring_queue_t *queue, struct st_h2o_io_uring_cmd_t *cmd)
|
||||
{
|
||||
assert(cmd->next == NULL);
|
||||
*queue->tail = cmd;
|
||||
queue->tail = &cmd->next;
|
||||
++queue->size;
|
||||
}
|
||||
|
||||
static struct st_h2o_io_uring_cmd_t *pop_queue(struct st_h2o_io_uring_queue_t *queue)
|
||||
{
|
||||
struct st_h2o_io_uring_cmd_t *popped;
|
||||
|
||||
if ((popped = queue->head) != NULL) {
|
||||
if ((queue->head = popped->next) == NULL)
|
||||
queue->tail = &queue->head;
|
||||
--queue->size;
|
||||
popped->next = NULL;
|
||||
}
|
||||
return popped;
|
||||
}
|
||||
|
||||
static int submit_commands(struct st_h2o_io_uring_t *io_uring, int can_delay)
|
||||
{
|
||||
if (can_delay && io_uring->submission.size < h2o_io_uring_batch_size)
|
||||
return 0;
|
||||
|
||||
int made_progress = 0;
|
||||
|
||||
while (io_uring->submission.head != NULL) {
|
||||
struct io_uring_sqe *sqe;
|
||||
if ((sqe = io_uring_get_sqe(&io_uring->uring)) == NULL)
|
||||
break;
|
||||
struct st_h2o_io_uring_cmd_t *cmd = pop_queue(&io_uring->submission);
|
||||
assert(cmd != NULL);
|
||||
H2O_PROBE(IO_URING_SUBMIT, cmd);
|
||||
io_uring_prep_splice(sqe, cmd->splice_.fd_in, cmd->splice_.off_in, cmd->splice_.fd_out, cmd->splice_.off_out,
|
||||
cmd->splice_.nbytes, cmd->splice_.splice_flags);
|
||||
sqe->user_data = (uint64_t)cmd;
|
||||
made_progress = 1;
|
||||
}
|
||||
|
||||
if (made_progress) {
|
||||
int ret;
|
||||
while ((ret = io_uring_submit(&io_uring->uring)) == -EINTR)
|
||||
;
|
||||
if (ret < 0)
|
||||
h2o_fatal("io_uring_submit:%s", strerror(-ret));
|
||||
}
|
||||
|
||||
return made_progress;
|
||||
}
|
||||
|
||||
static int check_completion(struct st_h2o_io_uring_t *io_uring, struct st_h2o_io_uring_cmd_t *cmd_sync)
|
||||
{
|
||||
int cmd_sync_done = 0, ret;
|
||||
|
||||
while (1) {
|
||||
struct st_h2o_io_uring_cmd_t *cmd;
|
||||
|
||||
{ /* obtain completed command and its result */
|
||||
struct io_uring_cqe *cqe;
|
||||
while ((ret = io_uring_peek_cqe(&io_uring->uring, &cqe)) == -EINTR)
|
||||
;
|
||||
if (ret != 0)
|
||||
break;
|
||||
cmd = (struct st_h2o_io_uring_cmd_t *)cqe->user_data;
|
||||
cmd->result = cqe->res;
|
||||
io_uring_cqe_seen(&io_uring->uring, cqe);
|
||||
}
|
||||
|
||||
/* link to completion list or indicate to the caller that `cmd_sync` has completed */
|
||||
if (cmd == cmd_sync) {
|
||||
cmd_sync_done = 1;
|
||||
} else {
|
||||
insert_queue(&io_uring->completion, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
return cmd_sync_done;
|
||||
}
|
||||
|
||||
static int dispatch_completed(struct st_h2o_io_uring_t *io_uring)
|
||||
{
|
||||
if (io_uring->completion.head == NULL)
|
||||
return 0;
|
||||
|
||||
do {
|
||||
struct st_h2o_io_uring_cmd_t *cmd = pop_queue(&io_uring->completion);
|
||||
H2O_PROBE(IO_URING_END, cmd);
|
||||
cmd->cb.func(cmd);
|
||||
free(cmd);
|
||||
} while (io_uring->completion.head != NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void start_command(h2o_loop_t *loop, struct st_h2o_io_uring_t *io_uring, struct st_h2o_io_uring_cmd_t *cmd)
|
||||
{
|
||||
int needs_timer = 0;
|
||||
|
||||
insert_queue(&io_uring->submission, cmd);
|
||||
|
||||
submit_commands(io_uring, 1);
|
||||
|
||||
/* if we have submitted all commands up to the current one, fetch completion events in hope that we might be able to complete
|
||||
* the current one synchronously (as doing so improves locality) */
|
||||
if (io_uring->submission.head == NULL) {
|
||||
if (check_completion(io_uring, cmd)) {
|
||||
cmd->cb.func(cmd);
|
||||
free(cmd);
|
||||
cmd = NULL;
|
||||
}
|
||||
if (io_uring->completion.head != NULL)
|
||||
needs_timer = 1;
|
||||
} else {
|
||||
needs_timer = 1;
|
||||
}
|
||||
|
||||
if (needs_timer && !h2o_timer_is_linked(&io_uring->delayed))
|
||||
h2o_timer_link(loop, 0, &io_uring->delayed);
|
||||
}
|
||||
|
||||
void h2o_io_uring_splice(h2o_loop_t *loop, int fd_in, int64_t off_in, int fd_out, int64_t off_out, unsigned nbytes,
|
||||
unsigned splice_flags, h2o_io_uring_cb cb, void *data)
|
||||
{
|
||||
struct st_h2o_io_uring_t *io_uring = h2o_evloop__io_uring(loop);
|
||||
|
||||
/* build command */
|
||||
struct st_h2o_io_uring_cmd_t *cmd = h2o_mem_alloc(sizeof(*cmd));
|
||||
*cmd = (struct st_h2o_io_uring_cmd_t){
|
||||
.cb.func = cb,
|
||||
.cb.data = data,
|
||||
.splice_.fd_in = fd_in,
|
||||
.splice_.off_in = off_in,
|
||||
.splice_.fd_out = fd_out,
|
||||
.splice_.off_out = off_out,
|
||||
.splice_.nbytes = nbytes,
|
||||
.splice_.splice_flags = splice_flags,
|
||||
};
|
||||
H2O_PROBE(IO_URING_SPLICE, cmd);
|
||||
|
||||
start_command(loop, io_uring, cmd);
|
||||
}
|
||||
|
||||
static void run_uring(struct st_h2o_io_uring_t *io_uring)
|
||||
{
|
||||
/* Repeatedly read cqe, until we bocome certain we haven't issued more read commands. */
|
||||
do {
|
||||
check_completion(io_uring, NULL);
|
||||
} while (dispatch_completed(io_uring) || submit_commands(io_uring, 0));
|
||||
|
||||
assert(io_uring->completion.head == NULL);
|
||||
}
|
||||
|
||||
static void on_notify(h2o_socket_t *sock, const char *err)
|
||||
{
|
||||
assert(err == NULL);
|
||||
|
||||
h2o_loop_t *loop = h2o_socket_get_loop(sock);
|
||||
struct st_h2o_io_uring_t *io_uring = h2o_evloop__io_uring(loop);
|
||||
run_uring(io_uring);
|
||||
}
|
||||
|
||||
static void on_delayed(h2o_timer_t *_timer)
|
||||
{
|
||||
struct st_h2o_io_uring_t *io_uring = H2O_STRUCT_FROM_MEMBER(struct st_h2o_io_uring_t, delayed, _timer);
|
||||
run_uring(io_uring);
|
||||
}
|
||||
|
||||
void h2o_io_uring_init(h2o_loop_t *loop)
|
||||
{
|
||||
struct st_h2o_io_uring_t *io_uring = h2o_evloop__io_uring(loop);
|
||||
|
||||
int ret;
|
||||
if ((ret = io_uring_queue_init(16, &io_uring->uring, 0)) != 0)
|
||||
h2o_fatal("io_uring_queue_init:%s", strerror(-ret));
|
||||
|
||||
io_uring->sock_notify = h2o_evloop_socket_create(loop, io_uring->uring.ring_fd, H2O_SOCKET_FLAG_DONT_READ);
|
||||
h2o_socket_read_start(io_uring->sock_notify, on_notify);
|
||||
|
||||
init_queue(&io_uring->submission);
|
||||
init_queue(&io_uring->completion);
|
||||
h2o_timer_init(&io_uring->delayed, on_delayed);
|
||||
}
|
@ -23,6 +23,9 @@
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/epoll.h>
|
||||
#if H2O_USE_IO_URING
|
||||
#include "h2o/io_uring.h"
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
#define DEBUG_LOG(...) h2o_error_printf(__VA_ARGS__)
|
||||
@ -33,6 +36,9 @@
|
||||
struct st_h2o_evloop_epoll_t {
|
||||
h2o_evloop_t super;
|
||||
int ep;
|
||||
#if H2O_USE_IO_URING
|
||||
h2o_io_uring_t io_uring;
|
||||
#endif
|
||||
};
|
||||
|
||||
static int change_epoll_mode(struct st_h2o_evloop_socket_t *sock, uint32_t events)
|
||||
@ -312,6 +318,7 @@ static void evloop_do_dispose(h2o_evloop_t *_loop)
|
||||
struct st_h2o_evloop_epoll_t *loop = (struct st_h2o_evloop_epoll_t *)_loop;
|
||||
close(loop->ep);
|
||||
}
|
||||
|
||||
h2o_evloop_t *h2o_evloop_create(void)
|
||||
{
|
||||
struct st_h2o_evloop_epoll_t *loop = (struct st_h2o_evloop_epoll_t *)create_evloop(sizeof(*loop));
|
||||
@ -321,5 +328,17 @@ h2o_evloop_t *h2o_evloop_create(void)
|
||||
h2o_fatal("h2o_evloop_create: epoll_create1 failed:%d:%s\n", errno, h2o_strerror_r(errno, buf, sizeof(buf)));
|
||||
}
|
||||
|
||||
#if H2O_USE_IO_URING
|
||||
h2o_io_uring_init(&loop->super);
|
||||
#endif
|
||||
|
||||
return &loop->super;
|
||||
}
|
||||
|
||||
#if H2O_USE_IO_URING
|
||||
struct st_h2o_io_uring_t *h2o_evloop__io_uring(h2o_evloop_t *_loop)
|
||||
{
|
||||
struct st_h2o_evloop_epoll_t *loop = (struct st_h2o_evloop_epoll_t *)_loop;
|
||||
return &loop->io_uring;
|
||||
}
|
||||
#endif
|
||||
|
@ -413,6 +413,14 @@ static int on_config_handshake_timeout(h2o_configurator_command_t *cmd, h2o_conf
|
||||
return config_timeout(cmd, node, &ctx->globalconf->handshake_timeout);
|
||||
}
|
||||
|
||||
static int on_config_max_spare_pipes(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
|
||||
{
|
||||
if (h2o_configurator_scanf(cmd, node, "%zu", &ctx->globalconf->max_spare_pipes) != 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_config_http1_request_timeout(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
|
||||
{
|
||||
return config_timeout(cmd, node, &ctx->globalconf->http1.req_timeout);
|
||||
@ -1042,6 +1050,12 @@ void h2o_configurator__init_core(h2o_globalconf_t *conf)
|
||||
h2o_configurator_define_command(&c->super, "handshake-timeout",
|
||||
H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
|
||||
on_config_handshake_timeout);
|
||||
h2o_configurator_define_command(&c->super, "max-spare-pipes",
|
||||
H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
|
||||
on_config_max_spare_pipes);
|
||||
h2o_configurator_define_command(&c->super, "proxy.max-spare-pipes" /* for compatibility, retain old name as an alias */,
|
||||
H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
|
||||
on_config_max_spare_pipes);
|
||||
h2o_configurator_define_command(&c->super, "http1-request-timeout",
|
||||
H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
|
||||
on_config_http1_request_timeout);
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
#include "cloexec.h"
|
||||
#include "h2o.h"
|
||||
#include "h2o/memcached.h"
|
||||
|
||||
@ -93,6 +94,7 @@ void h2o_context_init(h2o_context_t *ctx, h2o_loop_t *loop, h2o_globalconf_t *co
|
||||
ctx->queue = h2o_multithread_create_queue(loop);
|
||||
h2o_multithread_register_receiver(ctx->queue, &ctx->receivers.hostinfo_getaddr, h2o_hostinfo_getaddr_receiver);
|
||||
ctx->filecache = h2o_filecache_create(config->filecache.capacity);
|
||||
ctx->spare_pipes.pipes = h2o_mem_alloc(sizeof(ctx->spare_pipes.pipes[0]) * config->max_spare_pipes);
|
||||
|
||||
h2o_linklist_init_anchor(&ctx->_conns.active);
|
||||
h2o_linklist_init_anchor(&ctx->_conns.idle);
|
||||
@ -109,20 +111,8 @@ void h2o_context_init(h2o_context_t *ctx, h2o_loop_t *loop, h2o_globalconf_t *co
|
||||
ctx->proxy.client_ctx.protocol_selector.ratio.http2 = ctx->globalconf->proxy.protocol_ratio.http2;
|
||||
ctx->proxy.client_ctx.protocol_selector.ratio.http3 = ctx->globalconf->proxy.protocol_ratio.http3;
|
||||
ctx->proxy.connpool.socketpool = &ctx->globalconf->proxy.global_socketpool;
|
||||
ctx->proxy.spare_pipes.pipes = h2o_mem_alloc(sizeof(ctx->proxy.spare_pipes.pipes[0]) * config->proxy.max_spare_pipes);
|
||||
h2o_linklist_init_anchor(&ctx->proxy.connpool.http2.conns);
|
||||
|
||||
#ifdef __linux__
|
||||
/* pre-fill the pipe cache at context init */
|
||||
for (i = 0; i < config->proxy.max_spare_pipes; ++i) {
|
||||
if (pipe2(ctx->proxy.spare_pipes.pipes[i], O_NONBLOCK | O_CLOEXEC) != 0) {
|
||||
char errbuf[256];
|
||||
h2o_fatal("pipe2(2) failed:%s", h2o_strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
}
|
||||
ctx->proxy.spare_pipes.count++;
|
||||
}
|
||||
#endif
|
||||
|
||||
ctx->_module_configs = h2o_mem_alloc(sizeof(*ctx->_module_configs) * config->_num_config_slots);
|
||||
memset(ctx->_module_configs, 0, sizeof(*ctx->_module_configs) * config->_num_config_slots);
|
||||
|
||||
@ -146,19 +136,12 @@ void h2o_context_init(h2o_context_t *ctx, h2o_loop_t *loop, h2o_globalconf_t *co
|
||||
void h2o_context_dispose(h2o_context_t *ctx)
|
||||
{
|
||||
h2o_globalconf_t *config = ctx->globalconf;
|
||||
size_t i, j;
|
||||
|
||||
for (size_t i = 0; i < ctx->proxy.spare_pipes.count; ++i) {
|
||||
close(ctx->proxy.spare_pipes.pipes[i][0]);
|
||||
close(ctx->proxy.spare_pipes.pipes[i][1]);
|
||||
}
|
||||
free(ctx->proxy.spare_pipes.pipes);
|
||||
|
||||
h2o_socketpool_unregister_loop(&ctx->globalconf->proxy.global_socketpool, ctx->loop);
|
||||
|
||||
for (i = 0; config->hosts[i] != NULL; ++i) {
|
||||
for (size_t i = 0; config->hosts[i] != NULL; ++i) {
|
||||
h2o_hostconf_t *hostconf = config->hosts[i];
|
||||
for (j = 0; j != hostconf->paths.size; ++j) {
|
||||
for (size_t j = 0; j != hostconf->paths.size; ++j) {
|
||||
h2o_pathconf_t *pathconf = hostconf->paths.entries[j];
|
||||
h2o_context_dispose_pathconf_context(ctx, pathconf);
|
||||
}
|
||||
@ -168,11 +151,17 @@ void h2o_context_dispose(h2o_context_t *ctx)
|
||||
free(ctx->_module_configs);
|
||||
/* what should we do here? assert(!h2o_linklist_is_empty(&ctx->http2._conns); */
|
||||
|
||||
for (size_t i = 0; i < ctx->spare_pipes.count; ++i) {
|
||||
close(ctx->spare_pipes.pipes[i][0]);
|
||||
close(ctx->spare_pipes.pipes[i][1]);
|
||||
}
|
||||
free(ctx->spare_pipes.pipes);
|
||||
|
||||
h2o_filecache_destroy(ctx->filecache);
|
||||
ctx->filecache = NULL;
|
||||
|
||||
/* clear storage */
|
||||
for (i = 0; i != ctx->storage.size; ++i) {
|
||||
for (size_t i = 0; i != ctx->storage.size; ++i) {
|
||||
h2o_context_storage_item_t *item = ctx->storage.entries + i;
|
||||
if (item->dispose != NULL) {
|
||||
item->dispose(item->data);
|
||||
@ -298,3 +287,59 @@ void h2o_conn_set_state(h2o_conn_t *conn, h2o_conn_state_t state)
|
||||
link_conn(conn);
|
||||
}
|
||||
}
|
||||
|
||||
int h2o_context_new_pipe(h2o_context_t *ctx, int fds[2])
|
||||
{
|
||||
if (ctx->spare_pipes.count > 0) {
|
||||
int *src = ctx->spare_pipes.pipes[--ctx->spare_pipes.count];
|
||||
fds[0] = src[0];
|
||||
fds[1] = src[1];
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
return pipe2(fds, O_NONBLOCK | O_CLOEXEC) == 0;
|
||||
#else
|
||||
if (cloexec_pipe(fds) != 0)
|
||||
return 0;
|
||||
fcntl(fds[0], F_SETFL, O_NONBLOCK);
|
||||
fcntl(fds[1], F_SETFL, O_NONBLOCK);
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int empty_pipe(int fd)
|
||||
{
|
||||
ssize_t ret;
|
||||
char buf[1024];
|
||||
|
||||
drain_more:
|
||||
while ((ret = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR)
|
||||
;
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
} else if (ret == -1) {
|
||||
if (errno == EAGAIN)
|
||||
return 1;
|
||||
return 0;
|
||||
} else if (ret == sizeof(buf)) {
|
||||
goto drain_more;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void h2o_context_return_spare_pipe(h2o_context_t *ctx, int fds[2])
|
||||
{
|
||||
assert(fds[0] != -1);
|
||||
assert(fds[1] != -1);
|
||||
|
||||
if (ctx->spare_pipes.count < ctx->globalconf->max_spare_pipes && empty_pipe(fds[0])) {
|
||||
int *dst = ctx->spare_pipes.pipes[ctx->spare_pipes.count++];
|
||||
dst[0] = fds[0];
|
||||
dst[1] = fds[1];
|
||||
} else {
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
}
|
||||
}
|
||||
|
103
lib/core/proxy.c
103
lib/core/proxy.c
@ -306,27 +306,6 @@ static h2o_httpclient_t *detach_client(struct rp_generator_t *self)
|
||||
return client;
|
||||
}
|
||||
|
||||
static int empty_pipe(int fd)
|
||||
{
|
||||
ssize_t ret;
|
||||
char buf[1024];
|
||||
|
||||
drain_more:
|
||||
while ((ret = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR)
|
||||
;
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
} else if (ret == -1) {
|
||||
if (errno == EAGAIN)
|
||||
return 1;
|
||||
return 0;
|
||||
} else if (ret == sizeof(buf)) {
|
||||
goto drain_more;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void do_close(struct rp_generator_t *self)
|
||||
{
|
||||
/**
|
||||
@ -346,16 +325,7 @@ static void do_close(struct rp_generator_t *self)
|
||||
}
|
||||
h2o_timer_unlink(&self->send_headers_timeout);
|
||||
if (self->pipe_reader.fds[0] != -1) {
|
||||
h2o_context_t *ctx = self->src_req->conn->ctx;
|
||||
if (ctx->proxy.spare_pipes.count < ctx->globalconf->proxy.max_spare_pipes && empty_pipe(self->pipe_reader.fds[0])) {
|
||||
int *dst = ctx->proxy.spare_pipes.pipes[ctx->proxy.spare_pipes.count++];
|
||||
dst[0] = self->pipe_reader.fds[0];
|
||||
dst[1] = self->pipe_reader.fds[1];
|
||||
} else {
|
||||
close(self->pipe_reader.fds[0]);
|
||||
close(self->pipe_reader.fds[1]);
|
||||
}
|
||||
|
||||
h2o_context_return_spare_pipe(self->src_req->conn->ctx, self->pipe_reader.fds);
|
||||
self->pipe_reader.fds[0] = -1;
|
||||
}
|
||||
}
|
||||
@ -395,47 +365,6 @@ static void do_send(struct rp_generator_t *self)
|
||||
h2o_send(self->src_req, vecs, veccnt, ststate);
|
||||
}
|
||||
|
||||
static int from_pipe_read(h2o_sendvec_t *vec, void *dst, size_t len)
|
||||
{
|
||||
struct rp_generator_t *self = (void *)vec->cb_arg[0];
|
||||
|
||||
while (len != 0) {
|
||||
ssize_t ret;
|
||||
while ((ret = read(self->pipe_reader.fds[0], dst, len)) == -1 && errno == EINTR)
|
||||
;
|
||||
if (ret <= 0) {
|
||||
assert(errno != EAGAIN);
|
||||
return 0;
|
||||
}
|
||||
dst += ret;
|
||||
len -= ret;
|
||||
vec->len -= ret;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static size_t from_pipe_send(h2o_sendvec_t *vec, int sockfd, size_t len)
|
||||
{
|
||||
#ifdef __linux__
|
||||
struct rp_generator_t *self = (void *)vec->cb_arg[0];
|
||||
|
||||
ssize_t bytes_sent;
|
||||
while ((bytes_sent = splice(self->pipe_reader.fds[0], NULL, sockfd, NULL, len, SPLICE_F_NONBLOCK)) == -1 && errno == EINTR)
|
||||
;
|
||||
if (bytes_sent == -1 && errno == EAGAIN)
|
||||
return 0;
|
||||
if (bytes_sent <= 0)
|
||||
return SIZE_MAX;
|
||||
|
||||
vec->len -= bytes_sent;
|
||||
|
||||
return bytes_sent;
|
||||
#else
|
||||
h2o_fatal("%s:not implemented", __FUNCTION__);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void do_send_from_pipe(struct rp_generator_t *self)
|
||||
{
|
||||
h2o_send_state_t send_state = self->had_body_error ? H2O_SEND_STATE_ERROR
|
||||
@ -452,16 +381,13 @@ static void do_send_from_pipe(struct rp_generator_t *self)
|
||||
return;
|
||||
}
|
||||
|
||||
static const h2o_sendvec_callbacks_t callbacks = {.read_ = from_pipe_read, .send_ = from_pipe_send};
|
||||
h2o_sendvec_t vec = {.callbacks = &callbacks};
|
||||
if ((vec.len = self->body_bytes_read - self->body_bytes_sent) > H2O_PULL_SENDVEC_MAX_SIZE)
|
||||
vec.len = H2O_PULL_SENDVEC_MAX_SIZE;
|
||||
vec.cb_arg[0] = (uint64_t)self;
|
||||
vec.cb_arg[1] = 0; /* unused */
|
||||
size_t len;
|
||||
if ((len = self->body_bytes_read - self->body_bytes_sent) > H2O_PULL_SENDVEC_MAX_SIZE)
|
||||
len = H2O_PULL_SENDVEC_MAX_SIZE;
|
||||
|
||||
self->body_bytes_sent += vec.len;
|
||||
self->body_bytes_sent += len;
|
||||
self->pipe_inflight = 1;
|
||||
h2o_sendvec(self->src_req, &vec, 1, send_state);
|
||||
h2o_send_from_pipe(self->src_req, self->pipe_reader.fds[0], len, send_state);
|
||||
}
|
||||
|
||||
static void do_proceed(h2o_generator_t *generator, h2o_req_t *req)
|
||||
@ -717,20 +643,13 @@ static h2o_httpclient_body_cb on_head(h2o_httpclient_t *client, const char *errs
|
||||
|
||||
/* switch to using pipe reader, if the opportunity is provided */
|
||||
if (args->pipe_reader != NULL) {
|
||||
#ifdef __linux__
|
||||
if (req->conn->ctx->proxy.spare_pipes.count > 0) {
|
||||
int *src = req->conn->ctx->proxy.spare_pipes.pipes[--req->conn->ctx->proxy.spare_pipes.count];
|
||||
self->pipe_reader.fds[0] = src[0];
|
||||
self->pipe_reader.fds[1] = src[1];
|
||||
if (h2o_context_new_pipe(req->conn->ctx, self->pipe_reader.fds)) {
|
||||
args->pipe_reader->fd = self->pipe_reader.fds[1];
|
||||
args->pipe_reader->on_body_piped = on_body_piped;
|
||||
} else {
|
||||
if (pipe2(self->pipe_reader.fds, O_NONBLOCK | O_CLOEXEC) != 0) {
|
||||
char errbuf[256];
|
||||
h2o_fatal("pipe2(2) failed:%s", h2o_strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
}
|
||||
assert(self->pipe_reader.fds[0] == -1); /* check the field remains marked as unused */
|
||||
h2o_req_log_error(req, "lib/core/proxy.c", "failed to allocate zero-copy pipe; falling back to read/write");
|
||||
}
|
||||
args->pipe_reader->fd = self->pipe_reader.fds[1];
|
||||
args->pipe_reader->on_body_piped = on_body_piped;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* if httpclient has no received body at this time, immediately send only headers using zero timeout */
|
||||
|
@ -19,6 +19,7 @@
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -551,6 +552,55 @@ void h2o_sendvec(h2o_req_t *req, h2o_sendvec_t *bufs, size_t bufcnt, h2o_send_st
|
||||
do_sendvec(req, bufs, bufcnt, state);
|
||||
}
|
||||
|
||||
static int from_pipe_read(h2o_sendvec_t *vec, void *dst, size_t len)
|
||||
{
|
||||
int fd = vec->cb_arg[0];
|
||||
|
||||
while (len != 0) {
|
||||
ssize_t ret;
|
||||
while ((ret = read(fd, dst, len)) == -1 && errno == EINTR)
|
||||
;
|
||||
if (ret <= 0) {
|
||||
assert(errno != EAGAIN);
|
||||
return 0;
|
||||
}
|
||||
dst += ret;
|
||||
len -= ret;
|
||||
vec->len -= ret;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static size_t from_pipe_send(h2o_sendvec_t *vec, int sockfd, size_t len)
|
||||
{
|
||||
int fd = vec->cb_arg[0];
|
||||
|
||||
ssize_t bytes_sent;
|
||||
while ((bytes_sent = splice(fd, NULL, sockfd, NULL, len, SPLICE_F_NONBLOCK)) == -1 && errno == EINTR)
|
||||
;
|
||||
if (bytes_sent == -1 && errno == EAGAIN)
|
||||
return 0;
|
||||
if (bytes_sent <= 0)
|
||||
return SIZE_MAX;
|
||||
|
||||
vec->len -= bytes_sent;
|
||||
|
||||
return bytes_sent;
|
||||
}
|
||||
#else
|
||||
#define from_pipe_send NULL
|
||||
#endif
|
||||
|
||||
void h2o_send_from_pipe(h2o_req_t *req, int pipefd, size_t len, h2o_send_state_t send_state)
|
||||
{
|
||||
static const h2o_sendvec_callbacks_t callbacks = {.read_ = from_pipe_read, .send_ = from_pipe_send};
|
||||
h2o_sendvec_t vec = {.callbacks = &callbacks, .len = len, .cb_arg[0] = pipefd};
|
||||
|
||||
h2o_sendvec(req, &vec, 1, send_state);
|
||||
}
|
||||
|
||||
h2o_req_prefilter_t *h2o_add_prefilter(h2o_req_t *req, size_t alignment, size_t sz)
|
||||
{
|
||||
h2o_req_prefilter_t *prefilter = h2o_mem_alloc_pool_aligned(&req->pool, alignment, sz);
|
||||
|
@ -127,6 +127,24 @@ static int on_config_dir_listing(h2o_configurator_command_t *cmd, h2o_configurat
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_config_io_uring(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
|
||||
{
|
||||
struct st_h2o_file_configurator_t *self = (void *)cmd->configurator;
|
||||
|
||||
switch (h2o_configurator_get_one_of(cmd, node, "OFF,ON")) {
|
||||
case 0: /* off */
|
||||
self->vars->flags &= ~H2O_FILE_FLAG_IO_URING;
|
||||
break;
|
||||
case 1: /* on */
|
||||
self->vars->flags |= H2O_FILE_FLAG_IO_URING;
|
||||
break;
|
||||
default: /* error */
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char **dup_strlist(const char **s)
|
||||
{
|
||||
size_t i;
|
||||
@ -194,4 +212,8 @@ void h2o_file_register_configurator(h2o_globalconf_t *globalconf)
|
||||
(H2O_CONFIGURATOR_FLAG_ALL_LEVELS & ~H2O_CONFIGURATOR_FLAG_EXTENSION) |
|
||||
H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
|
||||
on_config_dir_listing);
|
||||
h2o_configurator_define_command(&self->super, "file.io_uring",
|
||||
(H2O_CONFIGURATOR_FLAG_ALL_LEVELS & ~H2O_CONFIGURATOR_FLAG_EXTENSION) |
|
||||
H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
|
||||
on_config_io_uring);
|
||||
}
|
||||
|
@ -523,14 +523,6 @@ static int on_config_emit_missing_date_header(h2o_configurator_command_t *cmd, h
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_config_max_spare_pipes(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
|
||||
{
|
||||
if (h2o_configurator_scanf(cmd, node, "%zu", &ctx->globalconf->proxy.max_spare_pipes) != 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_config_zerocopy(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
|
||||
{
|
||||
ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON,ALWAYS");
|
||||
@ -805,8 +797,6 @@ void h2o_proxy_register_configurator(h2o_globalconf_t *conf)
|
||||
on_config_emit_missing_date_header);
|
||||
h2o_configurator_define_command(&c->super, "proxy.zerocopy", H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
|
||||
on_config_zerocopy);
|
||||
h2o_configurator_define_command(&c->super, "proxy.max-spare-pipes",
|
||||
H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_max_spare_pipes);
|
||||
h2o_configurator_define_headers_commands(conf, &c->super, "proxy.header", get_headers_commands);
|
||||
h2o_configurator_define_command(&c->super, "proxy.max-buffer-size",
|
||||
H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
|
||||
|
@ -34,8 +34,10 @@
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "h2o.h"
|
||||
#if H2O_USE_IO_URING
|
||||
#include "h2o/io_uring.h"
|
||||
#endif
|
||||
|
||||
#define MAX_BUF_SIZE 65000
|
||||
#define BOUNDARY_SIZE 20
|
||||
@ -47,7 +49,6 @@ struct st_h2o_sendfile_generator_t {
|
||||
h2o_filecache_ref_t *ref;
|
||||
off_t off;
|
||||
} file;
|
||||
h2o_req_t *req;
|
||||
size_t bytesleft;
|
||||
h2o_iovec_t content_encoding;
|
||||
unsigned send_vary : 1;
|
||||
@ -66,6 +67,13 @@ struct st_h2o_sendfile_generator_t {
|
||||
char last_modified[H2O_TIMESTR_RFC1123_LEN + 1];
|
||||
char etag[H2O_FILECACHE_ETAG_MAXLEN + 1];
|
||||
} header_bufs;
|
||||
#if H2O_USE_IO_URING
|
||||
/**
|
||||
* back pointer to the request which is necessary for splicing async; becomes NULL when the generator is stopped
|
||||
*/
|
||||
h2o_req_t *src_req;
|
||||
int splice_fds[2];
|
||||
#endif
|
||||
};
|
||||
|
||||
struct st_h2o_file_handler_t {
|
||||
@ -119,6 +127,19 @@ static void close_file(struct st_h2o_sendfile_generator_t *self)
|
||||
h2o_filecache_close_file(self->file.ref);
|
||||
self->file.ref = NULL;
|
||||
}
|
||||
#if H2O_USE_IO_URING
|
||||
if (self->splice_fds[0] != -1) {
|
||||
if (self->src_req != NULL) {
|
||||
h2o_context_return_spare_pipe(self->src_req->conn->ctx, self->splice_fds);
|
||||
} else {
|
||||
/* TODO return pipe upon abrupt close too? maybe that's not need */
|
||||
close(self->splice_fds[0]);
|
||||
close(self->splice_fds[1]);
|
||||
}
|
||||
self->splice_fds[0] = -1;
|
||||
self->splice_fds[1] = -1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void on_generator_dispose(void *_self)
|
||||
@ -127,6 +148,40 @@ static void on_generator_dispose(void *_self)
|
||||
close_file(self);
|
||||
}
|
||||
|
||||
#if H2O_USE_IO_URING
|
||||
|
||||
static void do_stop_async_splice(h2o_generator_t *_self, h2o_req_t *req)
|
||||
{
|
||||
struct st_h2o_sendfile_generator_t *self = (void *)_self;
|
||||
self->src_req = NULL;
|
||||
}
|
||||
|
||||
static void do_proceed_on_splice_complete(h2o_io_uring_cmd_t *cmd)
|
||||
{
|
||||
struct st_h2o_sendfile_generator_t *self = cmd->cb.data;
|
||||
|
||||
if (self->src_req == NULL) {
|
||||
h2o_mem_release_shared(self);
|
||||
return;
|
||||
}
|
||||
|
||||
h2o_mem_release_shared(self);
|
||||
|
||||
if (cmd->result <= 0) {
|
||||
assert(cmd->result != -EINTR); /* could this ever happen? */
|
||||
h2o_send(self->src_req, NULL, 0, H2O_SEND_STATE_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
self->file.off += cmd->result;
|
||||
self->bytesleft -= cmd->result;
|
||||
|
||||
h2o_send_from_pipe(self->src_req, self->splice_fds[0], cmd->result,
|
||||
self->bytesleft != 0 ? H2O_SEND_STATE_IN_PROGRESS : H2O_SEND_STATE_FINAL);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int do_pread(h2o_sendvec_t *src, void *dst, size_t len)
|
||||
{
|
||||
struct st_h2o_sendfile_generator_t *self = (void *)src->cb_arg[0];
|
||||
@ -149,7 +204,7 @@ static int do_pread(h2o_sendvec_t *src, void *dst, size_t len)
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
size_t do_sendfile(int sockfd, int filefd, off_t off, size_t len)
|
||||
static size_t do_sendfile(int sockfd, int filefd, off_t off, size_t len)
|
||||
{
|
||||
off_t iooff = off;
|
||||
ssize_t ret;
|
||||
@ -160,7 +215,7 @@ size_t do_sendfile(int sockfd, int filefd, off_t off, size_t len)
|
||||
return ret;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
size_t do_sendfile(int sockfd, int filefd, off_t off, size_t len)
|
||||
static size_t do_sendfile(int sockfd, int filefd, off_t off, size_t len)
|
||||
{
|
||||
off_t iolen = len;
|
||||
int ret;
|
||||
@ -171,7 +226,7 @@ size_t do_sendfile(int sockfd, int filefd, off_t off, size_t len)
|
||||
return iolen;
|
||||
}
|
||||
#elif defined(__FreeBSD__)
|
||||
size_t do_sendfile(int sockfd, int filefd, off_t off, size_t len)
|
||||
static size_t do_sendfile(int sockfd, int filefd, off_t off, size_t len)
|
||||
{
|
||||
off_t outlen;
|
||||
int ret;
|
||||
@ -182,10 +237,10 @@ size_t do_sendfile(int sockfd, int filefd, off_t off, size_t len)
|
||||
return outlen;
|
||||
}
|
||||
#else
|
||||
#define NO_SENDFILE 1
|
||||
#define sendvec_sendfile NULL
|
||||
#endif
|
||||
#if !NO_SENDFILE
|
||||
static size_t sendvec_send(h2o_sendvec_t *src, int sockfd, size_t len)
|
||||
#if !defined(sendvec_sendfile)
|
||||
static size_t sendvec_sendfile(h2o_sendvec_t *src, int sockfd, size_t len)
|
||||
{
|
||||
struct st_h2o_sendfile_generator_t *self = (void *)src->cb_arg[0];
|
||||
ssize_t bytes_sent = do_sendfile(sockfd, self->file.ref->fd, (off_t)src->cb_arg[1], len);
|
||||
@ -199,32 +254,27 @@ static size_t sendvec_send(h2o_sendvec_t *src, int sockfd, size_t len)
|
||||
|
||||
static void do_proceed(h2o_generator_t *_self, h2o_req_t *req)
|
||||
{
|
||||
static const h2o_sendvec_callbacks_t sendvec_callbacks = {
|
||||
do_pread,
|
||||
#if !NO_SENDFILE
|
||||
sendvec_send,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct st_h2o_sendfile_generator_t *self = (void *)_self;
|
||||
h2o_sendvec_t vec;
|
||||
h2o_send_state_t send_state;
|
||||
size_t bytes_to_send = self->bytesleft < H2O_PULL_SENDVEC_MAX_SIZE ? self->bytesleft : H2O_PULL_SENDVEC_MAX_SIZE;
|
||||
|
||||
vec.len = self->bytesleft < H2O_PULL_SENDVEC_MAX_SIZE ? self->bytesleft : H2O_PULL_SENDVEC_MAX_SIZE;
|
||||
vec.callbacks = &sendvec_callbacks;
|
||||
vec.cb_arg[0] = (uint64_t)self;
|
||||
vec.cb_arg[1] = self->file.off;
|
||||
/* if io_uring is to be used, addref so that the self would not be released, then call `h2o_io_uring_splice_file` */
|
||||
#if H2O_USE_IO_URING
|
||||
if (self->splice_fds[0] != -1) {
|
||||
h2o_mem_addref_shared(self);
|
||||
h2o_io_uring_splice(self->src_req->conn->ctx->loop, self->file.ref->fd, self->file.off, self->splice_fds[1], -1,
|
||||
bytes_to_send, 0, do_proceed_on_splice_complete, self);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const h2o_sendvec_callbacks_t sendvec_callbacks = {.read_ = do_pread, .send_ = sendvec_sendfile};
|
||||
h2o_sendvec_t vec = {
|
||||
.callbacks = &sendvec_callbacks, .len = bytes_to_send, .cb_arg[0] = (uint64_t)self, .cb_arg[1] = self->file.off};
|
||||
|
||||
self->file.off += vec.len;
|
||||
self->bytesleft -= vec.len;
|
||||
if (self->bytesleft == 0) {
|
||||
send_state = H2O_SEND_STATE_FINAL;
|
||||
} else {
|
||||
send_state = H2O_SEND_STATE_IN_PROGRESS;
|
||||
}
|
||||
|
||||
/* send (closed in do_pread) */
|
||||
h2o_sendvec(req, &vec, 1, send_state);
|
||||
h2o_sendvec(req, &vec, 1, self->bytesleft != 0 ? H2O_SEND_STATE_IN_PROGRESS : H2O_SEND_STATE_FINAL);
|
||||
}
|
||||
|
||||
static void do_multirange_proceed(h2o_generator_t *_self, h2o_req_t *req)
|
||||
@ -334,7 +384,6 @@ Opened:
|
||||
self->super.stop = NULL;
|
||||
self->file.ref = fileref;
|
||||
self->file.off = 0;
|
||||
self->req = NULL;
|
||||
self->bytesleft = self->file.ref->st.st_size;
|
||||
self->ranged.range_count = 0;
|
||||
self->ranged.range_infos = NULL;
|
||||
@ -342,6 +391,18 @@ Opened:
|
||||
self->send_vary = (flags & H2O_FILE_FLAG_SEND_COMPRESSED) != 0;
|
||||
self->send_etag = (flags & H2O_FILE_FLAG_NO_ETAG) == 0;
|
||||
self->gunzip = gunzip;
|
||||
#if H2O_USE_IO_URING
|
||||
int try_async_splice = (flags & H2O_FILE_FLAG_IO_URING) != 0 && self->bytesleft != 0;
|
||||
if (try_async_splice && h2o_context_new_pipe(req->conn->ctx, self->splice_fds)) {
|
||||
self->super.stop = do_stop_async_splice;
|
||||
self->src_req = req;
|
||||
} else {
|
||||
if (try_async_splice)
|
||||
h2o_req_log_error(req, "lib/handler/file.c", "failed to allocate a pipe for async I/O; falling back to blocking I/O");
|
||||
self->splice_fds[0] = -1;
|
||||
self->splice_fds[1] = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return self;
|
||||
}
|
||||
@ -378,9 +439,6 @@ static void send_decompressed(h2o_ostream_t *_self, h2o_req_t *req, h2o_sendvec_
|
||||
static void do_send_file(struct st_h2o_sendfile_generator_t *self, h2o_req_t *req, int status, const char *reason,
|
||||
h2o_iovec_t mime_type, h2o_mime_attributes_t *mime_attr, int is_get)
|
||||
{
|
||||
/* link the request */
|
||||
self->req = req;
|
||||
|
||||
/* setup response */
|
||||
req->res.status = status;
|
||||
req->res.reason = reason;
|
||||
|
@ -106,6 +106,13 @@ const char h2o_hpack_err_invalid_content_length_header[] = "invalid content-leng
|
||||
const char h2o_hpack_soft_err_found_invalid_char_in_header_name[] = "found an invalid character in header name";
|
||||
const char h2o_hpack_soft_err_found_invalid_char_in_header_value[] = "found an invalid character in header value";
|
||||
|
||||
static int header_value_valid_as_whole(const char *s, size_t len)
|
||||
{
|
||||
if (len != 0 && (s[0] == 0x20 || s[0] == 0x09 || s[len - 1] == 0x20 || s[len - 1] == 0x09))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t h2o_hpack_decode_huffman(char *_dst, unsigned *soft_errors, const uint8_t *src, size_t len, int is_name,
|
||||
const char **err_desc)
|
||||
{
|
||||
@ -140,7 +147,7 @@ size_t h2o_hpack_decode_huffman(char *_dst, unsigned *soft_errors, const uint8_t
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((seen_char_types & NGHTTP2_HUFF_INVALID_FOR_HEADER_VALUE) != 0)
|
||||
if ((seen_char_types & NGHTTP2_HUFF_INVALID_FOR_HEADER_VALUE) != 0 || !header_value_valid_as_whole(_dst, dst - _dst))
|
||||
*soft_errors |= H2O_HPACK_SOFT_ERROR_BIT_INVALID_VALUE;
|
||||
}
|
||||
|
||||
@ -183,13 +190,6 @@ int h2o_hpack_validate_header_name(unsigned *soft_errors, const char *s, size_t
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int header_value_valid_as_whole(const char *s, size_t len)
|
||||
{
|
||||
if (len != 0 && (s[0] == 0x20 || s[0] == 0x09 || s[len - 1] == 0x20 || s[len - 1] == 0x09))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void h2o_hpack_validate_header_value(unsigned *soft_errors, const char *s, size_t len)
|
||||
{
|
||||
/* surrounding whitespace RFC 9113 8.2.1 */
|
||||
@ -240,8 +240,6 @@ static h2o_iovec_t *decode_string(h2o_mem_pool_t *pool, unsigned *soft_errors, c
|
||||
if ((ret->len = h2o_hpack_decode_huffman(ret->base, soft_errors, *src, len, is_header_name, err_desc)) == SIZE_MAX)
|
||||
return NULL;
|
||||
ret->base[ret->len] = '\0';
|
||||
if (!is_header_name && !header_value_valid_as_whole(ret->base, ret->len))
|
||||
*soft_errors |= H2O_HPACK_SOFT_ERROR_BIT_INVALID_VALUE;
|
||||
} else {
|
||||
if (len > src_end - *src)
|
||||
return NULL;
|
||||
|
@ -50,9 +50,10 @@ RUN (cd curl-7.81.0 && ./configure --prefix=/usr/local --with-openssl --without-
|
||||
|
||||
# cpan modules
|
||||
RUN apt-get install --yes cpanminus
|
||||
RUN apt-get install --yes libfcgi-perl libfcgi-procmanager-perl libipc-signal-perl libjson-perl liblist-moreutils-perl libplack-perl libscope-guard-perl libtest-exception-perl libwww-perl libio-socket-ssl-perl
|
||||
RUN apt-get install --yes libbsd-resource-perl libfcgi-perl libfcgi-procmanager-perl libipc-signal-perl libjson-perl liblist-moreutils-perl libplack-perl libscope-guard-perl libtest-exception-perl libwww-perl libio-socket-ssl-perl
|
||||
ENV PERL_CPANM_OPT="--mirror https://cpan.metacpan.org/"
|
||||
RUN cpanm -n Test::More Starlet Protocol::HTTP2 Net::DNS::Nameserver Test::TCP
|
||||
RUN cpanm -n Test::More Starlet Protocol::HTTP2 Test::TCP
|
||||
RUN cpanm -n NLNETLABS/Net-DNS-1.36.tar.gz # Net-DNS 1.39 has issues around use of alarm, fork, etc.
|
||||
|
||||
# h2spec
|
||||
RUN curl -Ls https://github.com/summerwind/h2spec/releases/download/v2.6.0/h2spec_linux_amd64.tar.gz | tar zx -C /usr/local/bin
|
||||
|
@ -46,6 +46,7 @@ RUN apt-get install --yes libnghttp2-dev \
|
||||
# perl
|
||||
RUN apt-get install --yes \
|
||||
cpanminus \
|
||||
libbsd-resource-perl \
|
||||
libfcgi-perl \
|
||||
libfcgi-procmanager-perl \
|
||||
libipc-signal-perl \
|
||||
|
@ -54,6 +54,7 @@ RUN apt-get install --yes \
|
||||
# perl
|
||||
RUN apt-get install --yes \
|
||||
cpanminus \
|
||||
libbsd-resource-perl \
|
||||
libfcgi-perl \
|
||||
libfcgi-procmanager-perl \
|
||||
libipc-signal-perl \
|
||||
@ -97,7 +98,7 @@ RUN mkdir -p /opt/src \
|
||||
RUN apt-get install --yes golang-go
|
||||
RUN mkdir -p /opt/src && cd /opt/src && git clone --depth=1 https://github.com/google/boringssl.git \
|
||||
&& (cd boringssl && mkdir build && cd build \
|
||||
&& cmake .. -DCMAKE_INSTALL_PREFIX=/opt/boringssl && make install && cp decrepit/libdecrepit.a /opt/boringssl/lib/ \
|
||||
&& cmake .. -DCMAKE_INSTALL_PREFIX=/opt/boringssl && make install && cp libdecrepit.a /opt/boringssl/lib/ \
|
||||
&& make clean \
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
IMAGE_NAME=h2oserver/h2o-ci
|
||||
VARIANT=unknown
|
||||
DOCKER_OPTS=
|
||||
PROJECT_DIR=$(shell pwd)
|
||||
|
||||
ALL:
|
||||
@ -8,10 +9,12 @@ ALL:
|
||||
@echo 'Command is either `build` or `push`.'
|
||||
@echo 'Variant might be ubuntu1604, ubuntu2004 (corresponds to misc/docker-ci/Dockerfile.$$variant).'
|
||||
@echo ''
|
||||
@echo 'DOCKER_OPTS can also be set; e.g., DOCKER_OPTS=--network=host'
|
||||
@echo ''
|
||||
|
||||
|
||||
build:
|
||||
cd misc/docker-ci/docker-root && docker build --tag "$(IMAGE_NAME):$(VARIANT)" -f "../Dockerfile.$(VARIANT)" .
|
||||
cd misc/docker-ci/docker-root && docker build $(DOCKER_OPTS) --tag "$(IMAGE_NAME):$(VARIANT)" -f "../Dockerfile.$(VARIANT)" .
|
||||
|
||||
push: build
|
||||
docker push "$(IMAGE_NAME):$(VARIANT)"
|
||||
|
25
src/main.c
25
src/main.c
@ -95,6 +95,9 @@
|
||||
#if H2O_USE_MRUBY
|
||||
#include "h2o/mruby_.h"
|
||||
#endif
|
||||
#if H2O_USE_IO_URING
|
||||
#include "h2o/io_uring.h"
|
||||
#endif
|
||||
#include "standalone.h"
|
||||
#include "../lib/probes_.h"
|
||||
|
||||
@ -3466,6 +3469,22 @@ static int on_config_neverbleed_offload(h2o_configurator_command_t *cmd, h2o_con
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_config_io_uring_batch_size(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
|
||||
{
|
||||
#if H2O_USE_IO_URING
|
||||
if (h2o_configurator_scanf(cmd, node, "%zu", &h2o_io_uring_batch_size) != 0)
|
||||
return -1;
|
||||
if (h2o_io_uring_batch_size == 0) {
|
||||
h2o_configurator_errprintf(cmd, node, "value must be above zero");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
h2o_configurator_errprintf(cmd, node, "support for io_uring is not available");
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static yoml_t *load_config(yoml_parse_args_t *parse_args, yoml_t *source)
|
||||
{
|
||||
FILE *fp;
|
||||
@ -4600,6 +4619,9 @@ static void setup_configurators(void)
|
||||
on_config_ssl_offload);
|
||||
h2o_configurator_define_command(c, "neverbleed-offload", H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
|
||||
on_config_neverbleed_offload);
|
||||
h2o_configurator_define_command(c, "io_uring-batch-size",
|
||||
H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
|
||||
on_config_io_uring_batch_size);
|
||||
}
|
||||
|
||||
h2o_access_log_register_configurator(&conf.globalconf);
|
||||
@ -4776,6 +4798,9 @@ int main(int argc, char **argv)
|
||||
#endif
|
||||
#if PTLS_HAVE_AEGIS
|
||||
printf("libaegis: YES\n");
|
||||
#endif
|
||||
#if H2O_USE_IO_URING
|
||||
printf("io_uring: YES\n");
|
||||
#endif
|
||||
exit(0);
|
||||
case 'h':
|
||||
|
@ -455,6 +455,19 @@ $ctx->{directive}->(
|
||||
Times spent for receiving <a href="configure/base_directives.html#listen-proxy-protocol">the PROXY protocol</a> and TLS handshake are counted.
|
||||
? })
|
||||
|
||||
<?
|
||||
$ctx->{directive}->(
|
||||
name => "io_uring-batch-size",
|
||||
levels => [ qw(global) ],
|
||||
default => "io_uring-batch-size: 1",
|
||||
desc => q{Number of io_uring calls to issue at once. Increasing this number might reduce overhead.},
|
||||
experimental => 1,
|
||||
see_also => render_mt(<<'EOT'),
|
||||
<a href="configure/file_directives.html#file.io_uring"><code>file.io_uring</code></a>
|
||||
EOT
|
||||
)->(sub {})
|
||||
?>
|
||||
|
||||
<?
|
||||
$ctx->{directive}->(
|
||||
name => "limit-request-body",
|
||||
@ -516,6 +529,33 @@ $ctx->{directive}->(
|
||||
desc => q{Limits the number of internal redirects.},
|
||||
)->(sub {});
|
||||
|
||||
<?
|
||||
$ctx->{directive}->(
|
||||
name => "max-spare-pipes",
|
||||
levels => [ qw(global) ],
|
||||
desc => q{This setting specifies the maximum number of pipes retained for reuse, when <code>file.io_uring</code> or <code>proxy.zerocopy</code> is used.},
|
||||
default => 0,
|
||||
see_also => render_mt(<<'EOT'),
|
||||
<a href="configure/file_directives.html#file.io_uring"><code>file.io_uring</code></a>, <a href="configure/proxy_directives.html#proxy.zerocopy"><code>proxy.zerocopy</code></a>
|
||||
EOT
|
||||
)->(sub {
|
||||
?>
|
||||
<p>
|
||||
The setting can be used to reduce lock contention in the kernel under high load.
|
||||
</p>
|
||||
<p>
|
||||
This maximum is applied per each worker thread.
|
||||
</p>
|
||||
<p>
|
||||
Setting this value to 0 will cause no pipes to be retained by h2o; the pipes will be closed after they are used.
|
||||
In this case, h2o will create new pipes each time they are needed.
|
||||
</p>
|
||||
<p>
|
||||
In previous versions, this configuration directive was called <code>proxy.max-spare-pipes</code>.
|
||||
</p>
|
||||
? })
|
||||
|
||||
<?
|
||||
$ctx->{directive}->(
|
||||
name => "neverbleed-offload",
|
||||
levels => [ qw(global) ],
|
||||
|
@ -114,6 +114,26 @@ The sequence of filenames are searched from left to right, and the first file th
|
||||
</p>
|
||||
? })
|
||||
|
||||
<?
|
||||
$ctx->{directive}->(
|
||||
name => "file.io_uring",
|
||||
levels => [ qw(global host path) ],
|
||||
default => "file.io_uring: OFF",
|
||||
desc => q{If io_uring should be used for serving files.},
|
||||
experimental => 1,
|
||||
see_also => render_mt(<<'EOT'),
|
||||
<a href="configure/base_directives.html#io_uring-batch-size"><code>io_uring-batch-size</code></a>,
|
||||
<a href="configure/base_directives.html#max-spare-pipes"><code>max-spare-pipes</code></a>
|
||||
EOT
|
||||
)->(sub {
|
||||
?>
|
||||
<p>
|
||||
By default, H2O uses system calls such as pread (2) or sendfile (2), which block if the file being read is not in the page cache.
|
||||
This can prevent H2O's worker threads from making progress on any connection handled by the affected thread.
|
||||
When this flag is enabled, H2O is no longer blocked by the I/O calls, as the data is asynchronously copied from disk to the page cache before h2o attempts to access it.
|
||||
</p>
|
||||
? })
|
||||
|
||||
<?
|
||||
$ctx->{directive}->(
|
||||
name => "file.mime.addtypes",
|
||||
|
@ -361,28 +361,6 @@ But if the backend server has enough concurrency, <code>proxy.max-buffer-size</c
|
||||
</p>
|
||||
? })
|
||||
|
||||
<?
|
||||
$ctx->{directive}->(
|
||||
name => "proxy.max-spare-pipes",
|
||||
levels => [ qw(global) ],
|
||||
desc => q{This setting specifies the maximum number of pipes retained for reuse, when <code>proxy.zerocopy</code> is used.},
|
||||
default => 0,
|
||||
see_also => render_mt(<<'EOT'),
|
||||
<a href="configure/proxy_directives.html#proxy.zerocopy"><code>proxy.zerocopy</code></a>
|
||||
EOT
|
||||
)->(sub {
|
||||
?>
|
||||
<p>
|
||||
This maximum is applied per each worker thread.
|
||||
The intention of this setting is to reduce lock contention in the kernel under high load when zerocopy is used.
|
||||
When this setting is set to a non-zero value, specified number of pipes will be allocated upon startup for each worker thread.
|
||||
</p>
|
||||
<p>
|
||||
Setting this value to 0 will cause no pipes to be retained by h2o; the pipes will be closed after they are used.
|
||||
In this case, h2o will create new pipes each time they are needed.
|
||||
</p>
|
||||
? })
|
||||
|
||||
<?
|
||||
$ctx->{directive}->(
|
||||
name => "proxy.preserve-host",
|
||||
@ -567,7 +545,7 @@ $ctx->{directive}->(
|
||||
desc => q{Sets the use of zerocopy operations for forwarding the response body.},
|
||||
experimental => 1,
|
||||
see_also => render_mt(<<'EOT'),
|
||||
<a href="configure/base_directives.html#ssl-offload"><code>ssl-offload</code></a>, <a href="configure/proxy_directives.html#proxy.max-spare-pipes"><code>proxy.max-spare-pipes</code></a>
|
||||
<a href="configure/base_directives.html#max-spare-pipes"><code>proxy.max-spare-pipes</code></a>, <a href="configure/base_directives.html#ssl-offload"><code>ssl-offload</code></a>
|
||||
EOT
|
||||
)->(sub {
|
||||
?>
|
||||
|
@ -194,17 +194,45 @@ static void test_decode_literal_invalid_value(void)
|
||||
{
|
||||
h2o_qpack_decoder_t *dec = h2o_qpack_create_decoder(4096, 10);
|
||||
|
||||
static const uint8_t input[] = {
|
||||
0, 0, /* required_inesrt_count=0, base=0 */
|
||||
0xd1, /* :method: GET */
|
||||
0xd7, /* :scheme: https */
|
||||
0x50, 3, 'a', '\n', 'b', /* :authority: a\nb */
|
||||
0xc1, /* :path: / */
|
||||
};
|
||||
do_test_decode_request(dec, 0, h2o_iovec_init(input, sizeof(input)), H2O_HTTP2_ERROR_INVALID_HEADER_CHAR,
|
||||
h2o_hpack_soft_err_found_invalid_char_in_header_value, h2o_iovec_init(H2O_STRLIT("GET")),
|
||||
&H2O_URL_SCHEME_HTTPS, h2o_iovec_init(H2O_STRLIT("a\nb")), h2o_iovec_init(H2O_STRLIT("/")), SIZE_MAX,
|
||||
NULL, 0, h2o_iovec_init(NULL, 0));
|
||||
{
|
||||
static const uint8_t input[] = {
|
||||
0, 0, /* required_inesrt_count=0, base=0 */
|
||||
0xd1, /* :method: GET */
|
||||
0xd7, /* :scheme: https */
|
||||
0x50, 3, 'a', '\n', 'b', /* :authority: a\nb */
|
||||
0xc1, /* :path: / */
|
||||
};
|
||||
do_test_decode_request(dec, 0, h2o_iovec_init(input, sizeof(input)), H2O_HTTP2_ERROR_INVALID_HEADER_CHAR,
|
||||
h2o_hpack_soft_err_found_invalid_char_in_header_value, h2o_iovec_init(H2O_STRLIT("GET")),
|
||||
&H2O_URL_SCHEME_HTTPS, h2o_iovec_init(H2O_STRLIT("a\nb")), h2o_iovec_init(H2O_STRLIT("/")), SIZE_MAX,
|
||||
NULL, 0, h2o_iovec_init(NULL, 0));
|
||||
}
|
||||
{
|
||||
static const uint8_t input[] = {
|
||||
0, 0, /* required_inesrt_count=0, base=0 */
|
||||
0xd1, /* :method: GET */
|
||||
0xd7, /* :scheme: https */
|
||||
0x50, 3, ' ', 'a', 'b', /* :authority: SP ab */
|
||||
0xc1, /* :path: / */
|
||||
};
|
||||
do_test_decode_request(dec, 0, h2o_iovec_init(input, sizeof(input)), H2O_HTTP2_ERROR_INVALID_HEADER_CHAR,
|
||||
h2o_hpack_soft_err_found_invalid_char_in_header_value, h2o_iovec_init(H2O_STRLIT("GET")),
|
||||
&H2O_URL_SCHEME_HTTPS, h2o_iovec_init(H2O_STRLIT(" ab")), h2o_iovec_init(H2O_STRLIT("/")), SIZE_MAX,
|
||||
NULL, 0, h2o_iovec_init(NULL, 0));
|
||||
}
|
||||
{
|
||||
static const uint8_t input[] = {
|
||||
0, 0, /* required_inesrt_count=0, base=0 */
|
||||
0xd1, /* :method: GET */
|
||||
0xd7, /* :scheme: https */
|
||||
0x50, 0x83, 0x50, 0x71, 0xff, /* :authority: SP ab (in huffman)*/
|
||||
0xc1, /* :path: / */
|
||||
};
|
||||
do_test_decode_request(dec, 0, h2o_iovec_init(input, sizeof(input)), H2O_HTTP2_ERROR_INVALID_HEADER_CHAR,
|
||||
h2o_hpack_soft_err_found_invalid_char_in_header_value, h2o_iovec_init(H2O_STRLIT("GET")),
|
||||
&H2O_URL_SCHEME_HTTPS, h2o_iovec_init(H2O_STRLIT(" ab")), h2o_iovec_init(H2O_STRLIT("/")), SIZE_MAX,
|
||||
NULL, 0, h2o_iovec_init(NULL, 0));
|
||||
}
|
||||
|
||||
h2o_qpack_destroy_decoder(dec);
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ EOT
|
||||
|
||||
# Start 1k concurrent requests by sending 1k POST headers followed by 1k DATA END_STREAMs.
|
||||
my $output = run_with_h2get_simple($server, <<"EOR");
|
||||
N = 1000
|
||||
N = 900
|
||||
|
||||
puts "Send HEADERS"
|
||||
(0..(N-1)).each { |it|
|
||||
@ -94,6 +94,6 @@ my @logs = do {
|
||||
};
|
||||
debug(join "\n", map { join " ", @$_ } @logs);
|
||||
|
||||
is scalar(grep { $_->[0] == 200 } @logs), 1000, "accepted all requests";
|
||||
is scalar(grep { $_->[0] == 200 } @logs), 900, "accepted all requests";
|
||||
|
||||
done_testing();
|
||||
|
2
t/40http3-iouring-batch1.t
Normal file
2
t/40http3-iouring-batch1.t
Normal file
@ -0,0 +1,2 @@
|
||||
exec $^X, "t/40http3/test.pl", "--batch-size=1";
|
||||
die "failed to invoke $^X t/40http3/test.pl:$!";
|
2
t/40http3-iouring-batch10.t
Normal file
2
t/40http3-iouring-batch10.t
Normal file
@ -0,0 +1,2 @@
|
||||
exec $^X, "t/40http3/test.pl", "--batch-size=10";
|
||||
die "failed to invoke $^X t/40http3/test.pl:$!";
|
204
t/40http3.t
204
t/40http3.t
@ -1,202 +1,2 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use Digest::MD5 qw(md5_hex);
|
||||
use Net::EmptyPort qw(wait_port);
|
||||
use File::Temp qw(tempdir);
|
||||
use Test::More;
|
||||
use t::Util;
|
||||
|
||||
my $tempdir = tempdir(CLEANUP => 1);
|
||||
|
||||
my $client_prog = bindir() . "/h2o-httpclient";
|
||||
plan skip_all => "$client_prog not found"
|
||||
unless -e $client_prog;
|
||||
|
||||
my $quic_port = empty_port({
|
||||
host => "127.0.0.1",
|
||||
proto => "udp",
|
||||
});
|
||||
|
||||
|
||||
sub doit {
|
||||
my $num_threads = shift;
|
||||
my $conf = << "EOT";
|
||||
listen:
|
||||
type: quic
|
||||
port: $quic_port
|
||||
ssl:
|
||||
key-file: examples/h2o/server.key
|
||||
certificate-file: examples/h2o/server.crt
|
||||
num-threads: $num_threads
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
/:
|
||||
file.dir: t/assets/doc_root
|
||||
EOT
|
||||
if (server_features()->{mruby}) {
|
||||
$conf .= << 'EOT';
|
||||
/echo:
|
||||
mruby.handler: |
|
||||
Proc.new do |env|
|
||||
[200, {}, [env["rack.input"].read]]
|
||||
end
|
||||
EOT
|
||||
}
|
||||
my $guard = spawn_h2o($conf);
|
||||
wait_port({port => $quic_port, proto => 'udp'});
|
||||
for (1..100) {
|
||||
subtest "hello world" => sub {
|
||||
my $resp = `$client_prog -3 100 https://127.0.0.1:$quic_port 2>&1`;
|
||||
like $resp, qr{^HTTP/.*\n\nhello\n$}s;
|
||||
};
|
||||
subtest "large file" => sub {
|
||||
my $resp = `$client_prog -3 100 https://127.0.0.1:$quic_port/halfdome.jpg 2> $tempdir/log`;
|
||||
is $?, 0;
|
||||
diag do {
|
||||
open my $fh, "-|", "share/h2o/annotate-backtrace-symbols < $tempdir/log"
|
||||
or die "failed to open $tempdir/log through annotated-backtrace-symbols:$?";
|
||||
local $/;
|
||||
<$fh>;
|
||||
} if $? != 0;
|
||||
is length($resp), (stat "t/assets/doc_root/halfdome.jpg")[7];
|
||||
is md5_hex($resp), md5_file("t/assets/doc_root/halfdome.jpg");
|
||||
};
|
||||
subtest "more than stream-concurrency" => sub {
|
||||
my $resp = `$client_prog -3 100 -t 1000 https://127.0.0.1:$quic_port 2> /dev/null`;
|
||||
is $resp, "hello\n" x 1000;
|
||||
};
|
||||
subtest "post" => sub {
|
||||
plan skip_all => 'mruby support is off'
|
||||
unless server_features()->{mruby};
|
||||
foreach my $cl (1, 100, 10000, 1000000) {
|
||||
my $resp = `$client_prog -3 100 -b $cl -c 100000 https://127.0.0.1:$quic_port/echo 2> /dev/null`;
|
||||
is length($resp), $cl;
|
||||
ok +($resp =~ /^a+$/s); # don't use of `like` to avoid excess amount of log lines on mismatch
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
subtest "single-thread" => sub {
|
||||
doit(1);
|
||||
};
|
||||
|
||||
subtest "multi-thread" => sub {
|
||||
doit(16);
|
||||
};
|
||||
|
||||
subtest "slow-echo-chunked" => sub {
|
||||
plan skip_all => 'mruby support is off'
|
||||
unless server_features()->{mruby};
|
||||
|
||||
my $guard = spawn_h2o(<< "EOT");
|
||||
listen:
|
||||
type: quic
|
||||
port: $quic_port
|
||||
ssl:
|
||||
key-file: examples/h2o/server.key
|
||||
certificate-file: examples/h2o/server.crt
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
/echo:
|
||||
mruby.handler: |
|
||||
Proc.new do |env|
|
||||
[200, {}, env["rack.input"]]
|
||||
end
|
||||
EOT
|
||||
|
||||
wait_port({port => $quic_port, proto => 'udp'});
|
||||
|
||||
my $resp = `$client_prog -3 100 -t 5 -d 1000 -b 10 -c 2 -i 1000 https://127.0.0.1:$quic_port/echo 2> /dev/null`;
|
||||
is length($resp), 50;
|
||||
is $resp, 'a' x 50;
|
||||
};
|
||||
|
||||
subtest "body-then-close" => sub {
|
||||
my $upstream = spawn_server(
|
||||
argv => [
|
||||
qw(plackup -s Starlet --max-workers 10 --access-log /dev/null --listen), "$tempdir/upstream.sock",
|
||||
ASSETS_DIR . "/upstream.psgi",
|
||||
],
|
||||
is_ready => sub { !! -e "$tempdir/upstream.sock" },
|
||||
);
|
||||
my $server = spawn_h2o(<< "EOT");
|
||||
http3-idle-timeout: 5
|
||||
listen:
|
||||
type: quic
|
||||
port: $quic_port
|
||||
ssl:
|
||||
key-file: examples/h2o/server.key
|
||||
certificate-file: examples/h2o/server.crt
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
/:
|
||||
proxy.reverse.url: http://[unix:$tempdir/upstream.sock]/
|
||||
EOT
|
||||
my $fetch = sub {
|
||||
my $qp = shift;
|
||||
open my $fh, "-|", "$client_prog -3 100 https://127.0.0.1:$quic_port/suspend-body$qp 2>&1"
|
||||
or die "failed to spawn $client_prog:$!";
|
||||
local $/;
|
||||
join "", <$fh>;
|
||||
};
|
||||
like $fetch->(""), qr{^HTTP/3 200\n.*\n\nx$}s;
|
||||
like $fetch->("?delay-fin"), qr{^HTTP/3 200\n.*\n\nx$}s;
|
||||
};
|
||||
|
||||
subtest "large-headers" => sub {
|
||||
my $server = spawn_h2o(<< "EOT");
|
||||
listen:
|
||||
type: quic
|
||||
port: $quic_port
|
||||
ssl:
|
||||
key-file: examples/h2o/server.key
|
||||
certificate-file: examples/h2o/server.crt
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
/:
|
||||
file.dir: t/assets/doc_root
|
||||
EOT
|
||||
|
||||
my $fetch = sub {
|
||||
my ($query, $opts) = @_;
|
||||
open my $fh, "-|", "$client_prog -3 100 $opts https://127.0.0.1:$quic_port/$query 2>&1"
|
||||
or die "failed to spawn $client_prog:$!";
|
||||
local $/;
|
||||
join "", <$fh>;
|
||||
};
|
||||
|
||||
like $fetch->("", ""), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "no headers";
|
||||
|
||||
# When generating headers, 'X' is used, as it is 8 bits in cleartext and also in static huffman.
|
||||
# TODO: can we check that the error is stream-level?
|
||||
subtest "single header" => sub {
|
||||
plan skip_all => "linux cannot handle args longer than 128KB"
|
||||
if $^O eq 'linux';
|
||||
like $fetch->("", "-H a:" . "X" x 409600), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly below limit";
|
||||
unlike $fetch->("", "-H a:" . "X" x 512000), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly above limit";
|
||||
};
|
||||
subtest "some large headers" => sub {
|
||||
like $fetch->("", join " ", map { "-H a:" . "X" x 65536 } (0..5)), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly below limit";
|
||||
unlike $fetch->("", join " ", map { "-H a:" . "X" x 65536 } (0..7)), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly above limit";
|
||||
};
|
||||
subtest "many headers" => sub {
|
||||
like $fetch->("", join " ", map { "-H a:" . "X" x 4096 } (0..90)), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly below limit";
|
||||
unlike $fetch->("", join " ", map { "-H a:" . "X" x 4096 } (0..110)), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly above limit";
|
||||
};
|
||||
subtest "URI" => sub {
|
||||
like $fetch->("?q=" . "X" x 120000, ""), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly below limit";
|
||||
subtest "above linux limit" => sub {
|
||||
plan skip_all => "linux cannot handle args longer than 128KB"
|
||||
if $^O eq 'linux';
|
||||
like $fetch->("?q=" . "X" x 409600, ""), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly below limit";
|
||||
unlike $fetch->("?q=" . "X" x 512000, ""), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly below limit";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
done_testing;
|
||||
exec $^X, "t/40http3/test.pl";
|
||||
die "failed to invoke $^X t/40http3/test.pl:$!";
|
||||
|
221
t/40http3/test.pl
Normal file
221
t/40http3/test.pl
Normal file
@ -0,0 +1,221 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use Digest::MD5 qw(md5_hex);
|
||||
use Getopt::Long;
|
||||
use Net::EmptyPort qw(wait_port);
|
||||
use File::Temp qw(tempdir);
|
||||
use Test::More;
|
||||
use t::Util;
|
||||
|
||||
my $io_uring_batch_size;
|
||||
|
||||
GetOptions(
|
||||
'batch-size=i' => \$io_uring_batch_size
|
||||
) or exit(1);
|
||||
|
||||
my $tempdir = tempdir(CLEANUP => 1);
|
||||
|
||||
my $client_prog = bindir() . "/h2o-httpclient";
|
||||
plan skip_all => "$client_prog not found"
|
||||
unless -e $client_prog;
|
||||
|
||||
plan skip_all => "io_uring is not available"
|
||||
if $io_uring_batch_size && !server_features()->{io_uring};
|
||||
|
||||
my $quic_port = empty_port({
|
||||
host => "127.0.0.1",
|
||||
proto => "udp",
|
||||
});
|
||||
|
||||
sub doit {
|
||||
my $num_threads = shift;
|
||||
my $conf = << "EOT";
|
||||
listen:
|
||||
type: quic
|
||||
port: $quic_port
|
||||
ssl:
|
||||
key-file: examples/h2o/server.key
|
||||
certificate-file: examples/h2o/server.crt
|
||||
num-threads: $num_threads
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
/:
|
||||
file.dir: t/assets/doc_root
|
||||
EOT
|
||||
if (server_features()->{mruby}) {
|
||||
$conf .= << 'EOT';
|
||||
/echo:
|
||||
mruby.handler: |
|
||||
Proc.new do |env|
|
||||
[200, {}, [env["rack.input"].read]]
|
||||
end
|
||||
EOT
|
||||
}
|
||||
if ($io_uring_batch_size) {
|
||||
$conf .= << "EOT";
|
||||
file.io_uring: ON
|
||||
io_uring-batch-size: $io_uring_batch_size
|
||||
EOT
|
||||
} else {
|
||||
$conf .= << "EOT";
|
||||
file.io_uring: OFF
|
||||
EOT
|
||||
}
|
||||
my $guard = spawn_h2o($conf);
|
||||
wait_port({port => $quic_port, proto => 'udp'});
|
||||
for (1..100) {
|
||||
subtest "hello world" => sub {
|
||||
my $resp = `$client_prog -3 100 https://127.0.0.1:$quic_port 2>&1`;
|
||||
like $resp, qr{^HTTP/.*\n\nhello\n$}s;
|
||||
};
|
||||
subtest "large file" => sub {
|
||||
my $resp = `$client_prog -3 100 https://127.0.0.1:$quic_port/halfdome.jpg 2> $tempdir/log`;
|
||||
is $?, 0;
|
||||
diag do {
|
||||
open my $fh, "-|", "share/h2o/annotate-backtrace-symbols < $tempdir/log"
|
||||
or die "failed to open $tempdir/log through annotated-backtrace-symbols:$?";
|
||||
local $/;
|
||||
<$fh>;
|
||||
} if $? != 0;
|
||||
is length($resp), (stat "t/assets/doc_root/halfdome.jpg")[7];
|
||||
is md5_hex($resp), md5_file("t/assets/doc_root/halfdome.jpg");
|
||||
};
|
||||
subtest "more than stream-concurrency" => sub {
|
||||
my $resp = `$client_prog -3 100 -t 1000 https://127.0.0.1:$quic_port 2> /dev/null`;
|
||||
is $resp, "hello\n" x 1000;
|
||||
};
|
||||
subtest "post" => sub {
|
||||
plan skip_all => 'mruby support is off'
|
||||
unless server_features()->{mruby};
|
||||
foreach my $cl (1, 100, 10000, 1000000) {
|
||||
my $resp = `$client_prog -3 100 -b $cl -c 100000 https://127.0.0.1:$quic_port/echo 2> /dev/null`;
|
||||
is length($resp), $cl;
|
||||
ok +($resp =~ /^a+$/s); # don't use of `like` to avoid excess amount of log lines on mismatch
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
subtest "single-thread" => sub {
|
||||
doit(1);
|
||||
};
|
||||
|
||||
subtest "multi-thread" => sub {
|
||||
doit(16);
|
||||
};
|
||||
|
||||
subtest "slow-echo-chunked" => sub {
|
||||
plan skip_all => 'mruby support is off'
|
||||
unless server_features()->{mruby};
|
||||
|
||||
my $guard = spawn_h2o(<< "EOT");
|
||||
listen:
|
||||
type: quic
|
||||
port: $quic_port
|
||||
ssl:
|
||||
key-file: examples/h2o/server.key
|
||||
certificate-file: examples/h2o/server.crt
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
/echo:
|
||||
mruby.handler: |
|
||||
Proc.new do |env|
|
||||
[200, {}, env["rack.input"]]
|
||||
end
|
||||
EOT
|
||||
|
||||
wait_port({port => $quic_port, proto => 'udp'});
|
||||
|
||||
my $resp = `$client_prog -3 100 -t 5 -d 1000 -b 10 -c 2 -i 1000 https://127.0.0.1:$quic_port/echo 2> /dev/null`;
|
||||
is length($resp), 50;
|
||||
is $resp, 'a' x 50;
|
||||
};
|
||||
|
||||
subtest "body-then-close" => sub {
|
||||
my $upstream = spawn_server(
|
||||
argv => [
|
||||
qw(plackup -s Starlet --max-workers 10 --access-log /dev/null --listen), "$tempdir/upstream.sock",
|
||||
ASSETS_DIR . "/upstream.psgi",
|
||||
],
|
||||
is_ready => sub { !! -e "$tempdir/upstream.sock" },
|
||||
);
|
||||
my $server = spawn_h2o(<< "EOT");
|
||||
http3-idle-timeout: 5
|
||||
listen:
|
||||
type: quic
|
||||
port: $quic_port
|
||||
ssl:
|
||||
key-file: examples/h2o/server.key
|
||||
certificate-file: examples/h2o/server.crt
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
/:
|
||||
proxy.reverse.url: http://[unix:$tempdir/upstream.sock]/
|
||||
EOT
|
||||
my $fetch = sub {
|
||||
my $qp = shift;
|
||||
open my $fh, "-|", "$client_prog -3 100 https://127.0.0.1:$quic_port/suspend-body$qp 2>&1"
|
||||
or die "failed to spawn $client_prog:$!";
|
||||
local $/;
|
||||
join "", <$fh>;
|
||||
};
|
||||
like $fetch->(""), qr{^HTTP/3 200\n.*\n\nx$}s;
|
||||
like $fetch->("?delay-fin"), qr{^HTTP/3 200\n.*\n\nx$}s;
|
||||
};
|
||||
|
||||
subtest "large-headers" => sub {
|
||||
my $server = spawn_h2o(<< "EOT");
|
||||
listen:
|
||||
type: quic
|
||||
port: $quic_port
|
||||
ssl:
|
||||
key-file: examples/h2o/server.key
|
||||
certificate-file: examples/h2o/server.crt
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
/:
|
||||
file.dir: t/assets/doc_root
|
||||
EOT
|
||||
|
||||
my $fetch = sub {
|
||||
my ($query, $opts) = @_;
|
||||
open my $fh, "-|", "$client_prog -3 100 $opts https://127.0.0.1:$quic_port/$query 2>&1"
|
||||
or die "failed to spawn $client_prog:$!";
|
||||
local $/;
|
||||
join "", <$fh>;
|
||||
};
|
||||
|
||||
like $fetch->("", ""), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "no headers";
|
||||
|
||||
# When generating headers, 'X' is used, as it is 8 bits in cleartext and also in static huffman.
|
||||
# TODO: can we check that the error is stream-level?
|
||||
subtest "single header" => sub {
|
||||
plan skip_all => "linux cannot handle args longer than 128KB"
|
||||
if $^O eq 'linux';
|
||||
like $fetch->("", "-H a:" . "X" x 409600), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly below limit";
|
||||
unlike $fetch->("", "-H a:" . "X" x 512000), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly above limit";
|
||||
};
|
||||
subtest "some large headers" => sub {
|
||||
like $fetch->("", join " ", map { "-H a:" . "X" x 65536 } (0..5)), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly below limit";
|
||||
unlike $fetch->("", join " ", map { "-H a:" . "X" x 65536 } (0..7)), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly above limit";
|
||||
};
|
||||
subtest "many headers" => sub {
|
||||
like $fetch->("", join " ", map { "-H a:" . "X" x 4096 } (0..90)), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly below limit";
|
||||
unlike $fetch->("", join " ", map { "-H a:" . "X" x 4096 } (0..110)), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly above limit";
|
||||
};
|
||||
subtest "URI" => sub {
|
||||
like $fetch->("?q=" . "X" x 120000, ""), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly below limit";
|
||||
subtest "above linux limit" => sub {
|
||||
plan skip_all => "linux cannot handle args longer than 128KB"
|
||||
if $^O eq 'linux';
|
||||
like $fetch->("?q=" . "X" x 409600, ""), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly below limit";
|
||||
unlike $fetch->("?q=" . "X" x 512000, ""), qr{^HTTP/3 200\n.*\n\nhello\n$}s, "slightly below limit";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
done_testing;
|
122
t/40protocol.t
122
t/40protocol.t
@ -9,63 +9,93 @@ my %files = map { do {
|
||||
+($_ => { size => +(stat $fn)[7], md5 => md5_file($fn) });
|
||||
} } qw(index.txt halfdome.jpg);
|
||||
|
||||
my $server = spawn_h2o(<< "EOT");
|
||||
sub run_tests {
|
||||
my $extra_conf = shift;
|
||||
|
||||
my $server = spawn_h2o(<< "EOT");
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
/:
|
||||
file.dir: @{[ DOC_ROOT ]}
|
||||
$extra_conf
|
||||
EOT
|
||||
|
||||
my $port = $server->{port};
|
||||
my $tls_port = $server->{tls_port};
|
||||
my $port = $server->{port};
|
||||
my $tls_port = $server->{tls_port};
|
||||
|
||||
subtest 'curl' => sub {
|
||||
run_with_curl($server, sub {
|
||||
my ($proto, $port, $curl) = @_;
|
||||
for my $file (sort keys %files) {
|
||||
my $content = `$curl --silent --show-error $proto://127.0.0.1:$port/$file`;
|
||||
is length($content), $files{$file}->{size}, "$file (size)";
|
||||
is md5_hex($content), $files{$file}->{md5}, "$file (md5)";
|
||||
}
|
||||
});
|
||||
subtest 'curl' => sub {
|
||||
run_with_curl($server, sub {
|
||||
my ($proto, $port, $curl) = @_;
|
||||
for my $file (sort keys %files) {
|
||||
my $content = `$curl --silent --show-error $proto://127.0.0.1:$port/$file`;
|
||||
is length($content), $files{$file}->{size}, "$file (size)";
|
||||
is md5_hex($content), $files{$file}->{md5}, "$file (md5)";
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
subtest 'nghttp' => sub {
|
||||
plan skip_all => 'nghttp not found'
|
||||
unless prog_exists('nghttp');
|
||||
my $doit = sub {
|
||||
my ($proto, $port) = @_;
|
||||
my $opt = $proto eq 'http' ? '-u' : '';
|
||||
for my $file (sort keys %files) {
|
||||
my $content = `nghttp $opt $proto://127.0.0.1:$port/$file`;
|
||||
is length($content), $files{$file}->{size}, "$proto://127.0.0.1/$file (size)";
|
||||
is md5_hex($content), $files{$file}->{md5}, "$proto://127.0.0.1/$file (md5)";
|
||||
}
|
||||
my $out = `nghttp -u -m 100 $proto://127.0.0.1:$port/index.txt`;
|
||||
is $out, "hello\n" x 100, "$proto://127.0.0.1/index.txt x 100 times";
|
||||
};
|
||||
$doit->('http', $port);
|
||||
subtest 'https' => sub {
|
||||
plan skip_all => 'OpenSSL does not support protocol negotiation; it is too old'
|
||||
unless openssl_can_negotiate();
|
||||
$doit->('https', $tls_port);
|
||||
};
|
||||
subtest 'dtsu' => sub {
|
||||
plan skip_all => 'OpenSSL does not support protocol negotiation; it is too old'
|
||||
unless openssl_can_negotiate();
|
||||
for my $table_size (0, 1024) {
|
||||
my $content = `nghttp --header-table-size=$table_size https://127.0.0.1:$tls_port/index.txt`;
|
||||
is $content, "hello\n";
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
subtest 'ab' => sub {
|
||||
plan skip_all => 'ab not found'
|
||||
unless prog_exists('ab');
|
||||
ok(system("ab -c 10 -n 10000 -k http://127.0.0.1:$port/index.txt") == 0);
|
||||
ok(system("ab -f tls1 -c 10 -n 10000 -k https://127.0.0.1:$tls_port/index.txt") == 0);
|
||||
};
|
||||
}
|
||||
|
||||
subtest 'default' => sub {
|
||||
run_tests(<< "EOT");
|
||||
file.io_uring: OFF
|
||||
EOT
|
||||
};
|
||||
|
||||
subtest 'nghttp' => sub {
|
||||
plan skip_all => 'nghttp not found'
|
||||
unless prog_exists('nghttp');
|
||||
my $doit = sub {
|
||||
my ($proto, $port) = @_;
|
||||
my $opt = $proto eq 'http' ? '-u' : '';
|
||||
for my $file (sort keys %files) {
|
||||
my $content = `nghttp $opt $proto://127.0.0.1:$port/$file`;
|
||||
is length($content), $files{$file}->{size}, "$proto://127.0.0.1/$file (size)";
|
||||
is md5_hex($content), $files{$file}->{md5}, "$proto://127.0.0.1/$file (md5)";
|
||||
subtest 'io_uring' => sub {
|
||||
plan skip_all => 'io_uring is not availble'
|
||||
unless server_features()->{io_uring};
|
||||
subtest 'file.io_uring=off' => sub {
|
||||
run_tests('file.io_uring: OFF');
|
||||
};
|
||||
for my $batch_size (qw(1 10)) {
|
||||
for my $spare_pipes (qw(0 10)) {
|
||||
subtest "(batch_size,spare_pipes)=($batch_size,$spare_pipes)" => sub {
|
||||
run_tests(join "\n", <<"EOT");
|
||||
file.io_uring: ON
|
||||
io_uring-batch-size: $batch_size
|
||||
max-spare-pipes: $spare_pipes
|
||||
EOT
|
||||
};
|
||||
}
|
||||
my $out = `nghttp -u -m 100 $proto://127.0.0.1:$port/index.txt`;
|
||||
is $out, "hello\n" x 100, "$proto://127.0.0.1/index.txt x 100 times";
|
||||
};
|
||||
$doit->('http', $port);
|
||||
subtest 'https' => sub {
|
||||
plan skip_all => 'OpenSSL does not support protocol negotiation; it is too old'
|
||||
unless openssl_can_negotiate();
|
||||
$doit->('https', $tls_port);
|
||||
};
|
||||
subtest 'dtsu' => sub {
|
||||
plan skip_all => 'OpenSSL does not support protocol negotiation; it is too old'
|
||||
unless openssl_can_negotiate();
|
||||
for my $table_size (0, 1024) {
|
||||
my $content = `nghttp --header-table-size=$table_size https://127.0.0.1:$tls_port/index.txt`;
|
||||
is $content, "hello\n";
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
subtest 'ab' => sub {
|
||||
plan skip_all => 'ab not found'
|
||||
unless prog_exists('ab');
|
||||
ok(system("ab -c 10 -n 10000 -k http://127.0.0.1:$port/index.txt") == 0);
|
||||
ok(system("ab -f tls1 -c 10 -n 10000 -k https://127.0.0.1:$tls_port/index.txt") == 0);
|
||||
}
|
||||
};
|
||||
|
||||
done_testing;
|
||||
|
@ -26,6 +26,7 @@ subtest "basic" => sub {
|
||||
);
|
||||
# spawn server
|
||||
my $server = spawn_h2o(<< "EOT");
|
||||
file.io_uring: OFF
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
@ -228,6 +229,7 @@ subtest "cache-digest" => sub {
|
||||
my $server = spawn_h2o(sub {
|
||||
my ($port, $tls_port) = @_;
|
||||
return << "EOT";
|
||||
file.io_uring: OFF
|
||||
hosts:
|
||||
"127.0.0.1:$tls_port":
|
||||
paths:
|
||||
|
@ -13,6 +13,7 @@ plan skip_all => 'php-cgi not found'
|
||||
|
||||
# spawn h2o
|
||||
my $server = spawn_h2o(<< "EOT");
|
||||
file.io_uring: OFF
|
||||
file.custom-handler:
|
||||
extension: .php
|
||||
fastcgi.spawn: "exec php-cgi"
|
||||
|
@ -30,6 +30,7 @@ hosts:
|
||||
file.dir: $tempdir
|
||||
header.add: "X-Traffic: 200000" # takes about 5 seconds
|
||||
throttle-response: ON
|
||||
file.io_uring: OFF
|
||||
EOT
|
||||
wait_port({port => $quic_port, proto => "udp"});
|
||||
|
||||
|
@ -6,7 +6,7 @@ use t::Util;
|
||||
|
||||
# commands that are meant to exist without any documentation are listed here
|
||||
my @private_commands = (
|
||||
qw(mruby.handler_path proxy.connect.proxy-status proxy.http2.max-concurrent_streams proxy-status.identity), # deprecated
|
||||
qw(mruby.handler_path proxy.connect.proxy-status proxy.http2.max-concurrent_streams proxy.max-spare-pipes proxy-status.identity), # deprecated
|
||||
qw(header.cookie.unset header.cookie.unsetunless), # removing "cookie" headers in response?
|
||||
qw(http3-ack-frequency http3-allow-delayed-ack quic-nodes self-trace), # highly experimental and therefore undocumented
|
||||
);
|
||||
|
74
t/80issues3479.t
Normal file
74
t/80issues3479.t
Normal file
@ -0,0 +1,74 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use BSD::Resource;
|
||||
use File::Temp qw(tempdir);
|
||||
use Path::Tiny;
|
||||
use Test::More;
|
||||
use t::Util;
|
||||
|
||||
plan skip_all => 'io_uring support is off'
|
||||
unless server_features()->{io_uring};
|
||||
plan skip_all => 'h2load not found'
|
||||
unless prog_exists('h2load');
|
||||
|
||||
my $tempdir = tempdir(CLEANUP => 1);
|
||||
|
||||
# upstream is spawned before setrlimit(2)
|
||||
my $upstream = spawn_h2o(<< "EOT");
|
||||
num-threads: 1
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
/:
|
||||
file.dir: t/assets/doc_root
|
||||
EOT
|
||||
|
||||
# allow each process open only as many as 30 files
|
||||
setrlimit(RLIMIT_NOFILE, 30, 30);
|
||||
|
||||
subtest "file" => sub {
|
||||
doit(<< "EOT");
|
||||
file.io_uring: ON
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
/:
|
||||
file.dir: t/assets/doc_root
|
||||
EOT
|
||||
};
|
||||
|
||||
subtest "proxy" => sub {
|
||||
doit(<< "EOT");
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
"/":
|
||||
proxy.reverse.url: http://127.0.0.1:@{[$upstream->{port}]}/
|
||||
EOT
|
||||
};
|
||||
|
||||
undef $upstream;
|
||||
|
||||
done_testing();
|
||||
|
||||
sub doit {
|
||||
my $conf = shift;
|
||||
my $server = spawn_h2o(<< "EOT");
|
||||
num-threads: 1
|
||||
max-connections: 20
|
||||
error-log: $tempdir/error.log
|
||||
$conf
|
||||
EOT
|
||||
|
||||
# Check we get some 200s (we might get 503s due to not being able to open the file rather than pipe), and that we see some
|
||||
# pipe errors. The test is run twice to make sure that the server did not crash in the first iteration.
|
||||
for (1..2) {
|
||||
subtest "run $_" => sub {
|
||||
my $bench_out = `h2load -n 400 -c 20 -t 1 http://127.0.0.1:@{[$server->{port}]}/halfdome.jpg`;
|
||||
diag $bench_out;
|
||||
like $bench_out, qr/status codes: [1-9][0-9]* 2xx,/, "some 200s";
|
||||
like path("$tempdir/error.log")->slurp,
|
||||
qr{failed to allocate a pipe for async I/O; falling back to blocking I/O}, "some pipe errors";
|
||||
};
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ my $upstream = spawn_server(
|
||||
|
||||
# spawn server
|
||||
my $server = spawn_h2o(<< "EOT");
|
||||
file.io_uring: OFF
|
||||
hosts:
|
||||
default:
|
||||
paths:
|
||||
|
@ -84,7 +84,7 @@ subtest "h2olog -S=0.00", sub {
|
||||
diag "h2olog output:\n", $trace;
|
||||
}
|
||||
|
||||
is $trace, "", "nothing has been logged";
|
||||
unlike $trace, qr{^(?!.+"type":"async-io-).+}m, "nothing logged other than async-io-";
|
||||
};
|
||||
|
||||
subtest "QUIC", sub {
|
||||
|
Loading…
x
Reference in New Issue
Block a user