+ pthread_mutex_unlock(&side_event_lock);
+ free(tracer_handle);
+}
+
+/* Called with side_statedump_lock held. */
+static
+void queue_statedump_pending(struct side_statedump_request_handle *handle, uint64_t key)
+{
+ struct side_statedump_notification *notif;
+
+ notif = (struct side_statedump_notification *) calloc(1, sizeof(struct side_statedump_notification));
+ if (!notif)
+ abort();
+ notif->key = key;
+ side_list_insert_node_tail(&handle->notification_queue, ¬if->node);
+ if (handle->mode == SIDE_STATEDUMP_MODE_AGENT_THREAD) {
+ (void)__atomic_or_fetch(&statedump_agent_thread.state, AGENT_THREAD_STATE_HANDLE_REQUEST, __ATOMIC_SEQ_CST);
+ pthread_cond_broadcast(&statedump_agent_thread.worker_cond);
+ }
+}
+
+/* Called with side_statedump_lock held. */
+static
+void unqueue_statedump_pending(struct side_statedump_request_handle *handle, uint64_t key)
+{
+ struct side_statedump_notification *notif, *tmp;
+
+ side_list_for_each_entry_safe(notif, tmp, &handle->notification_queue, node) {
+ if (key == SIDE_KEY_MATCH_ALL || key == notif->key) {
+ side_list_remove_node(¬if->node);
+ free(notif);
+ }
+ }
+}
+
+static
+void side_statedump_run(struct side_statedump_request_handle *handle,
+ struct side_statedump_notification *notif)
+{
+ side_statedump_event_call(side_statedump_begin, ¬if->key,
+ side_arg_list(side_arg_string(handle->name)));
+ /* Invoke the state dump callback specifically for the tracer key. */
+ handle->cb(¬if->key);
+ side_statedump_event_call(side_statedump_end, ¬if->key,
+ side_arg_list(side_arg_string(handle->name)));
+}
+
+static
+void _side_statedump_run_pending_requests(struct side_statedump_request_handle *handle)
+{
+ struct side_statedump_notification *notif, *tmp;
+ DEFINE_SIDE_LIST_HEAD(tmp_head);
+
+ pthread_mutex_lock(&side_statedump_lock);
+ side_list_splice(&handle->notification_queue, &tmp_head);
+ side_list_head_init(&handle->notification_queue);
+ pthread_mutex_unlock(&side_statedump_lock);
+
+ /* We are now sole owner of the tmp_head list. */
+ side_list_for_each_entry(notif, &tmp_head, node)
+ side_statedump_run(handle, notif);
+ side_list_for_each_entry_safe(notif, tmp, &tmp_head, node)
+ free(notif);
+
+ if (handle->mode == SIDE_STATEDUMP_MODE_AGENT_THREAD) {
+ pthread_mutex_lock(&side_statedump_lock);
+ pthread_cond_broadcast(&statedump_agent_thread.waiter_cond);
+ pthread_mutex_unlock(&side_statedump_lock);
+ }
+}
+
+static
+void *statedump_agent_func(void *arg __attribute__((unused)))
+{
+ for (;;) {
+ struct side_statedump_request_handle *handle;
+ struct side_rcu_read_state rcu_read_state;
+ enum agent_thread_state state;
+
+ pthread_mutex_lock(&side_statedump_lock);
+ for (;;) {
+ state = __atomic_load_n(&statedump_agent_thread.state, __ATOMIC_SEQ_CST);
+ if (state == AGENT_THREAD_STATE_BLOCKED)
+ pthread_cond_wait(&statedump_agent_thread.worker_cond, &side_statedump_lock);
+ else
+ break;
+ }
+ pthread_mutex_unlock(&side_statedump_lock);
+ if (state & AGENT_THREAD_STATE_EXIT)
+ break;
+ if (state & AGENT_THREAD_STATE_PAUSE) {
+ int attempt = 0;
+
+ (void)__atomic_or_fetch(&statedump_agent_thread.state, AGENT_THREAD_STATE_PAUSE_ACK, __ATOMIC_SEQ_CST);
+ for (;;) {
+ state = __atomic_load_n(&statedump_agent_thread.state, __ATOMIC_SEQ_CST);
+ if (!(state & AGENT_THREAD_STATE_PAUSE))
+ break;
+ if (attempt > SIDE_RETRY_BUSY_LOOP_ATTEMPTS) {
+ (void)poll(NULL, 0, SIDE_RETRY_DELAY_MS);
+ continue;
+ }
+ attempt++;
+ side_cpu_relax();
+ }
+ continue;
+ }
+ (void)__atomic_and_fetch(&statedump_agent_thread.state, ~AGENT_THREAD_STATE_HANDLE_REQUEST, __ATOMIC_SEQ_CST);
+ side_rcu_read_begin(&statedump_rcu_gp, &rcu_read_state);
+ side_list_for_each_entry_rcu(handle, &side_statedump_list, node)
+ _side_statedump_run_pending_requests(handle);
+ side_rcu_read_end(&statedump_rcu_gp, &rcu_read_state);
+ }
+ return NULL;
+}
+
+static
+void statedump_agent_thread_init(void)
+{
+ pthread_cond_init(&statedump_agent_thread.worker_cond, NULL);
+ pthread_cond_init(&statedump_agent_thread.waiter_cond, NULL);
+ statedump_agent_thread.state = AGENT_THREAD_STATE_BLOCKED;
+}
+
+/* Called with side_agent_thread_lock and side_statedump_lock held. */
+static
+void statedump_agent_thread_get(void)
+{
+ int ret;
+
+ if (statedump_agent_thread.ref++)
+ return;
+ statedump_agent_thread_init();
+ ret = pthread_create(&statedump_agent_thread.id, NULL,
+ statedump_agent_func, NULL);
+ if (ret) {
+ abort();
+ }
+}
+
+/*
+ * Called with side_agent_thread_lock and side_statedump_lock held.
+ * Returns true if join for agent thread is needed.
+ */
+static
+bool statedump_agent_thread_put(void)
+{
+ if (--statedump_agent_thread.ref)
+ return false;
+ (void)__atomic_or_fetch(&statedump_agent_thread.state, AGENT_THREAD_STATE_EXIT, __ATOMIC_SEQ_CST);
+ pthread_cond_broadcast(&statedump_agent_thread.worker_cond);
+ return true;
+}
+
+static
+void statedump_agent_thread_fini(void)
+{
+ statedump_agent_thread.state = AGENT_THREAD_STATE_BLOCKED;
+ if (pthread_cond_destroy(&statedump_agent_thread.worker_cond))
+ abort();
+ if (pthread_cond_destroy(&statedump_agent_thread.waiter_cond))
+ abort();
+}
+
+/* Called with side_agent_thread_lock held. */
+static
+void statedump_agent_thread_join(void)
+{
+ int ret;
+ void *retval;
+
+ ret = pthread_join(statedump_agent_thread.id, &retval);
+ if (ret) {
+ abort();
+ }
+ statedump_agent_thread_fini();
+}
+
+struct side_statedump_request_handle *
+ side_statedump_request_notification_register(const char *state_name,
+ void (*statedump_cb)(void *statedump_request_key),
+ enum side_statedump_mode mode)
+{
+ struct side_statedump_request_handle *handle;
+ char *name;
+
+ if (finalized)
+ return NULL;
+ if (!initialized)
+ side_init();
+ handle = (struct side_statedump_request_handle *)
+ calloc(1, sizeof(struct side_statedump_request_handle));
+ if (!handle)
+ return NULL;
+ name = strdup(state_name);
+ if (!name)
+ goto name_nomem;
+ handle->cb = statedump_cb;
+ handle->name = name;
+ handle->mode = mode;
+ side_list_head_init(&handle->notification_queue);
+
+ if (mode == SIDE_STATEDUMP_MODE_AGENT_THREAD)
+ pthread_mutex_lock(&side_agent_thread_lock);
+ pthread_mutex_lock(&side_statedump_lock);
+ if (mode == SIDE_STATEDUMP_MODE_AGENT_THREAD)
+ statedump_agent_thread_get();
+ side_list_insert_node_tail_rcu(&side_statedump_list, &handle->node);
+ /* Queue statedump pending for all tracers. */
+ queue_statedump_pending(handle, SIDE_KEY_MATCH_ALL);
+ pthread_mutex_unlock(&side_statedump_lock);
+
+ if (mode == SIDE_STATEDUMP_MODE_AGENT_THREAD) {
+ pthread_mutex_unlock(&side_agent_thread_lock);
+
+ pthread_mutex_lock(&side_statedump_lock);
+ while (!side_list_empty(&handle->notification_queue))
+ pthread_cond_wait(&statedump_agent_thread.waiter_cond, &side_statedump_lock);
+ pthread_mutex_unlock(&side_statedump_lock);
+ }
+
+ return handle;
+
+name_nomem:
+ free(handle);
+ return NULL;
+}
+
+void side_statedump_request_notification_unregister(struct side_statedump_request_handle *handle)
+{
+ bool join = false;
+
+ if (finalized)
+ return;
+ if (!initialized)
+ side_init();
+
+ if (handle->mode == SIDE_STATEDUMP_MODE_AGENT_THREAD)
+ pthread_mutex_lock(&side_agent_thread_lock);
+ pthread_mutex_lock(&side_statedump_lock);
+ unqueue_statedump_pending(handle, SIDE_KEY_MATCH_ALL);
+ side_list_remove_node_rcu(&handle->node);
+ if (handle->mode == SIDE_STATEDUMP_MODE_AGENT_THREAD)
+ join = statedump_agent_thread_put();
+ pthread_mutex_unlock(&side_statedump_lock);
+ if (join)
+ statedump_agent_thread_join();
+ if (handle->mode == SIDE_STATEDUMP_MODE_AGENT_THREAD)
+ pthread_mutex_unlock(&side_agent_thread_lock);
+
+ side_rcu_wait_grace_period(&statedump_rcu_gp);
+ free(handle->name);
+ free(handle);
+}
+
+/* Returns true if the handle has pending statedump requests. */
+bool side_statedump_poll_pending_requests(struct side_statedump_request_handle *handle)
+{
+ bool ret;
+
+ if (handle->mode != SIDE_STATEDUMP_MODE_POLLING)
+ return false;
+ pthread_mutex_lock(&side_statedump_lock);
+ ret = !side_list_empty(&handle->notification_queue);
+ pthread_mutex_unlock(&side_statedump_lock);
+ return ret;
+}
+
+/*
+ * Only polling mode state dump handles allow application to explicitly handle the
+ * pending requests.
+ */
+int side_statedump_run_pending_requests(struct side_statedump_request_handle *handle)
+{
+ if (handle->mode != SIDE_STATEDUMP_MODE_POLLING)
+ return SIDE_ERROR_INVAL;
+ _side_statedump_run_pending_requests(handle);
+ return SIDE_ERROR_OK;
+}
+
+/*
+ * Request a state dump for tracer callbacks identified with "key".
+ */
+int side_tracer_statedump_request(uint64_t key)
+{
+ struct side_statedump_request_handle *handle;
+
+ if (key == SIDE_KEY_MATCH_ALL)
+ return SIDE_ERROR_INVAL;
+ pthread_mutex_lock(&side_statedump_lock);
+ side_list_for_each_entry(handle, &side_statedump_list, node)
+ queue_statedump_pending(handle, key);
+ pthread_mutex_lock(&side_statedump_lock);
+ return SIDE_ERROR_OK;
+}
+
+/*
+ * Cancel a statedump request.
+ */
+int side_tracer_statedump_request_cancel(uint64_t key)
+{
+ struct side_statedump_request_handle *handle;
+
+ if (key == SIDE_KEY_MATCH_ALL)
+ return SIDE_ERROR_INVAL;
+ pthread_mutex_lock(&side_statedump_lock);
+ side_list_for_each_entry(handle, &side_statedump_list, node)
+ unqueue_statedump_pending(handle, key);
+ pthread_mutex_lock(&side_statedump_lock);
+ return SIDE_ERROR_OK;
+}
+
+/*
+ * Tracer keys are represented on 64-bit. Return SIDE_ERROR_NOMEM on
+ * overflow (which should never happen in practice).
+ */
+int side_tracer_request_key(uint64_t *key)
+{
+ int ret = SIDE_ERROR_OK;
+
+ pthread_mutex_lock(&side_key_lock);
+ if (side_key_next == 0) {
+ ret = SIDE_ERROR_NOMEM;
+ goto end;
+ }
+ *key = side_key_next++;
+end:
+ pthread_mutex_unlock(&side_key_lock);
+ return ret;
+}
+
+/*
+ * Use of pthread_atfork depends on glibc 2.24 to eliminate hangs when
+ * waiting for the agent thread if the agent thread calls malloc. This
+ * is corrected by GNU libc
+ * commit 8a727af925be63aa6ea0f5f90e16751fd541626b.
+ * Ref. https://bugzilla.redhat.com/show_bug.cgi?id=906468
+ */
+static
+void side_before_fork(void)
+{
+ int attempt = 0;
+
+ pthread_mutex_lock(&side_agent_thread_lock);
+ if (!statedump_agent_thread.ref)
+ return;
+ /* Pause agent thread. */
+ pthread_mutex_lock(&side_statedump_lock);
+ (void)__atomic_or_fetch(&statedump_agent_thread.state, AGENT_THREAD_STATE_PAUSE, __ATOMIC_SEQ_CST);
+ pthread_cond_broadcast(&statedump_agent_thread.worker_cond);
+ pthread_mutex_unlock(&side_statedump_lock);
+ /* Wait for agent thread acknowledge. */
+ while (!(__atomic_load_n(&statedump_agent_thread.state, __ATOMIC_SEQ_CST) & AGENT_THREAD_STATE_PAUSE_ACK)) {
+ if (attempt > SIDE_RETRY_BUSY_LOOP_ATTEMPTS) {
+ (void)poll(NULL, 0, SIDE_RETRY_DELAY_MS);
+ continue;
+ }
+ attempt++;
+ side_cpu_relax();
+ }
+}
+
+static
+void side_after_fork_parent(void)
+{
+ if (statedump_agent_thread.ref)
+ (void)__atomic_and_fetch(&statedump_agent_thread.state,
+ ~(AGENT_THREAD_STATE_PAUSE | AGENT_THREAD_STATE_PAUSE_ACK),
+ __ATOMIC_SEQ_CST);
+ pthread_mutex_unlock(&side_agent_thread_lock);
+}
+
+/*
+ * The agent thread does not exist in the child process after a fork.
+ * Re-initialize its data structures and create a new agent thread.
+ */
+static
+void side_after_fork_child(void)
+{
+ if (statedump_agent_thread.ref) {
+ int ret;
+
+ statedump_agent_thread_fini();
+ statedump_agent_thread_init();
+ ret = pthread_create(&statedump_agent_thread.id, NULL,
+ statedump_agent_func, NULL);
+ if (ret) {
+ abort();
+ }
+ }
+ pthread_mutex_unlock(&side_agent_thread_lock);