Merge branch 'fix/w4-compress' into fix/w4-integration
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include "utils.h"
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <threads.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void test_data_compress_decompress_roundtrip() {
|
||||
@@ -137,10 +138,84 @@ static void test_chunk_compress_decompress_roundtrip() {
|
||||
unlink(path2);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int id;
|
||||
int iterations;
|
||||
bool ok;
|
||||
} CompressionThreadArg;
|
||||
|
||||
/* Each worker exercises the per-thread cached zstd contexts: several
|
||||
* compress/decompress round-trips with varying payload sizes, levels and
|
||||
* worker counts so the context is reused (and its parameters re-applied)
|
||||
* across calls, concurrently with other workers. */
|
||||
static int compression_reuse_worker(void* arg) {
|
||||
CompressionThreadArg* a = (CompressionThreadArg*)arg;
|
||||
a->ok = true;
|
||||
for (int it = 0; it < a->iterations; it++) {
|
||||
size_t size = 512 + (size_t)((a->id * 7919 + it * 104729) % (48 * 1024));
|
||||
char* original = malloc(size);
|
||||
if (!original) {
|
||||
a->ok = false;
|
||||
break;
|
||||
}
|
||||
for (size_t i = 0; i < size; i++)
|
||||
original[i] = (char)((i * 31 + (size_t)a->id + (size_t)it * 7) % 251);
|
||||
Data* input = data_create(original, size);
|
||||
if (!input) { /* data_create takes ownership of original, even on failure */
|
||||
a->ok = false;
|
||||
break;
|
||||
}
|
||||
int level = 1 + ((it / 2) % 5);
|
||||
int threads = ((it / 2) % 2 == 0) ? 2 : 0;
|
||||
Data* compressed = data_compress_with_threads(input, level, threads);
|
||||
if (!compressed) {
|
||||
data_destroy(input);
|
||||
a->ok = false;
|
||||
break;
|
||||
}
|
||||
Data* decompressed = data_decompress(compressed);
|
||||
bool roundtrip_ok = decompressed != NULL && decompressed->size == size &&
|
||||
memcmp(decompressed->data, original, size) == 0;
|
||||
data_destroy(decompressed);
|
||||
data_destroy(compressed);
|
||||
data_destroy(input);
|
||||
if (!roundtrip_ok) {
|
||||
a->ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Deliberately do NOT free the thread context here: the C11 tss destructor
|
||||
* must release it when this thread exits (validated by LeakSanitizer). */
|
||||
return thrd_success;
|
||||
}
|
||||
|
||||
static void test_data_compress_reused_contexts_multithreaded() {
|
||||
enum { NTHREADS = 8, ITERATIONS = 6 };
|
||||
thrd_t threads[NTHREADS];
|
||||
CompressionThreadArg args[NTHREADS];
|
||||
bool all_created = true;
|
||||
for (int i = 0; i < NTHREADS; i++) {
|
||||
args[i].id = i;
|
||||
args[i].iterations = ITERATIONS;
|
||||
args[i].ok = false;
|
||||
if (thrd_create(&threads[i], compression_reuse_worker, &args[i]) != thrd_success) {
|
||||
all_created = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(all_created);
|
||||
for (int i = 0; i < NTHREADS; i++)
|
||||
EXPECT_EQ_INT(thrd_join(threads[i], NULL), thrd_success);
|
||||
for (int i = 0; i < NTHREADS; i++)
|
||||
EXPECT_TRUE(args[i].ok);
|
||||
compression_free_thread_contexts();
|
||||
}
|
||||
|
||||
void test_compression() {
|
||||
test_data_compress_decompress_roundtrip();
|
||||
test_data_compress_decompress_large();
|
||||
test_skip_compress_suffix_matching();
|
||||
test_data_compress_with_threads_roundtrip();
|
||||
test_data_compress_reused_contexts_multithreaded();
|
||||
test_chunk_compress_decompress_roundtrip();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "protocol.h"
|
||||
#include "test_utils.h"
|
||||
#include "transport_tcp.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <string.h>
|
||||
@@ -195,6 +196,49 @@ static void test_client_disconnect_delete() {
|
||||
client_delete(c);
|
||||
}
|
||||
|
||||
/* TCP_NODELAY is enabled by default on a connected transfer socket, and an
|
||||
* explicit --sockopts TCP_NODELAY=0 still overrides it. */
|
||||
static void test_tcp_nodelay_default_and_override() {
|
||||
Server* s = server_create(0);
|
||||
EXPECT_NOT_NULL(s);
|
||||
EXPECT_EQ_INT(listen(s->file_descriptor, 1), 0);
|
||||
struct sockaddr_in bound;
|
||||
socklen_t bound_len = sizeof(bound);
|
||||
EXPECT_EQ_INT(getsockname(s->file_descriptor, (struct sockaddr*)&bound, &bound_len), 0);
|
||||
int port = (int)ntohs(bound.sin_port);
|
||||
EXPECT_TRUE(port > 0);
|
||||
|
||||
Client* c = client_create();
|
||||
EXPECT_NOT_NULL(c);
|
||||
EXPECT_TRUE(client_connect(c, "127.0.0.1", port));
|
||||
int got = 0;
|
||||
socklen_t len = sizeof(got);
|
||||
EXPECT_EQ_INT(getsockopt(c->file_descriptor, IPPROTO_TCP, TCP_NODELAY, &got, &len), 0);
|
||||
EXPECT_EQ_INT(got, 1);
|
||||
client_disconnect(c);
|
||||
client_delete(c);
|
||||
|
||||
SockOptEntry* entries = NULL;
|
||||
int count = 0;
|
||||
EXPECT_EQ_INT(config_sockopts_parse("TCP_NODELAY=0", &entries, &count), 0);
|
||||
TcpConnectOptions opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
opts.sockopts = entries;
|
||||
opts.sockopt_count = count;
|
||||
|
||||
Client* c2 = client_create();
|
||||
EXPECT_NOT_NULL(c2);
|
||||
EXPECT_TRUE(client_connect_ex(c2, "127.0.0.1", port, &opts));
|
||||
got = 0;
|
||||
len = sizeof(got);
|
||||
EXPECT_EQ_INT(getsockopt(c2->file_descriptor, IPPROTO_TCP, TCP_NODELAY, &got, &len), 0);
|
||||
EXPECT_EQ_INT(got, 0);
|
||||
client_disconnect(c2);
|
||||
client_delete(c2);
|
||||
free(entries);
|
||||
server_delete(&s);
|
||||
}
|
||||
|
||||
void test_transport_tcp() {
|
||||
test_server_create_ephemeral();
|
||||
test_server_delete_null();
|
||||
@@ -211,4 +255,5 @@ void test_transport_tcp() {
|
||||
test_sockopts_apply_sets_option();
|
||||
test_server_create_bind_address();
|
||||
test_server_create_bind_ipv6();
|
||||
test_tcp_nodelay_default_and_override();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user