chunkqueue_peek_data() experiment to mmap temporary files
(currently disabled in code due to not making measurable difference
in throughput (during a specific microbenchmark load test),
though it does reduce CPU use by ~10% in the same microbenchmark)
enabling this may cause large spikes in RSS mem usage reported by the
system, due to the read-only memory maps of the temporary files,
but this is nothing to be alarmed about, as the memory maps are
file-backed and read-only, so minimally add to memory pressure
splice() data from backends to tempfiles (where splice() is available);
reduce copying data to userspace when writing data to tempfiles
Note: splice() on Linux returns EINVAL if target file has O_APPEND set
so lighttpd uses pwrite() (where available) when writing to tempfiles
(instead of lseek() + write(), or O_APPEND and write())
remove redundant checks for tempfile chunk reuse
c->file.is_temp is only set if c->type == FILE_CHUNK is also true
The test for (0 == c->offset) is historical. Before the temporary files
were opened O_APPEND (or written to using pwrite()), the file offset may
have changed via lseek() if lighttpd had started reading the file to
send to the client. To avoid this, the (0 == c->offset) check was used
as a quick check to avoid continuing to write to a temporary file that
lighttpd had begun to read.
rename chunkqueue_get_append_tempfile()
-> chunkqueue_get_append_newtempfile()
pull some code from chunkqueue_append_mem_to_tempfile()
into smaller func for (new func) chunkqueue_get_append_tempfile(),
which might call into chunkqueue_get_append_newtempfile()
pull some code from chunkqueue_append_mem_to_tempfile()
into smaller func chunkqueue_append_tempfile_err()
to handle write errors with respect to removing empty chunk
and stepping to next configured tempdir
file names tend to be much shorter than chunk_buf_sz
so using separate pool saves memory for large request and
response bodies where many temporary files are collected
reduce oversized memory allocations when reading from backends:
avoid extra power-2 allocation for 1 byte ('\0') when data
available to read is exactly power-2
If chunkqueue size grows large enough in memory to use tempfiles,
write all MEM_CHUNK in chunkqueue to tempfiles to free up memory.
If earlier chunks in chunkqueue are MEM_CHUNK, then a prior attempt
to write was not able to proceed, or items are being added to
chunkqueue as they are decoded from TLS. In either case, choose to
free up memory sooner. This may result in some cases where lighttpd
soon reads data back from disk to send to the backend, but since some
(now all) of the data is in tempfiles, sendfile() will avoid reading
back into userspace, though sendfile() will not be used if lighttpd
needs to read data back into memory to encrypt data using TLS.
previously undocumented server.upload-temp-file-size in lighttpd 1.4.38
preceded introduction of lighttpd streaming options in lighttpd 1.4.40
(server.stream-request-body and server.stream-response-body)
This commit is a large set of code changes and results in removal of
hundreds, perhaps thousands, of CPU instructions, a portion of which
are on hot code paths.
Most (buffer *) used by lighttpd are not NULL, especially since buffers
were inlined into numerous larger structs such as request_st and chunk.
In the small number of instances where that is not the case, a NULL
check is often performed earlier in a function where that buffer is
later used with a buffer_* func. In the handful of cases that remained,
a NULL check was added, e.g. with r->http_host and r->conf.server_tag.
- check for empty strings at config time and set value to NULL if blank
string will be ignored at runtime; at runtime, simple pointer check
for NULL can be used to check for a value that has been set and is not
blank ("")
- use buffer_is_blank() instead of buffer_string_is_empty(),
and use buffer_is_unset() instead of buffer_is_empty(),
where buffer is known not to be NULL so that NULL check can be skipped
- use buffer_clen() instead of buffer_string_length() when buffer is
known not to be NULL (to avoid NULL check at runtime)
- use buffer_truncate() instead of buffer_string_set_length() to
truncate string, and use buffer_extend() to extend
Examples where buffer known not to be NULL:
- cpv->v.b from config_plugin_values_init is not NULL if T_CONFIG_BOOL
(though we might set it to NULL if buffer_is_blank(cpv->v.b))
- address of buffer is arg (&foo)
(compiler optimizer detects this in most, but not all, cases)
- buffer is checked for NULL earlier in func
- buffer is accessed in same scope without a NULL check (e.g. b->ptr)
internal behavior change:
callers must not pass a NULL buffer to some funcs.
- buffer_init_buffer() requires non-null args
- buffer_copy_buffer() requires non-null args
- buffer_append_string_buffer() requires non-null args
- buffer_string_space() requires non-null arg
create API in chunk.[ch] for writing a chunk to an fd
(pull similar code from mod_cgi and mod_webdav)
This new API is intended for use on request body input, which is
written to size-limited temporary files controlled by lighttpd and
written to files or pipes.
(network_backend_write() is for writing chunkqueues to sockets)
(thx flynn)
fix large memory usage for large file downloads from dynamic backends
reuse or release large memory chunks
x-ref:
"Memory Growth with PUT and full buffered streams"
https://redmine.lighttpd.net/issues/3033
(replace existing check which suffered from ToC-ToU race condition)
enhances logic from 2015 commit 593599f1 and avoids repeated fstat()
checks when sending large files
For mmap(), lighttpd catches SIGBUS if file is (externally) truncated
and lighttpd attempts to access bytes in a read-only mapping more than
a memory page boundary following the end of the file.
For sendfile(), lighttpd returns an error if sendfile() reports no error
and that no bytes have been sent after lighttpd attempts to send a
non-zero number of bytes.
defer optimization to read small files into memory until after
response_start hooks have a chance to run, e.g. until after
mod_deflate chooses whether or not to serve file from compressed
cache, if deflate.cache-dir is configured