+/*
+ * Called with the rotation_timer_queue lock held.
+ * Return true if the same timer job already exists in the queue, false if not.
+ */
+static
+bool check_duplicate_timer_job(struct timer_thread_parameters *ctx,
+ struct ltt_session *session, unsigned int signal)
+{
+ bool ret = false;
+ struct sessiond_rotation_timer *node;
+
+ rcu_read_lock();
+ cds_list_for_each_entry(node, &ctx->rotation_timer_queue->list, head) {
+ if (node->session_id == session->id && node->signal == signal) {
+ ret = true;
+ goto end;
+ }
+ }
+
+end:
+ rcu_read_unlock();
+ return ret;
+}
+
+/*
+ * Add the session ID and signal value to the rotation_timer_queue if it is
+ * not already there and wakeup the rotation thread. The rotation thread
+ * empties the whole queue everytime it is woken up. The event_pipe is
+ * non-blocking, if it would block, we just return because we know the
+ * rotation thread will be awaken anyway.
+ */
+static
+int enqueue_timer_rotate_job(struct timer_thread_parameters *ctx,
+ struct ltt_session *session, unsigned int signal)
+{
+ int ret;
+ char *c = "!";
+ struct sessiond_rotation_timer *timer_data = NULL;
+
+ pthread_mutex_lock(&ctx->rotation_timer_queue->lock);
+ if (check_duplicate_timer_job(ctx, session, signal)) {
+ /*
+ * This timer job is already pending, we don't need to add
+ * it.
+ */
+ ret = 0;
+ goto end;
+ }
+
+ timer_data = zmalloc(sizeof(struct sessiond_rotation_timer));
+ if (!timer_data) {
+ PERROR("Allocation of timer data");
+ ret = -1;
+ goto end;
+ }
+ timer_data->session_id = session->id;
+ timer_data->signal = signal;
+ cds_list_add_tail(&timer_data->head,
+ &ctx->rotation_timer_queue->list);
+
+ ret = lttng_write(
+ lttng_pipe_get_writefd(ctx->rotation_timer_queue->event_pipe),
+ c, 1);
+ if (ret < 0) {
+ /*
+ * We do not want to block in the timer handler, the job has been
+ * enqueued in the list, the wakeup pipe is probably full, the job
+ * will be processed when the rotation_thread catches up.
+ */
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ ret = 0;
+ goto end;
+ }
+ PERROR("Timer wakeup rotation thread");
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ pthread_mutex_unlock(&ctx->rotation_timer_queue->lock);
+ return ret;
+}
+
+/*
+ * Ask the rotation thread to check if the last rotation started in this
+ * session is still pending on the relay.
+ */
+static
+void relay_rotation_pending_timer(struct timer_thread_parameters *ctx,
+ int sig, siginfo_t *si)
+{
+ struct ltt_session *session = si->si_value.sival_ptr;
+
+ assert(session);
+
+ (void) enqueue_timer_rotate_job(ctx, session,
+ LTTNG_SESSIOND_SIG_ROTATE_PENDING);
+}
+
+/*
+ * Handle the LTTNG_SESSIOND_SIG_ROTATE_TIMER timer. Add the session ID to
+ * the rotation_timer_queue so the rotation thread can trigger a new rotation
+ * on that session.
+ */
+static
+void rotate_timer(struct timer_thread_parameters *ctx, int sig, siginfo_t *si)
+{
+ int ret;
+ /*
+ * The session cannot be freed/destroyed while we are running this
+ * signal handler.
+ */
+ struct ltt_session *session = si->si_value.sival_ptr;
+ assert(session);
+
+ ret = enqueue_timer_rotate_job(ctx, session, LTTNG_SESSIOND_SIG_ROTATE_TIMER);
+ if (ret) {
+ PERROR("wakeup rotate pipe");
+ }
+}
+