1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
5 * Copyright (C) 2000-2003, Ximian, Inc.
12 #include "soup-session-async.h"
13 #include "soup-session-private.h"
14 #include "soup-message-private.h"
15 #include "soup-misc.h"
18 * SECTION:soup-session-async
19 * @short_description: Soup session for asynchronous (main-loop-based) I/O.
21 * #SoupSessionAsync is an implementation of #SoupSession that uses
22 * non-blocking I/O via the glib main loop. It is intended for use in
23 * single-threaded programs.
26 static gboolean run_queue (SoupSessionAsync *sa);
27 static void do_idle_run_queue (SoupSession *session);
29 static void queue_message (SoupSession *session, SoupMessage *req,
30 SoupSessionCallback callback, gpointer user_data);
31 static guint send_message (SoupSession *session, SoupMessage *req);
33 G_DEFINE_TYPE (SoupSessionAsync, soup_session_async, SOUP_TYPE_SESSION)
36 GSource *idle_run_queue_source;
37 } SoupSessionAsyncPrivate;
38 #define SOUP_SESSION_ASYNC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION_ASYNC, SoupSessionAsyncPrivate))
41 soup_session_async_init (SoupSessionAsync *sa)
46 finalize (GObject *object)
48 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (object);
50 if (priv->idle_run_queue_source)
51 g_source_destroy (priv->idle_run_queue_source);
53 G_OBJECT_CLASS (soup_session_async_parent_class)->finalize (object);
57 soup_session_async_class_init (SoupSessionAsyncClass *soup_session_async_class)
59 SoupSessionClass *session_class = SOUP_SESSION_CLASS (soup_session_async_class);
60 GObjectClass *object_class = G_OBJECT_CLASS (session_class);
62 g_type_class_add_private (soup_session_async_class,
63 sizeof (SoupSessionAsyncPrivate));
65 /* virtual method override */
66 session_class->queue_message = queue_message;
67 session_class->send_message = send_message;
69 object_class->finalize = finalize;
74 * soup_session_async_new:
76 * Creates an asynchronous #SoupSession with the default options.
78 * Return value: the new session.
81 soup_session_async_new (void)
83 return g_object_new (SOUP_TYPE_SESSION_ASYNC, NULL);
87 * soup_session_async_new_with_options:
88 * @optname1: name of first property to set
89 * @...: value of @optname1, followed by additional property/value pairs
91 * Creates an asynchronous #SoupSession with the specified options.
93 * Return value: the new session.
96 soup_session_async_new_with_options (const char *optname1, ...)
101 va_start (ap, optname1);
102 session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION_ASYNC,
111 connection_closed (SoupConnection *conn, gpointer session)
113 /* Run the queue in case anyone was waiting for a connection
116 do_idle_run_queue (session);
120 got_connection (SoupConnection *conn, guint status, gpointer user_data)
122 SoupSession *session = user_data;
124 if (status == SOUP_STATUS_OK) {
125 g_signal_connect (conn, "disconnected",
126 G_CALLBACK (connection_closed), session);
128 /* @conn has been marked reserved by SoupSession, but
129 * we don't actually have any specific message in mind
130 * for it. (In particular, the message we were
131 * originally planning to queue on it may have already
132 * been queued on some other connection that became
133 * available while we were waiting for this one to
134 * connect.) So we release the connection into the
135 * idle pool and then just run the queue and see what
138 soup_connection_release (conn);
141 /* Even if the connection failed, we run the queue, since
142 * there may have been messages waiting for the connection
145 do_idle_run_queue (session);
146 g_object_unref (session);
150 run_queue (SoupSessionAsync *sa)
152 SoupSession *session = SOUP_SESSION (sa);
153 SoupMessageQueue *queue = soup_session_get_queue (session);
154 SoupMessageQueueItem *item;
156 SoupConnection *conn;
157 gboolean try_pruning = TRUE, should_prune = FALSE;
158 gboolean started_any = FALSE, is_new;
160 /* FIXME: prefer CONNECTING messages */
163 for (item = soup_message_queue_first (queue);
164 item && !should_prune;
165 item = soup_message_queue_next (queue, item)) {
168 if (!SOUP_MESSAGE_IS_STARTING (msg) ||
169 soup_message_io_in_progress (msg))
172 conn = soup_session_get_connection (session, msg,
173 &should_prune, &is_new);
178 soup_connection_connect_async (conn, got_connection,
179 g_object_ref (session));
181 soup_connection_send_request (conn, msg);
184 soup_message_queue_item_unref (item);
186 if (try_pruning && should_prune) {
187 /* There is at least one message in the queue that
188 * could be sent if we pruned an idle connection from
191 if (soup_session_try_prune_connection (session)) {
192 try_pruning = should_prune = FALSE;
201 request_restarted (SoupMessage *req, gpointer sa)
207 final_finished (SoupMessage *req, gpointer user_data)
209 SoupMessageQueueItem *item = user_data;
210 SoupSession *session = item->session;
212 g_object_ref (session);
214 if (!SOUP_MESSAGE_IS_STARTING (req)) {
215 g_signal_handlers_disconnect_by_func (req, final_finished, item);
217 item->callback (session, req, item->callback_data);
219 g_object_unref (req);
220 soup_message_queue_item_unref (item);
223 do_idle_run_queue (session);
224 g_object_unref (session);
228 idle_run_queue (gpointer sa)
230 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (sa);
232 priv->idle_run_queue_source = NULL;
238 do_idle_run_queue (SoupSession *session)
240 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (session);
242 if (!priv->idle_run_queue_source) {
243 priv->idle_run_queue_source = soup_add_completion (
244 soup_session_get_async_context (session),
245 idle_run_queue, session);
250 queue_message (SoupSession *session, SoupMessage *req,
251 SoupSessionCallback callback, gpointer user_data)
253 SoupMessageQueueItem *item;
255 SOUP_SESSION_CLASS (soup_session_async_parent_class)->queue_message (session, req, callback, user_data);
257 item = soup_message_queue_lookup (soup_session_get_queue (session), req);
258 g_return_if_fail (item != NULL);
260 g_signal_connect (req, "restarted",
261 G_CALLBACK (request_restarted), session);
262 g_signal_connect_after (req, "finished",
263 G_CALLBACK (final_finished), item);
265 do_idle_run_queue (session);
269 send_message (SoupSession *session, SoupMessage *req)
271 GMainContext *async_context =
272 soup_session_get_async_context (session);
274 /* Balance out the unref that final_finished will do */
277 queue_message (session, req, NULL, NULL);
279 while (soup_message_get_io_status (req) != SOUP_MESSAGE_IO_STATUS_FINISHED &&
280 !SOUP_STATUS_IS_TRANSPORT_ERROR (req->status_code))
281 g_main_context_iteration (async_context, TRUE);
283 return req->status_code;