Bug 574957 - soup-session-sync doesn't unlock mutex on proxy error
[platform/upstream/libsoup.git] / libsoup / soup-session-sync.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-session-sync.c
4  *
5  * Copyright (C) 2000-2003, Ximian, Inc.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include "soup-address.h"
13 #include "soup-session-sync.h"
14 #include "soup-session-private.h"
15 #include "soup-address.h"
16 #include "soup-message-private.h"
17 #include "soup-misc.h"
18
19 /**
20  * SECTION:soup-session-sync
21  * @short_description: Soup session for blocking I/O in multithreaded
22  * programs.
23  *
24  * #SoupSessionSync is an implementation of #SoupSession that uses
25  * synchronous I/O, intended for use in multi-threaded programs.
26  *
27  * You can use #SoupSessionSync from multiple threads concurrently.
28  * Eg, you can send a #SoupMessage in one thread, and then while
29  * waiting for the response, send another #SoupMessage from another
30  * thread. You can also send a message from one thread and then call
31  * soup_session_cancel_message() on it from any other thread (although
32  * you need to be careful to avoid race conditions, where the message
33  * finishes and is then unreffed by the sending thread just before you
34  * cancel it).
35  *
36  * However, the majority of other types and methods in libsoup are not
37  * MT-safe. In particular, you <emphasis>cannot</emphasis> modify or
38  * examine a #SoupMessage while it is being transmitted by
39  * #SoupSessionSync in another thread. Once a message has been handed
40  * off to #SoupSessionSync, it can only be manipulated from its signal
41  * handler callbacks, until I/O is complete.
42  **/
43
44 typedef struct {
45         GMutex *lock;
46         GCond *cond;
47 } SoupSessionSyncPrivate;
48 #define SOUP_SESSION_SYNC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION_SYNC, SoupSessionSyncPrivate))
49
50 static void  queue_message  (SoupSession *session, SoupMessage *msg,
51                              SoupSessionCallback callback, gpointer user_data);
52 static guint send_message   (SoupSession *session, SoupMessage *msg);
53 static void  cancel_message (SoupSession *session, SoupMessage *msg,
54                              guint status_code);
55
56 G_DEFINE_TYPE (SoupSessionSync, soup_session_sync, SOUP_TYPE_SESSION)
57
58 static void
59 soup_session_sync_init (SoupSessionSync *ss)
60 {
61         SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (ss);
62
63         priv->lock = g_mutex_new ();
64         priv->cond = g_cond_new ();
65 }
66
67 static void
68 finalize (GObject *object)
69 {
70         SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (object);
71
72         g_mutex_free (priv->lock);
73         g_cond_free (priv->cond);
74
75         G_OBJECT_CLASS (soup_session_sync_parent_class)->finalize (object);
76 }
77
78 static void
79 soup_session_sync_class_init (SoupSessionSyncClass *session_sync_class)
80 {
81         GObjectClass *object_class = G_OBJECT_CLASS (session_sync_class);
82         SoupSessionClass *session_class = SOUP_SESSION_CLASS (session_sync_class);
83
84         g_type_class_add_private (session_sync_class, sizeof (SoupSessionSyncPrivate));
85
86         /* virtual method override */
87         session_class->queue_message = queue_message;
88         session_class->send_message = send_message;
89         session_class->cancel_message = cancel_message;
90         object_class->finalize = finalize;
91 }
92
93
94 /**
95  * soup_session_sync_new:
96  *
97  * Creates an synchronous #SoupSession with the default options.
98  *
99  * Return value: the new session.
100  **/
101 SoupSession *
102 soup_session_sync_new (void)
103 {
104         return g_object_new (SOUP_TYPE_SESSION_SYNC, NULL);
105 }
106
107 /**
108  * soup_session_sync_new_with_options:
109  * @optname1: name of first property to set
110  * @...: value of @optname1, followed by additional property/value pairs
111  *
112  * Creates an synchronous #SoupSession with the specified options.
113  *
114  * Return value: the new session.
115  **/
116 SoupSession *
117 soup_session_sync_new_with_options (const char *optname1, ...)
118 {
119         SoupSession *session;
120         va_list ap;
121
122         va_start (ap, optname1);
123         session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION_SYNC,
124                                                       optname1, ap);
125         va_end (ap);
126
127         return session;
128 }
129
130 static SoupConnection *
131 wait_for_connection (SoupSession *session, SoupMessage *msg)
132 {
133         SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session);
134         gboolean try_pruning = FALSE, is_new = FALSE;
135         SoupProxyResolver *proxy_resolver;
136         SoupAddress *proxy_addr = NULL;
137         SoupConnection *conn;
138         guint status;
139
140         proxy_resolver = soup_session_get_proxy_resolver (session);
141         g_mutex_lock (priv->lock);
142
143  try_again:
144         if (proxy_resolver) {
145                 status = soup_proxy_resolver_get_proxy_sync (proxy_resolver, msg, NULL, &proxy_addr);
146                 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
147                         g_mutex_unlock (priv->lock);
148                         soup_session_cancel_message (session, msg, status);
149                         return NULL;
150                 }
151         }
152
153         conn = soup_session_get_connection (session, msg, proxy_addr,
154                                             &try_pruning, &is_new);
155         if (proxy_addr)
156                 g_object_unref (proxy_addr);
157         if (conn) {
158                 if (is_new) {
159                         status = soup_connection_connect_sync (conn);
160
161                         /* If the connection attempt fails, SoupSession
162                          * will notice, unref conn, and set an error
163                          * status on msg. So all we need to do is just
164                          * not return the no-longer-valid connection.
165                          */
166
167                         if (status == SOUP_STATUS_TRY_AGAIN)
168                                 goto try_again;
169                         else if (!SOUP_STATUS_IS_SUCCESSFUL (status))
170                                 conn = NULL;
171                         else if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_FINISHED) {
172                                 /* Message was cancelled while we were
173                                  * connecting.
174                                  */
175                                 soup_connection_disconnect (conn);
176                                 conn = NULL;
177                         }
178                 }
179
180                 g_mutex_unlock (priv->lock);
181                 return conn;
182         }
183
184         if (try_pruning && soup_session_try_prune_connection (session))
185                 goto try_again;
186
187         /* Wait... */
188         g_cond_wait (priv->cond, priv->lock);
189
190         /* See if something bad happened */
191         if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_FINISHED) {
192                 g_mutex_unlock (priv->lock);
193                 return NULL;
194         }
195
196         goto try_again;
197 }
198
199 static void
200 process_queue_item (SoupMessageQueueItem *item)
201 {
202         SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (item->session);
203         SoupMessage *msg = item->msg;
204         SoupConnection *conn;
205         SoupAddress *addr;
206         guint status;
207
208         do {
209                 /* Resolve address */
210                 addr = soup_message_get_address (msg);
211                 status = soup_address_resolve_sync (addr, item->cancellable);
212                 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
213                         if (status != SOUP_STATUS_CANCELLED)
214                                 soup_session_cancel_message (item->session, msg, status);
215                         break;
216                 }
217
218                 /* Get a connection */
219                 conn = wait_for_connection (item->session, msg);
220                 if (!conn)
221                         break;
222
223                 soup_connection_send_request (conn, msg);
224                 g_cond_broadcast (priv->cond);
225         } while (soup_message_get_io_status (msg) != SOUP_MESSAGE_IO_STATUS_FINISHED);
226 }
227
228 static gboolean
229 queue_message_callback (gpointer data)
230 {
231         SoupMessageQueueItem *item = data;
232
233         item->callback (item->session, item->msg, item->callback_data);
234         g_object_unref (item->session);
235         soup_message_queue_item_unref (item);
236         return FALSE;
237 }
238
239 static gpointer
240 queue_message_thread (gpointer data)
241 {
242         SoupMessageQueueItem *item = data;
243
244         process_queue_item (item);
245         if (item->callback) {
246                 soup_add_completion (soup_session_get_async_context (item->session),
247                                      queue_message_callback, item);
248         } else {
249                 g_object_unref (item->session);
250                 soup_message_queue_item_unref (item);
251         }
252
253         return NULL;
254 }
255
256 static void
257 queue_message (SoupSession *session, SoupMessage *msg,
258                SoupSessionCallback callback, gpointer user_data)
259 {
260         SoupMessageQueueItem *item;
261
262         SOUP_SESSION_CLASS (soup_session_sync_parent_class)->
263                 queue_message (g_object_ref (session), msg, callback, user_data);
264
265         item = soup_message_queue_lookup (soup_session_get_queue (session), msg);
266         g_return_if_fail (item != NULL);
267
268         g_thread_create (queue_message_thread, item, FALSE, NULL);
269 }
270
271 static guint
272 send_message (SoupSession *session, SoupMessage *msg)
273 {
274         SoupMessageQueueItem *item;
275         guint status;
276
277         SOUP_SESSION_CLASS (soup_session_sync_parent_class)->queue_message (session, msg, NULL, NULL);
278
279         item = soup_message_queue_lookup (soup_session_get_queue (session), msg);
280         g_return_val_if_fail (item != NULL, SOUP_STATUS_MALFORMED);
281
282         process_queue_item (item);
283         status = msg->status_code;
284         soup_message_queue_item_unref (item);
285         return status;
286 }
287
288 static void
289 cancel_message (SoupSession *session, SoupMessage *msg, guint status_code)
290 {
291         SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session);
292
293         SOUP_SESSION_CLASS (soup_session_sync_parent_class)->cancel_message (session, msg, status_code);
294         g_cond_broadcast (priv->cond);
295 }
296