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-address.h"
15 #include "soup-message-private.h"
16 #include "soup-misc.h"
19 * SECTION:soup-session-async
20 * @short_description: Soup session for asynchronous (main-loop-based) I/O.
22 * #SoupSessionAsync is an implementation of #SoupSession that uses
23 * non-blocking I/O via the glib main loop. It is intended for use in
24 * single-threaded programs.
27 static gboolean run_queue (SoupSessionAsync *sa);
28 static void do_idle_run_queue (SoupSession *session);
30 static void queue_message (SoupSession *session, SoupMessage *req,
31 SoupSessionCallback callback, gpointer user_data);
32 static guint send_message (SoupSession *session, SoupMessage *req);
34 G_DEFINE_TYPE (SoupSessionAsync, soup_session_async, SOUP_TYPE_SESSION)
37 GSource *idle_run_queue_source;
38 } SoupSessionAsyncPrivate;
39 #define SOUP_SESSION_ASYNC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION_ASYNC, SoupSessionAsyncPrivate))
42 soup_session_async_init (SoupSessionAsync *sa)
47 finalize (GObject *object)
49 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (object);
51 if (priv->idle_run_queue_source)
52 g_source_destroy (priv->idle_run_queue_source);
54 G_OBJECT_CLASS (soup_session_async_parent_class)->finalize (object);
58 soup_session_async_class_init (SoupSessionAsyncClass *soup_session_async_class)
60 SoupSessionClass *session_class = SOUP_SESSION_CLASS (soup_session_async_class);
61 GObjectClass *object_class = G_OBJECT_CLASS (session_class);
63 g_type_class_add_private (soup_session_async_class,
64 sizeof (SoupSessionAsyncPrivate));
66 /* virtual method override */
67 session_class->queue_message = queue_message;
68 session_class->send_message = send_message;
70 object_class->finalize = finalize;
75 * soup_session_async_new:
77 * Creates an asynchronous #SoupSession with the default options.
79 * Return value: the new session.
82 soup_session_async_new (void)
84 return g_object_new (SOUP_TYPE_SESSION_ASYNC, NULL);
88 * soup_session_async_new_with_options:
89 * @optname1: name of first property to set
90 * @...: value of @optname1, followed by additional property/value pairs
92 * Creates an asynchronous #SoupSession with the specified options.
94 * Return value: the new session.
97 soup_session_async_new_with_options (const char *optname1, ...)
102 va_start (ap, optname1);
103 session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION_ASYNC,
112 resolved_msg_addr (SoupAddress *addr, guint status, gpointer user_data)
114 SoupMessageQueueItem *item = user_data;
115 SoupSession *session = item->session;
118 /* Message was cancelled before its address resolved */
119 soup_message_queue_item_unref (item);
123 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
124 soup_session_cancel_message (session, item->msg, status);
125 soup_message_queue_item_unref (item);
129 item->msg_addr = g_object_ref (addr);
130 item->resolving_msg_addr = FALSE;
132 soup_message_queue_item_unref (item);
134 /* If we got here we know session still exists */
135 run_queue ((SoupSessionAsync *)session);
139 resolve_msg_addr (SoupMessageQueueItem *item)
141 if (item->resolving_msg_addr)
143 item->resolving_msg_addr = TRUE;
145 soup_message_queue_item_ref (item);
146 soup_address_resolve_async (soup_message_get_address (item->msg),
147 soup_session_get_async_context (item->session),
149 resolved_msg_addr, item);
153 connection_closed (SoupConnection *conn, gpointer session)
155 /* Run the queue in case anyone was waiting for a connection
158 do_idle_run_queue (session);
162 got_connection (SoupConnection *conn, guint status, gpointer user_data)
164 SoupSession *session = user_data;
166 if (status == SOUP_STATUS_OK) {
167 g_signal_connect (conn, "disconnected",
168 G_CALLBACK (connection_closed), session);
170 /* @conn has been marked reserved by SoupSession, but
171 * we don't actually have any specific message in mind
172 * for it. (In particular, the message we were
173 * originally planning to queue on it may have already
174 * been queued on some other connection that became
175 * available while we were waiting for this one to
176 * connect.) So we release the connection into the
177 * idle pool and then just run the queue and see what
180 soup_connection_release (conn);
183 /* Even if the connection failed, we run the queue, since
184 * there may have been messages waiting for the connection
187 do_idle_run_queue (session);
188 g_object_unref (session);
192 run_queue (SoupSessionAsync *sa)
194 SoupSession *session = SOUP_SESSION (sa);
195 SoupMessageQueue *queue = soup_session_get_queue (session);
196 SoupMessageQueueItem *item;
198 SoupConnection *conn;
199 gboolean try_pruning = TRUE, should_prune = FALSE;
200 gboolean started_any = FALSE, is_new;
202 /* FIXME: prefer CONNECTING messages */
205 for (item = soup_message_queue_first (queue);
206 item && !should_prune;
207 item = soup_message_queue_next (queue, item)) {
210 if (!SOUP_MESSAGE_IS_STARTING (msg) ||
211 soup_message_io_in_progress (msg))
214 if (!item->msg_addr) {
215 resolve_msg_addr (item);
219 conn = soup_session_get_connection (session, msg,
220 &should_prune, &is_new);
225 soup_connection_connect_async (conn, got_connection,
226 g_object_ref (session));
228 soup_connection_send_request (conn, msg);
231 soup_message_queue_item_unref (item);
233 if (try_pruning && should_prune) {
234 /* There is at least one message in the queue that
235 * could be sent if we pruned an idle connection from
238 if (soup_session_try_prune_connection (session)) {
239 try_pruning = should_prune = FALSE;
248 request_restarted (SoupMessage *req, gpointer user_data)
250 SoupMessageQueueItem *item = user_data;
252 if (item->msg_addr &&
253 item->msg_addr != soup_message_get_address (item->msg)) {
254 g_object_unref (item->msg_addr);
255 item->msg_addr = NULL;
258 run_queue ((SoupSessionAsync *)item->session);
262 final_finished (SoupMessage *req, gpointer user_data)
264 SoupMessageQueueItem *item = user_data;
265 SoupSession *session = item->session;
267 g_object_ref (session);
269 if (!SOUP_MESSAGE_IS_STARTING (req)) {
270 g_signal_handlers_disconnect_by_func (req, final_finished, item);
272 item->callback (session, req, item->callback_data);
274 g_object_unref (req);
275 soup_message_queue_item_unref (item);
278 do_idle_run_queue (session);
279 g_object_unref (session);
283 idle_run_queue (gpointer sa)
285 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (sa);
287 priv->idle_run_queue_source = NULL;
293 do_idle_run_queue (SoupSession *session)
295 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (session);
297 if (!priv->idle_run_queue_source) {
298 priv->idle_run_queue_source = soup_add_completion (
299 soup_session_get_async_context (session),
300 idle_run_queue, session);
305 queue_message (SoupSession *session, SoupMessage *req,
306 SoupSessionCallback callback, gpointer user_data)
308 SoupMessageQueueItem *item;
310 SOUP_SESSION_CLASS (soup_session_async_parent_class)->queue_message (session, req, callback, user_data);
312 item = soup_message_queue_lookup (soup_session_get_queue (session), req);
313 g_return_if_fail (item != NULL);
315 g_signal_connect (req, "restarted",
316 G_CALLBACK (request_restarted), item);
317 g_signal_connect_after (req, "finished",
318 G_CALLBACK (final_finished), item);
320 do_idle_run_queue (session);
324 send_message (SoupSession *session, SoupMessage *req)
326 GMainContext *async_context =
327 soup_session_get_async_context (session);
329 /* Balance out the unref that final_finished will do */
332 queue_message (session, req, NULL, NULL);
334 while (soup_message_get_io_status (req) != SOUP_MESSAGE_IO_STATUS_FINISHED &&
335 !SOUP_STATUS_IS_TRANSPORT_ERROR (req->status_code))
336 g_main_context_iteration (async_context, TRUE);
338 return req->status_code;