/* Реальный 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 #include #include #include #include #include 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; }