talloc_free(ctx);
}
-#if 0
/*
* Test calling reconnect with requests in each different state
*/
preq->treq = treq;
tconn = treq->pub.tconn; /* Store the conn the request was assigned to */
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING), 1);
fr_trunk_connection_signal_reconnect(tconn, FR_CONNECTION_FAILED);
* Should be reassigned to the other connection
*/
TEST_CHECK(tconn != treq->pub.tconn);
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING), 1);
/*
* Should be reassigned to the backlog
*/
fr_trunk_connection_signal_reconnect(treq->pub.tconn, FR_CONNECTION_FAILED);
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_BACKLOG) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_BACKLOG), 1);
TEST_CHECK(!treq->pub.tconn);
TEST_CASE("cancel on reconnect - FR_TRUNK_REQUEST_STATE_PARTIAL");
+ fr_debug_lvl = 4;
+
/*
* Allow the connections to reconnect
*/
test_time_base = fr_time_add_time_delta(test_time_base, fr_time_delta_from_sec(1));
fr_event_corral(el, test_time_base, false);
- fr_event_service(el);
+ fr_event_service(el); /* run management function */
+ fr_event_service(el); /* service any I/O callbacks */
/*
* Request should now be assigned back to one of the reconnected
* connections.
*/
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING), 1);
TEST_CHECK(treq->pub.tconn != NULL);
test_time_base = fr_time_add_time_delta(test_time_base, fr_time_delta_from_sec(1));
fr_event_corral(el, test_time_base, false); /* Send the request (partially) */
fr_event_service(el);
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PARTIAL) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PARTIAL), 1);
/*
* Reconnect the connection.
preq->cancelled = false; /* Reset */
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING), 1);
TEST_CHECK(tconn != treq->pub.tconn); /* Ensure it moved */
TEST_CASE("cancel on reconnect - FR_TRUNK_REQUEST_STATE_SENT");
fr_event_corral(el, test_time_base, false); /* Send the request (partially) */
fr_event_service(el);
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_SENT) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_SENT), 1);
tconn = treq->pub.tconn;
fr_trunk_connection_signal_reconnect(treq->pub.tconn, FR_CONNECTION_FAILED);
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING), 1);
/*
* Allow the connections to reconnect
* Signal the request should be cancelled
*/
fr_trunk_request_signal_cancel(treq);
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_CANCEL) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_CANCEL), 1);
/*
* Requests in the cancel state, are
treq = NULL;
fr_trunk_request_enqueue(&treq, trunk, NULL, preq, NULL);
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING), 1);
/*
* Sent the request (fully)
fr_event_corral(el, test_time_base, false); /* Send the request (fully) */
fr_event_service(el);
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_SENT) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_SENT), 1);
fr_trunk_request_signal_cancel(treq); /* Cancel the request */
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_CANCEL) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_CANCEL), 1);
/*
* Transition to cancel partial
fr_event_corral(el, test_time_base, false);
fr_event_service(el);
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_CANCEL_PARTIAL) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_CANCEL_PARTIAL), 1);
/*
* Trigger a reconnection
treq = NULL;
fr_trunk_request_enqueue(&treq, trunk, NULL, preq, NULL);
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_PENDING), 1);
/*
* Sent the request (fully)
fr_event_corral(el, test_time_base, false); /* Send the request (fully) */
fr_event_service(el);
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_SENT) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_SENT), 1);
fr_trunk_request_signal_cancel(treq); /* Cancel the request */
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_CANCEL) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_CANCEL), 1);
/*
* Transition to cancel
fr_event_corral(el, test_time_base, false);
fr_event_service(el);
- TEST_CHECK(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_CANCEL_SENT) == 1);
+ TEST_CHECK_LEN(fr_trunk_request_count_by_state(trunk, FR_TRUNK_CONN_ALL, FR_TRUNK_REQUEST_STATE_CANCEL_SENT), 1);
/*
* Trigger a reconnection
talloc_free(ctx);
}
-#endif
static void test_connection_start_on_enqueue(void)
{
{ "Enqueue - Basic", test_enqueue_basic },
{ "Enqueue - Cancellation points", test_enqueue_cancellation_points },
{ "Enqueue - Partial state transitions", test_partial_to_complete_states },
-#if 0
{ "Requeue - On reconnect", test_requeue_on_reconnect },
-#endif
/*
* Rebalance
#include <pthread.h>
-#if defined(DEBUG_THREAD_LOCAL) && !defined(NDEBUG)
-# define THREAD_LOCAL_DEBUG FR_FAULT_LOG
+#if defined(DEBUG_ATEXIT) && !defined(NDEBUG)
+# define ATEXIT_DEBUG FR_FAULT_LOG
#else
-# define THREAD_LOCAL_DEBUG(...)
+# define ATEXIT_DEBUG(...)
#endif
typedef struct fr_exit_handler_list_s fr_atexit_list_t;
*/
static int _atexit_entry_free(fr_atexit_entry_t *e)
{
- THREAD_LOCAL_DEBUG("%s - Thread %u freeing %p/%p func=%p, uctx=%p (alloced %s:%u)",
+ ATEXIT_DEBUG("%s - Thread %u freeing %p/%p func=%p, uctx=%p (alloced %s:%u)",
__FUNCTION__, (unsigned int)pthread_self(),
e->list, e, e->func, e->uctx, e->file, e->line);
e->line = line;
#endif
- THREAD_LOCAL_DEBUG("%s - Thread %u arming %p/%p func=%p, uctx=%p (alloced %s:%u)",
+ ATEXIT_DEBUG("%s - Thread %u arming %p/%p func=%p, uctx=%p (alloced %s:%u)",
__FUNCTION__, (unsigned int)pthread_self(),
list, e, e->func, e->uctx, e->file, e->line);
*/
static int _thread_local_list_free(fr_atexit_list_t *list)
{
- THREAD_LOCAL_DEBUG("%s - Freeing _Thread_local destructor list %p",
+ ATEXIT_DEBUG("%s - Freeing _Thread_local destructor list %p",
__FUNCTION__, list);
fr_dlist_talloc_free(&list->head); /* Free in order */
*/
static int _global_list_free(fr_atexit_list_t *list)
{
- THREAD_LOCAL_DEBUG("%s - Freeing global destructor list %p",
+ ATEXIT_DEBUG("%s - Freeing global destructor list %p",
__FUNCTION__, list);
fr_dlist_talloc_free(&list->head); /* Free in order */
fr_atexit_global = talloc_zero(NULL, fr_atexit_list_t);
if (unlikely(!fr_atexit_global)) return -1;
- THREAD_LOCAL_DEBUG("%s - Alloced global destructor list %p", __FUNCTION__, fr_atexit_global);
+ ATEXIT_DEBUG("%s - Alloced global destructor list %p", __FUNCTION__, fr_atexit_global);
fr_dlist_talloc_init(&fr_atexit_global->head, fr_atexit_entry_t, entry);
talloc_set_destructor(fr_atexit_global, _global_list_free);
fr_atexit_threads = talloc_zero(NULL, fr_atexit_list_t);
if (unlikely(!fr_atexit_threads)) return -1;
- THREAD_LOCAL_DEBUG("%s - Alloced threads destructor list %p", __FUNCTION__, fr_atexit_threads);
+ ATEXIT_DEBUG("%s - Alloced threads destructor list %p", __FUNCTION__, fr_atexit_threads);
fr_dlist_talloc_init(&fr_atexit_threads->head, fr_atexit_entry_t, entry);
talloc_set_destructor(fr_atexit_threads, _global_list_free);
list = talloc_zero(NULL, fr_atexit_list_t);
if (unlikely(!list)) return -1;
- THREAD_LOCAL_DEBUG("%s - Thread %u alloced _Thread_local destructor list %p",
+ ATEXIT_DEBUG("%s - Thread %u alloced _Thread_local destructor list %p",
__FUNCTION__,
(unsigned int)pthread_self(), list);
return 0;
}
-/** Remove destructor
+/** Remove a specific destructor for this thread (without executing them)
*
- * @return
- * - 0 on success.
- * - -1 if function and uctx could not be found.
+ * @note This function's primary purpose is to help diagnose issues with destructors
+ * from within a debugger.
+ *
+ * @param[in] uctx_scope Only process entries where the func and scope both match.
+ * @param[in] func Entries matching this function will be disarmed.
+ * @param[in] uctx associated with the entry.
+ * @return How many destructors were disarmed.
*/
-int fr_atexit_thread_local_disarm(fr_atexit_t func, void const *uctx)
+unsigned int fr_atexit_thread_local_disarm(bool uctx_scope, fr_atexit_t func, void const *uctx)
{
- fr_atexit_entry_t *e = NULL;
+ fr_atexit_entry_t *e = NULL;
+ unsigned int count = 0;
if (!fr_atexit_thread_local) return -1;
while ((e = fr_dlist_next(&fr_atexit_thread_local->head, e))) {
- if ((e->func == func) && (e->uctx == uctx)) {
- THREAD_LOCAL_DEBUG("%s - Thread %u disarming %p/%p func=%p, uctx=%p (alloced %s:%u)",
- __FUNCTION__,
- (unsigned int)pthread_self(),
- fr_atexit_thread_local, e, e->func, e->uctx, e->file, e->line);
- fr_dlist_remove(&fr_atexit_thread_local->head, e);
- talloc_set_destructor(e, NULL);
- talloc_free(e);
- return 0;
- }
+ fr_atexit_entry_t *disarm;
+
+ if ((e->func != func) || !uctx_scope || (e->uctx != uctx)) continue;
+
+ ATEXIT_DEBUG("%s - Thread %u disarming %p/%p func=%p, uctx=%p (alloced %s:%u)",
+ __FUNCTION__,
+ (unsigned int)pthread_self(),
+ fr_atexit_thread_local, e, e->func, e->uctx, e->file, e->line);
+ disarm = e;
+ e = fr_dlist_remove(&fr_atexit_thread_local->head, e);
+ talloc_set_destructor(disarm, NULL);
+ talloc_free(disarm);
+
+ count++;
}
- return -1;
+ return count;
}
+/** Remove all destructors for this thread (without executing them)
+ *
+ * @note This function's primary purpose is to help diagnose issues with destructors
+ * from within a debugger.
+ */
void fr_atexit_thread_local_disarm_all(void)
{
fr_atexit_entry_t *e = NULL;
if (!fr_atexit_thread_local) return;
while ((e = fr_dlist_pop_head(&fr_atexit_thread_local->head))) {
- THREAD_LOCAL_DEBUG("%s - Thread %u disarming %p/%p func=%p, uctx=%p (alloced %s:%u)",
- __FUNCTION__,
- (unsigned int)pthread_self(),
- fr_atexit_thread_local, e, e->func, e->uctx, e->file, e->line);
+ ATEXIT_DEBUG("%s - Thread %u disarming %p/%p func=%p, uctx=%p (alloced %s:%u)",
+ __FUNCTION__,
+ (unsigned int)pthread_self(),
+ fr_atexit_thread_local, e, e->func, e->uctx, e->file, e->line);
+ talloc_set_destructor(e, NULL);
+ talloc_free(e);
+ }
+}
+
+/** Remove a specific global destructor (without executing it)
+ *
+ * @note This function's primary purpose is to help diagnose issues with destructors
+ * from within a debugger.
+ *
+ * @param[in] uctx_scope Only process entries where the func and scope both match.
+ * @param[in] func Entries matching this function will be disarmed.
+ * @param[in] uctx associated with the entry.
+ * @return How many global destructors were disarmed.
+ */
+unsigned int fr_atexit_disarm(bool uctx_scope, fr_atexit_t func, void const *uctx)
+{
+ fr_atexit_entry_t *e = NULL;
+ unsigned int count = 0;
+
+ if (!fr_atexit_thread_local) return -1;
+
+ while ((e = fr_dlist_next(&fr_atexit_global->head, e))) {
+ fr_atexit_entry_t *disarm;
+
+ if ((e->func != func) || !uctx_scope || (e->uctx != uctx)) continue;
+
+ ATEXIT_DEBUG("%s - Disarming %p/%p func=%p, uctx=%p (alloced %s:%u)",
+ __FUNCTION__,
+ fr_atexit_global, e, e->func, e->uctx, e->file, e->line);
+
+ disarm = e;
+ e = fr_dlist_remove(&fr_atexit_global->head, e);
+ talloc_set_destructor(disarm, NULL);
+ talloc_free(disarm);
+
+ count++;
+ }
+
+ return count;
+}
+
+/** Remove all global destructors (without executing them)
+ *
+ * @note This function's primary purpose is to help diagnose issues with destructors
+ * from within a debugger.
+ */
+void fr_atexit_disarm_all(void)
+{
+ fr_atexit_entry_t *e = NULL;
+
+ if (!fr_atexit_global) return;
+
+ while ((e = fr_dlist_pop_head(&fr_atexit_global->head))) {
+ ATEXIT_DEBUG("%s - Disarming %p/%p func=%p, uctx=%p (alloced %s:%u)",
+ __FUNCTION__,
+ fr_atexit_global, e, e->func, e->uctx, e->file, e->line);
+
talloc_set_destructor(e, NULL);
talloc_free(e);
}
/** Iterates through all thread local destructor lists, causing destructor to be triggered
*
- * This should only be called by the main process, and not by threads.
+ * This should only be called by the main process not by threads.
+ *
+ * The main purpose of the function is to force cleanups at a specific time for problematic
+ * destructors.
*
- * @param[in] func Entries matching this function will be triggered.
+ * @param[in] uctx_scope Only process entries where the func and scope both match.
+ * @param[in] func Entries matching this function will be triggered.
+ * @param[in] uctx associated with the entry.
* @return How many triggers fired.
*/
-int fr_atexit_trigger(fr_atexit_t func)
+unsigned int fr_atexit_trigger(bool uctx_scope, fr_atexit_t func, void const *uctx)
{
fr_atexit_entry_t *e = NULL, *ee;
fr_atexit_list_t *list;
if (!fr_atexit_global) return 0;
/*
- * Iterate over the list of thread local destructor
- * lists.
+ * Iterate over the global destructors
+ */
+ while ((e = fr_dlist_next(&fr_atexit_global->head, e))) {
+ if ((e->func != func) || !uctx_scope || (e->uctx != uctx)) continue;
+
+ ATEXIT_DEBUG("%s - Triggering %p/%p func=%p, uctx=%p (alloced %s:%u)",
+ __FUNCTION__,
+ fr_atexit_global, e, e->func, e->uctx, e->file, e->line);
+
+ count++;
+ e = fr_dlist_talloc_free_item(&fr_atexit_global->head, e);
+ }
+ e = NULL;
+
+ /*
+ * Iterate over the list of thread local
+ * destructor lists running the
+ * destructors.
*/
while ((e = fr_dlist_next(&fr_atexit_threads->head, e))) {
if (!e->func) continue; /* thread already joined */
list = talloc_get_type_abort(e->uctx, fr_atexit_list_t);
ee = NULL;
while ((ee = fr_dlist_next(&list->head, ee))) {
- if (ee->func != func) continue;
+ if ((ee->func != func) || !uctx_scope || (e->uctx != uctx)) continue;
+
+ ATEXIT_DEBUG("%s - Thread %u triggering %p/%p func=%p, uctx=%p (alloced %s:%u)",
+ __FUNCTION__,
+ (unsigned int)pthread_self(),
+ list, ee, ee->func, ee->uctx, ee->file, ee->line);
count++;
ee = fr_dlist_talloc_free_item(&list->head, ee);