![]() * Extend the integration test harness to track FDs Motivation This patch extends the NIO integration test harness to track file descriptors, in particular to search for leaks. This change has been validated on Linux and Darwin, and in both cases correctly diagnoses FD leaks. The goal is to enable us to regression test for things like Modifications - Add support for hooking socket and close calls. - Wire up this support into the test harness. - Extend the test harness to handle the logging. - Add new regression test for #2047. Results We can write regression tests for FD leaks. * Disable FD checking in most builds. I'm doing this for speed reasons * Always print the leaked fds number |
||
---|---|---|
.. | ||
README.md | ||
run-nio-alloc-counter-tests.sh | ||
shared.swift | ||
test_1_reqs_1000_conn.swift | ||
test_1000_addHandlers.swift | ||
test_1000_addHandlers_sync.swift | ||
test_1000_addRemoveHandlers.swift | ||
test_1000_autoReadGetAndSet.swift | ||
test_1000_autoReadGetAndSet_sync.swift | ||
test_1000_copying_bytebufferview_to_array.swift | ||
test_1000_copying_circularbuffer_to_array.swift | ||
test_1000_getHandlers.swift | ||
test_1000_getHandlers_sync.swift | ||
test_1000_reqs_1_conn.swift | ||
test_1000_rst_connections.swift | ||
test_1000_tcpbootstraps.swift | ||
test_1000_tcpconnections.swift | ||
test_1000_udp_reqs.swift | ||
test_1000_udpbootstraps.swift | ||
test_1000_udpconnections.swift | ||
test_1000000_asyncwriter.swift | ||
test_10000000_asyncsequenceproducer.swift | ||
test_bytebuffer_lots_of_rw.swift | ||
test_creating_10000_headers.swift | ||
test_decode_1000_ws_frames.swift | ||
test_encode_1000_ws_frames.swift | ||
test_execute_hop_10000_tasks.swift | ||
test_future_erase_result.swift | ||
test_future_lots_of_callbacks.swift | ||
test_get_100000_headers_canonical_form.swift | ||
test_modifying_1000_circular_buffer_elements.swift | ||
test_modifying_byte_buffer_view.swift | ||
test_ping_pong_1000_reqs_1_conn.swift | ||
test_read_10000_chunks_from_file.swift | ||
test_schedule_10000_tasks.swift | ||
test_schedule_and_run_10000_tasks.swift | ||
test_scheduling_10000_executions.swift | ||
test_udp_1_reqs_1000_conn.swift | ||
test_udp_1000_reqs_1_conn.swift |
README.md
Allocation Counting Test
This briefly describes how the allocation counting test works.
How does it work?
This is possibly the simplest implementation that counts memory allocations (malloc
and friends) and frees (mostly free
). It just maintains two atomic variables which count the number of mallocs and the number of frees respectively. We run a simple HTTP1 example -- 1000 requests and responses generated by a simple SwiftNIO based client and server -- and then evaluate the number of mallocs and frees. The difference mallocs - frees
should be pretty much 0 and the number of mallocs
should remain stable (or decrease) across commits. We can't establish a perfect baseline as the exact number of allocations depends on your operating system, libc and Swift version.
How are the functions hooked?
Usually in UNIX it's enough to just define a function, for example
void free(void *ptr) { ... }
in the main binary and all modules will use this free
function instead of the real one from the libc
. For Linux, this is exactly what we're doing, the bootstrap
binary defines such a free
function in its main.c
. On Darwin (macOS/iOS/...) however that is not the case and you need to use dyld's interpose feature. The odd thing is that dyld's interposing only works if it's in a .dylib
and not from a binary's main executable. Therefore we need to build a slightly strange SwiftPM package:
bootstrap
: The main executable's main module (written in C) so we can hook thefree
function on Linux.BootstrapSwift
: A SwiftPM module (written in Swift) called in frombootstrap
which implements the actual SwiftNIO benchmark (and therefore depends on theNIO
module).HookedFunctions
: A separate SwiftPM package that builds a shared library (.so
on Linux,.dylib
on Darwin) which contains thereplacement_malloc
,replacement_free
, etc functions which just increment an atomic integers representing the number of operations. On Darwin, we useDYLD_INTERPOSE
in this module, interposing libc functions with ourreplacement_
functions. This needs to be a separate SwiftPM package as otherwise its code would just live inside of thebootstrap
executable and the dyld interposing feature wouldn't work.AtomicCounter
: SwiftPM package (written in C) that implements the atomic counters. It needs to be a separate package as bothBoostrapSwift
(to read the allocation counter) as well asHookedFree
(to increment the allocation counter) depend on it.
What benchmark is run?
We run a single TCP connection over which 1000 HTTP requests are made by a client written in NIO, responded to by a server also written in NIO. We re-run the benchmark 10 times and return the lowest number of allocations that has been made.
Why do I have to set a baseline?
By default this test should always succeed as it doesn't actually compare the number of allocations to a certain number. The reason is that this number varies ever so slightly between operating systems and Swift versions. At the time of writing on macOS we got roughly 326k allocations and on Linux 322k allocations for 1000 HTTP requests & responses. To set a baseline simply run
export MAX_ALLOCS_ALLOWED_1000_reqs_1_conn=327000
or similar to set the maximum number of allocations allowed. If the benchmark exceeds these allocations the test will fail.