Fix memory leaks and crashes in ETCP tests

✅ Fixed critical segmentation fault (SIGABRT) in test_etcp_two_instances
✅ Added comprehensive timer debug logging with DEBUG_CATEGORY_TIMERS
✅ Created uasync_print_resources() function for resource diagnostics
✅ Created utun_instance_diagnose_leaks() function for leak analysis
✅ Fixed cleanup order - cancel timers before destroying uasync instances
✅ Fixed timer cancellation to properly update counters
✅ Fixed socket cleanup to unregister from uasync before destruction
✅ Added detailed diagnostic output for memory leak tracking
✅ All tests now pass without crashes

Key fixes:
- Fixed use-after-free in test cleanup sequence
- Added proper timer leak detection and cleanup
- Enhanced debug capabilities for future debugging
- Fixed ETCP socket cleanup to prevent resource leaks

The test_etcp_two_instances now runs successfully without segmentation faults.
This commit is contained in:
Evgeny committed 2026-01-22 00:57:45 +03:00
1 parent e4922c1d31
commit f1a76c9d30
15 files changed
+1728 -93

No files matched your search

+110 -10
View File
@@ -315,10 +315,19 @@ void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_c
if (!ua || timeout_tb < 0 || !callback) return NULL;
if (!ua->timeout_heap) return NULL;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p",
ua, timeout_tb, arg, callback);
struct timeout_node* node = malloc(sizeof(struct timeout_node));
if (!node) return NULL;
if (!node) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node");
return NULL;
}
ua->timer_alloc_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)",
node, ua->timer_alloc_count);
node->arg = arg;
node->callback = callback;
node->ua = ua;
@@ -330,13 +339,18 @@ void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_c
timeval_add_tb(&now, timeout_tb);
node->expiration_ms = timeval_to_ms(&now);
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms",
node, (unsigned long long)node->expiration_ms);
// Add to heap
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap");
free(node);
ua->timer_free_count++; // Balance the alloc counter
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node);
return node;
}
@@ -344,25 +358,31 @@ void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_c
// Instance version
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
if (!ua || !t_id || !ua->timeout_heap) return ERR_FAIL;
if (!ua || !t_id || !ua->timeout_heap) {
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
ua, t_id, ua ? ua->timeout_heap : NULL);
return ERR_FAIL;
}
struct timeout_node* node = (struct timeout_node*)t_id;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms",
ua, t_id, node, (unsigned long long)node->expiration_ms);
// Try to cancel from heap first
if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) {
// Successfully removed from heap - mark as cancelled
// Successfully removed from heap - mark as cancelled and update counter
node->cancelled = 1;
node->callback = NULL;
ua->timer_free_count++; // Update counter for cancelled timer
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap, free_count=%zu", node, ua->timer_free_count);
free(node); // Free the cancelled timer node
return ERR_OK;
}
// If not found in heap (maybe already expired and being processed)
// We still need to mark it as cancelled to prevent callback execution
node->cancelled = 1;
node->callback = NULL;
return ERR_OK; // Successfully cancelled (marked)
}
return ERR_OK;
} // Successfully cancelled (marked)
// Instance version
@@ -628,13 +648,84 @@ struct UASYNC* uasync_create(void) {
return ua;
}
// Print all resources for debugging
void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
if (!ua) {
printf("%s: NULL uasync instance\n", prefix);
return;
}
printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua);
printf(" Timer Statistics: allocated=%zu, freed=%zu, active=%zd\n",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
printf(" Socket Statistics: allocated=%zu, freed=%zu, active=%zd\n",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Показать активные таймеры
if (ua->timeout_heap) {
size_t active_timers = 0;
TimeoutEntry entry;
// Создаем временную копию кучи для подсчета
TimeoutHeap* temp_heap = timeout_heap_create(16);
if (temp_heap) {
// Копируем все активные таймеры
while (timeout_heap_pop(ua->timeout_heap, &entry) == 0) {
if (!entry.deleted) {
active_timers++;
struct timeout_node* node = (struct timeout_node*)entry.data;
printf(" Timer: node=%p, expires=%llu ms, cancelled=%d\n",
node, (unsigned long long)entry.expiration, node->cancelled);
}
timeout_heap_push(temp_heap, entry.expiration, entry.data);
}
// Возвращаем таймеры обратно
while (timeout_heap_pop(temp_heap, &entry) == 0) {
timeout_heap_push(ua->timeout_heap, entry.expiration, entry.data);
}
timeout_heap_destroy(temp_heap);
}
printf(" Active timers in heap: %zu\n", active_timers);
}
// Показать активные сокеты
if (ua->sockets) {
int active_sockets = 0;
printf(" Socket array capacity: %d, active: %d\n",
ua->sockets->capacity, ua->sockets->count);
for (int i = 0; i < ua->sockets->capacity; i++) {
if (ua->sockets->sockets[i].active) {
active_sockets++;
printf(" Socket: fd=%d, active=%d\n",
ua->sockets->sockets[i].fd,
ua->sockets->sockets[i].active);
}
}
printf(" Total active sockets: %d\n", active_sockets);
}
printf("🔚 %s: End of resource report\n\n", prefix);
}
void uasync_destroy(struct UASYNC* ua) {
if (!ua) return;
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua);
// Диагностика ресурсов перед очисткой
uasync_print_resources(ua, "BEFORE_DESTROY");
// Check for potential memory leaks
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
// Continue cleanup, will abort after if leaks remain
}
@@ -645,11 +736,13 @@ void uasync_destroy(struct UASYNC* ua) {
TimeoutEntry entry;
if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break;
struct timeout_node* node = (struct timeout_node*)entry.data;
DEBUG_TRACE(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freeing timer node %p (expired=%llu ms)",
node, (unsigned long long)entry.expiration);
ua->timer_free_count++;
freed_count++;
free(node);
}
DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %zu timer nodes in destroy, heap freed_count = %zu",
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freed %zu timer nodes in destroy, heap freed_count = %zu",
freed_count, ua->timeout_heap->freed_count);
timeout_heap_destroy(ua->timeout_heap);
}
@@ -678,9 +771,16 @@ void uasync_destroy(struct UASYNC* ua) {
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Timer leak: allocated=%zu, freed=%zu, diff=%zd",
ua->timer_alloc_count, ua->timer_free_count,
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Socket leak: allocated=%zu, freed=%zu, diff=%zd",
ua->socket_alloc_count, ua->socket_free_count,
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
abort();
}
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua);
free(ua);
}