+/** Set up the main context the session will be executing in.
+ *
+ * Must be called just before the session starts, by the thread which
+ * will execute the session main loop. Once acquired, the main context
+ * pointer is immutable for the duration of the session run.
+ */
+static int set_main_context(struct sr_session *session)
+{
+ GMainContext *main_context;
+
+ g_mutex_lock(&session->main_mutex);
+
+ /* May happen if sr_session_start() is called a second time
+ * while the session is still running.
+ */
+ if (session->main_context) {
+ sr_err("Main context already set.");
+
+ g_mutex_unlock(&session->main_mutex);
+ return SR_ERR;
+ }
+ main_context = g_main_context_ref_thread_default();
+ /*
+ * Try to use an existing main context if possible, but only if we
+ * can make it owned by the current thread. Otherwise, create our
+ * own main context so that event source callbacks can execute in
+ * the session thread.
+ */
+ if (g_main_context_acquire(main_context)) {
+ g_main_context_release(main_context);
+
+ sr_dbg("Using thread-default main context.");
+ } else {
+ g_main_context_unref(main_context);
+
+ sr_dbg("Creating our own main context.");
+ main_context = g_main_context_new();
+ }
+ session->main_context = main_context;
+
+ g_mutex_unlock(&session->main_mutex);
+
+ return SR_OK;
+}
+
+/** Unset the main context used for the current session run.
+ *
+ * Must be called right after stopping the session. Note that if the
+ * session is stopped asynchronously, the main loop may still be running
+ * after the main context has been unset. This is OK as long as no new
+ * event sources are created -- the main loop holds its own reference
+ * to the main context.
+ */
+static int unset_main_context(struct sr_session *session)
+{
+ int ret;
+
+ g_mutex_lock(&session->main_mutex);
+
+ if (session->main_context) {
+ g_main_context_unref(session->main_context);
+ session->main_context = NULL;
+ ret = SR_OK;
+ } else {
+ /* May happen if the set/unset calls are not matched.
+ */
+ sr_err("No main context to unset.");
+ ret = SR_ERR;
+ }
+ g_mutex_unlock(&session->main_mutex);
+
+ return ret;
+}
+
+static unsigned int session_source_attach(struct sr_session *session,
+ GSource *source)
+{
+ unsigned int id = 0;
+
+ g_mutex_lock(&session->main_mutex);
+
+ if (session->main_context)
+ id = g_source_attach(source, session->main_context);
+ else
+ sr_err("Cannot add event source without main context.");
+
+ g_mutex_unlock(&session->main_mutex);
+
+ return id;
+}
+
+/* Idle handler; invoked when the number of registered event sources
+ * for a running session drops to zero.
+ */
+static gboolean delayed_stop_check(void *data)
+{
+ struct sr_session *session;
+
+ session = data;
+ session->stop_check_id = 0;
+
+ /* Session already ended? */
+ if (!session->running)
+ return G_SOURCE_REMOVE;
+
+ /* New event sources may have been installed in the meantime. */
+ if (g_hash_table_size(session->event_sources) != 0)
+ return G_SOURCE_REMOVE;
+
+ session->running = FALSE;
+ unset_main_context(session);
+
+ sr_info("Stopped.");
+
+ /* This indicates a bug in user code, since it is not valid to
+ * restart or destroy a session while it may still be running.
+ */
+ if (!session->main_loop && !session->stopped_callback) {
+ sr_err("BUG: Session stop left unhandled.");
+ return G_SOURCE_REMOVE;
+ }
+ if (session->main_loop)
+ g_main_loop_quit(session->main_loop);
+
+ if (session->stopped_callback)
+ (*session->stopped_callback)(session->stopped_cb_data);
+
+ return G_SOURCE_REMOVE;
+}
+
+static int stop_check_later(struct sr_session *session)
+{
+ GSource *source;
+ unsigned int source_id;
+
+ if (session->stop_check_id != 0)
+ return SR_OK; /* idle handler already installed */
+
+ source = g_idle_source_new();
+ g_source_set_callback(source, &delayed_stop_check, session, NULL);
+
+ source_id = session_source_attach(session, source);
+ session->stop_check_id = source_id;
+
+ g_source_unref(source);
+
+ return (source_id != 0) ? SR_OK : SR_ERR;
+}
+