You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
5.7 KiB
134 lines
5.7 KiB
/* Реальный Android UDP-логгер: несколько producers, смена адреса, restart и |
|
* reentrant debug hook. Все операции с таймерами обязаны идти в owner thread. */ |
|
#include "../tools/chatgui-android/jni_bridge/android_udp_log.h" |
|
#include "../lib/u_async.h" |
|
#include "../lib/debug_config.h" |
|
#include "../lib/mem.h" |
|
#include <assert.h> |
|
#include <limits.h> |
|
#include <pthread.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <unistd.h> |
|
|
|
static pthread_t owner; |
|
static int fail_timer; |
|
static unsigned timer_sets, timer_cancels; |
|
|
|
void* __real_uasync_set_timeout(struct UASYNC*, int, void*, timeout_callback_t, const char*); |
|
err_t __real_uasync_cancel_timeout(struct UASYNC*, void*); |
|
|
|
void* __wrap_uasync_set_timeout(struct UASYNC* ua, int tb, void* arg, timeout_callback_t cb, const char* name) { |
|
assert(pthread_equal(owner, pthread_self())); |
|
timer_sets++; |
|
if (fail_timer) { |
|
fail_timer = 0; |
|
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "injected flush timer allocation failure"); |
|
return NULL; |
|
} |
|
return __real_uasync_set_timeout(ua, tb, arg, cb, name); |
|
} |
|
|
|
err_t __wrap_uasync_cancel_timeout(struct UASYNC* ua, void* timer) { |
|
assert(pthread_equal(owner, pthread_self())); |
|
timer_cancels++; |
|
return __real_uasync_cancel_timeout(ua, timer); |
|
} |
|
|
|
struct producer { unsigned id; int port; }; |
|
|
|
static void* produce(void* arg) { |
|
struct producer* p = arg; |
|
char line[32]; memset(line, 'a' + p->id, sizeof(line)); line[31] = '\n'; |
|
for (unsigned i = 0; i < 1000; i++) { |
|
udp_log_write(line, sizeof(line)); |
|
if (!p->id && i % 250 == 0) udp_log_set_target("127.0.0.1", p->port); |
|
if (i % 32 == 0) usleep(500); |
|
} |
|
return NULL; |
|
} |
|
|
|
static unsigned receive_lines(int fd) { |
|
char buf[65536]; |
|
unsigned count = 0; |
|
ssize_t len; |
|
while ((len = recv(fd, buf, sizeof(buf), MSG_DONTWAIT)) > 0) { |
|
assert(len % 32 == 0); |
|
for (ssize_t off = 0; off < len; off += 32) { |
|
assert(buf[off] >= 'a' && buf[off] <= 'd' && buf[off + 31] == '\n'); |
|
for (int i = 1; i < 31; i++) assert(buf[off + i] == buf[off]); |
|
count++; |
|
} |
|
} |
|
return count; |
|
} |
|
|
|
static void forward_log(int level, const char* category, const char* message) { |
|
(void)category; |
|
udp_log_write(message, (int)strlen(message)); |
|
if (level == DEBUG_LEVEL_ERROR) udp_log_flush(); |
|
} |
|
|
|
int main(void) { |
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); |
|
owner = pthread_self(); |
|
int fd = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); assert(fd >= 0); |
|
int buffer_size = 1024 * 1024; |
|
assert(setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buffer_size, sizeof(buffer_size)) == 0); |
|
struct sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
|
assert(bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0); |
|
socklen_t addr_len = sizeof(addr); |
|
assert(getsockname(fd, (struct sockaddr*)&addr, &addr_len) == 0); |
|
int port = ntohs(addr.sin_port); |
|
struct UASYNC* ua = uasync_create(); assert(ua); |
|
udp_log_set_target("127.0.0.1", port); udp_log_attach(ua); |
|
pthread_t threads[4]; struct producer producers[4] = {0}; |
|
for (unsigned i = 0; i < 4; i++) { |
|
producers[i].id = i; producers[i].port = port; |
|
assert(pthread_create(&threads[i], NULL, produce, &producers[i]) == 0); |
|
} |
|
unsigned received = 0; |
|
uint64_t deadline = get_time_tb() + 30000; |
|
while (received < 4000 && get_time_tb() < deadline) { |
|
uasync_poll(ua, 10); |
|
received += receive_lines(fd); |
|
} |
|
for (int i = 0; i < 4; i++) assert(pthread_join(threads[i], NULL) == 0); |
|
assert(received == 4000); |
|
assert(timer_sets && ua->timer_alloc_count - ua->timer_free_count == 1); |
|
puts("PASS: 4000 records from 4 threads, target changes and timed flush without torn records"); |
|
|
|
/* Команда в очереди перед detach не должна обращаться к уничтоженному ua. */ |
|
udp_log_set_target("127.0.0.1", port); |
|
udp_log_detach(ua); uasync_poll(ua, 0); |
|
assert(ua->timer_alloc_count == ua->timer_free_count); |
|
uasync_destroy(ua, 0); |
|
unsigned detached_sets = timer_sets; |
|
udp_log_write("detached\n", 9); |
|
udp_log_set_target("127.0.0.1", port); |
|
assert(timer_sets == detached_sets); |
|
udp_log_stop(); |
|
char error_log[4096]; |
|
while (recv(fd, error_log, sizeof(error_log), MSG_DONTWAIT) > 0) {} |
|
|
|
/* Ошибка установки таймера логируется через тот же hook, без deadlock. */ |
|
ua = uasync_create(); assert(ua); |
|
debug_set_log_hook(forward_log); |
|
udp_log_set_target("127.0.0.1", port); fail_timer = 1; udp_log_attach(ua); |
|
ssize_t error_len = recv(fd, error_log, sizeof(error_log) - 1, MSG_DONTWAIT); |
|
assert(error_len > 0); error_log[error_len] = 0; |
|
assert(strstr(error_log, "injected flush timer allocation failure")); |
|
udp_log_set_target("127.0.0.1", port); uasync_poll(ua, 0); |
|
assert(ua->timer_alloc_count - ua->timer_free_count == 1); |
|
udp_log_write("x", INT_MAX); /* Отказ до чтения данных, без переполнения длины. */ |
|
udp_log_set_target(NULL, 0); uasync_poll(ua, 0); |
|
assert(ua->timer_alloc_count == ua->timer_free_count); |
|
udp_log_set_target("127.0.0.1", port); uasync_poll(ua, 0); |
|
assert(ua->timer_alloc_count - ua->timer_free_count == 1); |
|
udp_log_detach(ua); udp_log_stop(); uasync_poll(ua, 0); |
|
assert(ua->timer_alloc_count == ua->timer_free_count && timer_cancels); |
|
debug_set_log_hook(NULL); |
|
uasync_destroy(ua, 0); close(fd); |
|
puts("PASS: detach/restart, disable/re-enable, oversized log and reentrant error hook"); |
|
return 0; |
|
}
|
|
|