1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
5 * Copyright (C) 2000-2003, Ximian, Inc.
12 #define LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY
14 #include "soup-address.h"
15 #include "soup-session-sync.h"
16 #include "soup-session-private.h"
17 #include "soup-address.h"
18 #include "soup-message-private.h"
19 #include "soup-message-queue.h"
20 #include "soup-misc.h"
21 #include "soup-password-manager.h"
22 #include "soup-proxy-uri-resolver.h"
26 * SECTION:soup-session-sync
27 * @short_description: Soup session for blocking I/O in multithreaded
30 * #SoupSessionSync is an implementation of #SoupSession that uses
31 * synchronous I/O, intended for use in multi-threaded programs.
33 * You can use #SoupSessionSync from multiple threads concurrently.
34 * Eg, you can send a #SoupMessage in one thread, and then while
35 * waiting for the response, send another #SoupMessage from another
36 * thread. You can also send a message from one thread and then call
37 * soup_session_cancel_message() on it from any other thread (although
38 * you need to be careful to avoid race conditions, where the message
39 * finishes and is then unreffed by the sending thread just before you
42 * However, the majority of other types and methods in libsoup are not
43 * MT-safe. In particular, you <emphasis>cannot</emphasis> modify or
44 * examine a #SoupMessage while it is being transmitted by
45 * #SoupSessionSync in another thread. Once a message has been handed
46 * off to #SoupSessionSync, it can only be manipulated from its signal
47 * handler callbacks, until I/O is complete.
53 } SoupSessionSyncPrivate;
54 #define SOUP_SESSION_SYNC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION_SYNC, SoupSessionSyncPrivate))
56 static void queue_message (SoupSession *session, SoupMessage *msg,
57 SoupSessionCallback callback, gpointer user_data);
58 static guint send_message (SoupSession *session, SoupMessage *msg);
59 static void cancel_message (SoupSession *session, SoupMessage *msg,
61 static void auth_required (SoupSession *session, SoupMessage *msg,
62 SoupAuth *auth, gboolean retrying);
63 static void flush_queue (SoupSession *session);
65 G_DEFINE_TYPE (SoupSessionSync, soup_session_sync, SOUP_TYPE_SESSION)
68 soup_session_sync_init (SoupSessionSync *ss)
70 SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (ss);
72 priv->lock = g_mutex_new ();
73 priv->cond = g_cond_new ();
77 finalize (GObject *object)
79 SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (object);
81 g_mutex_free (priv->lock);
82 g_cond_free (priv->cond);
84 G_OBJECT_CLASS (soup_session_sync_parent_class)->finalize (object);
88 soup_session_sync_class_init (SoupSessionSyncClass *session_sync_class)
90 GObjectClass *object_class = G_OBJECT_CLASS (session_sync_class);
91 SoupSessionClass *session_class = SOUP_SESSION_CLASS (session_sync_class);
93 g_type_class_add_private (session_sync_class, sizeof (SoupSessionSyncPrivate));
95 /* virtual method override */
96 session_class->queue_message = queue_message;
97 session_class->send_message = send_message;
98 session_class->cancel_message = cancel_message;
99 session_class->auth_required = auth_required;
100 session_class->flush_queue = flush_queue;
102 object_class->finalize = finalize;
107 * soup_session_sync_new:
109 * Creates an synchronous #SoupSession with the default options.
111 * Return value: the new session.
114 soup_session_sync_new (void)
116 return g_object_new (SOUP_TYPE_SESSION_SYNC, NULL);
120 * soup_session_sync_new_with_options:
121 * @optname1: name of first property to set
122 * @...: value of @optname1, followed by additional property/value pairs
124 * Creates an synchronous #SoupSession with the specified options.
126 * Return value: the new session.
129 soup_session_sync_new_with_options (const char *optname1, ...)
131 SoupSession *session;
134 va_start (ap, optname1);
135 session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION_SYNC,
143 tunnel_connect (SoupSession *session, SoupMessageQueueItem *related)
145 SoupConnection *conn = related->conn;
146 SoupMessageQueueItem *item;
151 item = soup_session_make_connect_message (session, conn);
153 soup_session_send_queue_item (session, item, NULL);
154 status = item->msg->status_code;
155 if (item->state == SOUP_MESSAGE_RESTARTING &&
156 soup_connection_get_state (conn) != SOUP_CONNECTION_DISCONNECTED) {
157 item->state = SOUP_MESSAGE_STARTING;
158 soup_message_restarted (item->msg);
160 if (item->state == SOUP_MESSAGE_RESTARTING)
161 status = SOUP_STATUS_TRY_AGAIN;
162 item->state = SOUP_MESSAGE_FINISHED;
163 soup_message_finished (item->msg);
165 } while (item->state == SOUP_MESSAGE_STARTING);
166 soup_session_unqueue_item (session, item);
167 soup_message_queue_item_unref (item);
169 if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
170 if (!soup_connection_start_ssl_sync (conn, related->cancellable))
171 status = SOUP_STATUS_SSL_FAILED;
174 if (!SOUP_STATUS_IS_SUCCESSFUL (status))
175 soup_connection_disconnect (conn);
177 g_object_unref (conn);
182 get_connection (SoupMessageQueueItem *item)
184 SoupSession *session = item->session;
185 SoupMessage *msg = item->msg;
186 gboolean try_pruning = FALSE;
190 soup_session_cleanup_connections (session, FALSE);
192 if (!soup_session_get_connection (session, item, &try_pruning)) {
195 soup_session_cleanup_connections (session, TRUE);
196 if (!soup_session_get_connection (session, item, &try_pruning))
201 if (soup_connection_get_state (item->conn) != SOUP_CONNECTION_NEW) {
202 item->state = SOUP_MESSAGE_READY;
206 status = soup_connection_connect_sync (item->conn, item->cancellable);
207 if (status == SOUP_STATUS_TRY_AGAIN) {
208 soup_connection_disconnect (item->conn);
209 g_object_unref (item->conn);
214 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
215 if (!msg->status_code)
216 soup_session_set_item_status (session, item, status);
217 item->state = SOUP_MESSAGE_FINISHING;
218 soup_connection_disconnect (item->conn);
219 g_object_unref (item->conn);
224 if (soup_connection_get_tunnel_addr (item->conn)) {
225 status = tunnel_connect (session, item);
226 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
227 soup_connection_disconnect (item->conn);
228 g_object_unref (item->conn);
230 if (status == SOUP_STATUS_TRY_AGAIN)
232 soup_session_set_item_status (session, item, status);
233 item->state = SOUP_MESSAGE_FINISHING;
238 item->state = SOUP_MESSAGE_READY;
242 process_queue_item (SoupMessageQueueItem *item)
244 SoupSession *session = item->session;
245 SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session);
246 SoupMessage *msg = item->msg;
247 SoupProxyURIResolver *proxy_resolver;
250 item->state = SOUP_MESSAGE_STARTING;
252 switch (item->state) {
253 case SOUP_MESSAGE_STARTING:
254 proxy_resolver = (SoupProxyURIResolver *)soup_session_get_feature_for_message (session, SOUP_TYPE_PROXY_URI_RESOLVER, msg);
255 if (!proxy_resolver) {
256 item->state = SOUP_MESSAGE_AWAITING_CONNECTION;
260 status = soup_proxy_uri_resolver_get_proxy_uri_sync (
261 proxy_resolver, soup_message_get_uri (msg),
262 item->cancellable, &item->proxy_uri);
263 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
264 soup_session_set_item_status (session, item, status);
265 item->state = SOUP_MESSAGE_FINISHING;
268 if (!item->proxy_uri) {
269 item->state = SOUP_MESSAGE_AWAITING_CONNECTION;
273 item->proxy_addr = soup_address_new (
274 item->proxy_uri->host, item->proxy_uri->port);
275 status = soup_address_resolve_sync (item->proxy_addr,
277 if (SOUP_STATUS_IS_SUCCESSFUL (status))
278 item->state = SOUP_MESSAGE_AWAITING_CONNECTION;
280 soup_session_set_item_status (session, item, soup_status_proxify (status));
281 item->state = SOUP_MESSAGE_FINISHING;
285 case SOUP_MESSAGE_AWAITING_CONNECTION:
286 g_mutex_lock (priv->lock);
288 get_connection (item);
289 if (item->state == SOUP_MESSAGE_AWAITING_CONNECTION)
290 g_cond_wait (priv->cond, priv->lock);
291 } while (item->state == SOUP_MESSAGE_AWAITING_CONNECTION);
292 g_mutex_unlock (priv->lock);
295 case SOUP_MESSAGE_READY:
296 item->state = SOUP_MESSAGE_RUNNING;
297 soup_session_send_queue_item (item->session, item, NULL);
298 if (item->state != SOUP_MESSAGE_RESTARTING)
299 item->state = SOUP_MESSAGE_FINISHING;
302 case SOUP_MESSAGE_RESTARTING:
303 item->state = SOUP_MESSAGE_STARTING;
304 soup_message_restarted (item->msg);
307 case SOUP_MESSAGE_FINISHING:
308 item->state = SOUP_MESSAGE_FINISHED;
309 soup_message_finished (item->msg);
310 soup_session_unqueue_item (session, item);
311 g_cond_broadcast (priv->cond);
315 g_warn_if_reached ();
316 item->state = SOUP_MESSAGE_FINISHING;
319 } while (item->state != SOUP_MESSAGE_FINISHED);
323 queue_message_callback (gpointer data)
325 SoupMessageQueueItem *item = data;
327 item->callback (item->session, item->msg, item->callback_data);
328 g_object_unref (item->session);
329 g_object_unref (item->msg);
330 soup_message_queue_item_unref (item);
335 queue_message_thread (gpointer data)
337 SoupMessageQueueItem *item = data;
339 process_queue_item (item);
340 if (item->callback) {
341 soup_add_completion (soup_session_get_async_context (item->session),
342 queue_message_callback, item);
344 g_object_unref (item->session);
345 g_object_unref (item->msg);
346 soup_message_queue_item_unref (item);
353 queue_message (SoupSession *session, SoupMessage *msg,
354 SoupSessionCallback callback, gpointer user_data)
356 SoupMessageQueueItem *item;
358 SOUP_SESSION_CLASS (soup_session_sync_parent_class)->
359 queue_message (g_object_ref (session), msg, callback, user_data);
361 item = soup_message_queue_lookup (soup_session_get_queue (session), msg);
362 g_return_if_fail (item != NULL);
364 g_thread_create (queue_message_thread, item, FALSE, NULL);
368 send_message (SoupSession *session, SoupMessage *msg)
370 SoupMessageQueueItem *item;
373 SOUP_SESSION_CLASS (soup_session_sync_parent_class)->queue_message (session, msg, NULL, NULL);
375 item = soup_message_queue_lookup (soup_session_get_queue (session), msg);
376 g_return_val_if_fail (item != NULL, SOUP_STATUS_MALFORMED);
378 process_queue_item (item);
379 status = msg->status_code;
380 soup_message_queue_item_unref (item);
385 cancel_message (SoupSession *session, SoupMessage *msg, guint status_code)
387 SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session);
389 g_mutex_lock (priv->lock);
390 SOUP_SESSION_CLASS (soup_session_sync_parent_class)->cancel_message (session, msg, status_code);
391 g_cond_broadcast (priv->cond);
392 g_mutex_unlock (priv->lock);
396 auth_required (SoupSession *session, SoupMessage *msg,
397 SoupAuth *auth, gboolean retrying)
399 SoupSessionFeature *password_manager;
401 password_manager = soup_session_get_feature_for_message (
402 session, SOUP_TYPE_PASSWORD_MANAGER, msg);
403 if (password_manager) {
404 soup_password_manager_get_passwords_sync (
405 SOUP_PASSWORD_MANAGER (password_manager),
406 msg, auth, NULL); /* FIXME cancellable */
409 SOUP_SESSION_CLASS (soup_session_sync_parent_class)->
410 auth_required (session, msg, auth, retrying);
414 flush_queue (SoupSession *session)
416 SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session);
417 SoupMessageQueue *queue;
418 SoupMessageQueueItem *item;
420 gboolean done = FALSE;
422 /* Record the current contents of the queue */
423 current = g_hash_table_new (NULL, NULL);
424 queue = soup_session_get_queue (session);
425 for (item = soup_message_queue_first (queue);
427 item = soup_message_queue_next (queue, item))
428 g_hash_table_insert (current, item, item);
430 /* Cancel everything */
431 SOUP_SESSION_CLASS (soup_session_sync_parent_class)->flush_queue (session);
433 /* Wait until all of the items in @current have been removed
434 * from the queue. (This is not the same as "wait for the
435 * queue to be empty", because the app may queue new requests
436 * in response to the cancellation of the old ones. We don't
437 * try to cancel those requests as well, since we'd likely
438 * just end up looping forever.)
440 g_mutex_lock (priv->lock);
443 for (item = soup_message_queue_first (queue);
445 item = soup_message_queue_next (queue, item)) {
446 if (g_hash_table_lookup (current, item))
451 g_cond_wait (priv->cond, priv->lock);
453 g_mutex_unlock (priv->lock);
455 g_hash_table_destroy (current);