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:
1 parent
e4922c1d31
commit
f1a76c9d30
15 files changed
+1728
-93
No files matched your search
+110
-10
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user