1 /* $Id: glibcurl.c,v 1.14 2004/12/05 16:15:12 atterer Exp $ -*- C -*-
3 |_) /| Copyright (C) 2004 | richard@
4 | \/¯| Richard Atterer | atterer.net
8 Permission to use, copy, modify, and distribute this software for any
9 purpose with or without fee is hereby granted, provided that the above
10 copyright notice and this permission notice appear in all copies.
12 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
15 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18 USE OR OTHER DEALINGS IN THE SOFTWARE.
20 Except as contained in this notice, the name of a copyright holder shall
21 not be used in advertising or otherwise to promote the sale, use or other
22 dealings in this Software without prior written authorization of the
34 /* #define D(_args) fprintf _args; */
39 /*______________________________________________________________________*/
41 /* Timeout for the fds passed to glib's poll() call, in millisecs.
42 curl_multi_fdset(3) says we should call curl_multi_perform() at regular
44 #define GLIBCURL_TIMEOUT 500
46 /* A structure which "derives" (in glib speak) from GSource */
47 typedef struct CurlGSource_ {
48 GSource source; /* First: The type we're deriving from */
51 GThread* selectThread;
52 GCond* cond; /* To signal selectThread => main thread: call perform() */
53 GMutex* mutex; /* Not held by selectThread whenever it is waiting */
55 gboolean callPerform; /* TRUE => Call curl_multi_perform() Real Soon */
57 gboolean selectRunning; /* FALSE => selectThread terminates */
59 /* For data returned by curl_multi_fdset */
67 /* Global state: Our CurlGSource object */
68 static CurlGSource* curlSrc = 0;
70 /* The "methods" of CurlGSource */
71 static gboolean prepare(GSource* source, gint* timeout);
72 static gboolean check(GSource* source);
73 static gboolean dispatch(GSource* source, GSourceFunc callback,
75 static void finalize(GSource* source);
77 static GSourceFuncs curlFuncs = {
78 &prepare, &check, &dispatch, &finalize, 0, 0
80 /*______________________________________________________________________*/
82 void glibcurl_init() {
83 /* Create source object for curl file descriptors, and hook it into the
84 default main context. */
85 GSource* src = g_source_new(&curlFuncs, sizeof(CurlGSource));
86 curlSrc = (CurlGSource*)src;
87 g_source_attach(&curlSrc->source, NULL);
89 if (!g_thread_supported()) g_thread_init(NULL);
91 /* Init rest of our data */
92 curlSrc->callPerform = 0;
93 curlSrc->selectThread = 0;
94 curlSrc->cond = g_cond_new();
95 curlSrc->mutex = g_mutex_new();
96 curlSrc->gtkBlockAndWait = 0;
99 curl_global_init(CURL_GLOBAL_ALL);
100 curlSrc->multiHandle = curl_multi_init();
102 /*______________________________________________________________________*/
104 void glibcurl_cleanup() {
105 D((stderr, "glibcurl_cleanup\n"));
106 /* You must call curl_multi_remove_handle() and curl_easy_cleanup() for all
107 requests before calling this. */
108 /* assert(curlSrc->callPerform == 0); */
110 /* All easy handles must be finished */
112 /* Lock before accessing selectRunning/selectThread */
113 g_mutex_lock(curlSrc->mutex);
114 curlSrc->selectRunning = FALSE;
115 while (curlSrc->selectThread != NULL) {
116 g_mutex_unlock(curlSrc->mutex);
118 g_cond_signal(curlSrc->cond); /* Make the select thread shut down */
120 g_mutex_lock(curlSrc->mutex); /* Wait until it has shut down */
122 g_mutex_unlock(curlSrc->mutex);
124 assert(curlSrc->selectThread == NULL);
126 g_cond_free(curlSrc->cond);
127 g_mutex_free(curlSrc->mutex);
129 curl_multi_cleanup(curlSrc->multiHandle);
130 curlSrc->multiHandle = 0;
131 curl_global_cleanup();
133 g_source_unref(&curlSrc->source);
136 /*______________________________________________________________________*/
138 CURLM* glibcurl_handle() {
139 return curlSrc->multiHandle;
141 /*______________________________________________________________________*/
143 CURLMcode glibcurl_add(CURL *easy_handle) {
144 assert(curlSrc != 0);
145 assert(curlSrc->multiHandle != 0);
147 return curl_multi_add_handle(curlSrc->multiHandle, easy_handle);
149 /*______________________________________________________________________*/
151 CURLMcode glibcurl_remove(CURL *easy_handle) {
152 D((stderr, "glibcurl_remove %p\n", easy_handle));
153 assert(curlSrc != 0);
154 assert(curlSrc->multiHandle != 0);
155 return curl_multi_remove_handle(curlSrc->multiHandle, easy_handle);
157 /*______________________________________________________________________*/
159 /* Call this whenever you have added a request using
160 curl_multi_add_handle(). */
161 void glibcurl_start() {
162 D((stderr, "glibcurl_start\n"));
163 curlSrc->callPerform = TRUE;
165 /*______________________________________________________________________*/
167 void glibcurl_set_callback(GlibcurlCallback function, void* data) {
168 g_source_set_callback(&curlSrc->source, (GSourceFunc)function, data,
171 /*______________________________________________________________________*/
173 static gpointer selectThread(gpointer data) {
175 struct timeval timeout;
176 assert(data == 0); /* Just to get rid of unused param warning */
178 D((stderr, "selectThread\n"));
179 g_mutex_lock(curlSrc->mutex);
180 D((stderr, "selectThread: got lock\n"));
182 curlSrc->selectRunning = TRUE;
183 while (curlSrc->selectRunning) {
185 FD_ZERO(&curlSrc->fdRead);
186 FD_ZERO(&curlSrc->fdWrite);
187 FD_ZERO(&curlSrc->fdExc);
189 /* What fds does libcurl want us to poll? */
190 curl_multi_fdset(curlSrc->multiHandle, &curlSrc->fdRead,
191 &curlSrc->fdWrite, &curlSrc->fdExc, &curlSrc->fdMax);
192 timeout.tv_sec = GLIBCURL_TIMEOUT / 1000;
193 timeout.tv_usec = (GLIBCURL_TIMEOUT % 1000) * 1000;
194 fdCount = select(curlSrc->fdMax + 1, &curlSrc->fdRead, &curlSrc->fdWrite,
195 &curlSrc->fdExc, &timeout);
196 D((stderr, "selectThread: select() fdCount=%d\n", fdCount));
198 g_atomic_int_inc(&curlSrc->gtkBlockAndWait); /* "GTK thread, block!" */
199 D((stderr, "selectThread: waking up GTK thread %d\n",
200 curlSrc->gtkBlockAndWait));
201 /* GTK thread will almost immediately block in prepare() */
202 g_main_context_wakeup(NULL);
204 /* Now unblock GTK thread, continue after it signals us */
205 D((stderr, "selectThread: pre-wait\n"));
206 g_cond_wait(curlSrc->cond, curlSrc->mutex);
207 D((stderr, "selectThread: post-wait\n"));
211 curlSrc->selectThread = NULL;
212 D((stderr, "selectThread: exit\n"));
213 g_mutex_unlock(curlSrc->mutex);
216 /*______________________________________________________________________*/
218 /* Returning FALSE may cause the main loop to block indefinitely, but that is
219 not a problem, we use g_main_context_wakeup to wake it up */
220 /* Returns TRUE iff it holds the mutex lock */
221 gboolean prepare(GSource* source, gint* timeout) {
222 assert(source == &curlSrc->source);
223 D((stderr, "prepare: callPerform=%d, thread=%p\n",
224 curlSrc->callPerform, curlSrc->selectThread));
228 if (g_atomic_int_dec_and_test(&curlSrc->gtkBlockAndWait)) {
229 /* The select thread wants us to block */
230 D((stderr, "prepare: trying lock\n"));
231 g_mutex_lock(curlSrc->mutex);
232 D((stderr, "prepare: got lock\n"));
235 g_atomic_int_inc(&curlSrc->gtkBlockAndWait);
238 /* Usual behaviour: Nothing happened, so don't dispatch. */
239 if (!curlSrc->callPerform) return FALSE;
241 /* Always dispatch if callPerform, i.e. 1st download just starting. */
242 D((stderr, "prepare: trying lock 2\n"));
243 /* Problem: We can block up to GLIBCURL_TIMEOUT msecs here, until the
244 select() call returns. However, under Win32 this does not appear to be a
245 problem (don't know why) - it _does_ tend to block the GTK thread under
247 g_mutex_lock(curlSrc->mutex);
248 D((stderr, "prepare: got lock 2\n"));
249 curlSrc->callPerform = FALSE;
250 if (curlSrc->selectThread == NULL) {
251 D((stderr, "prepare: starting select thread\n"));
252 /* Note that the thread will stop soon because we hold mutex */
253 curlSrc->selectThread = g_thread_create(&selectThread, 0, FALSE, NULL);
254 assert(curlSrc->selectThread != NULL);
258 /*______________________________________________________________________*/
260 /* Called after all the file descriptors are polled by glib. */
261 gboolean check(GSource* source) {
262 assert(source == &curlSrc->source);
265 /*______________________________________________________________________*/
267 gboolean dispatch(GSource* source, GSourceFunc callback,
268 gpointer user_data) {
272 assert(source == &curlSrc->source);
274 x = curl_multi_perform(curlSrc->multiHandle, &multiCount);
275 D((stderr, "dispatched: code=%d, reqs=%d\n", x, multiCount));
276 } while (x == CURLM_CALL_MULTI_PERFORM);
279 curlSrc->selectRunning = FALSE;
281 if (callback != 0) (*callback)(user_data);
283 /* Let selectThread call select() again */
284 g_cond_signal(curlSrc->cond);
285 g_mutex_unlock(curlSrc->mutex);
287 return TRUE; /* "Do not destroy me" */
289 /*______________________________________________________________________*/
291 void finalize(GSource* source) {
292 assert(source == &curlSrc->source);
294 /*======================================================================*/
296 #else /* !G_OS_WIN32 */
298 /* Number of highest allowed fd */
299 #define GLIBCURL_FDMAX 127
301 /* Timeout for the fds passed to glib's poll() call, in millisecs.
302 curl_multi_fdset(3) says we should call curl_multi_perform() at regular
304 #define GLIBCURL_TIMEOUT 1000
306 /* GIOCondition event masks */
307 #define GLIBCURL_READ (G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP)
308 #define GLIBCURL_WRITE (G_IO_OUT | G_IO_ERR | G_IO_HUP)
309 #define GLIBCURL_EXC (G_IO_ERR | G_IO_HUP)
311 /** A structure which "derives" (in glib speak) from GSource */
312 typedef struct CurlGSource_ {
313 GSource source; /* First: The type we're deriving from */
317 /* Previously seen FDs, for comparing with libcurl's current fd_sets */
318 GPollFD lastPollFd[GLIBCURL_FDMAX + 1];
319 int lastPollFdMax; /* Index of highest non-empty entry in lastPollFd */
321 int callPerform; /* Non-zero => curl_multi_perform() gets called */
323 /* For data returned by curl_multi_fdset */
331 /* Global state: Our CurlGSource object */
332 static CurlGSource* curlSrc = 0;
334 /* The "methods" of CurlGSource */
335 static gboolean prepare(GSource* source, gint* timeout);
336 static gboolean check(GSource* source);
337 static gboolean dispatch(GSource* source, GSourceFunc callback,
339 static void finalize(GSource* source);
341 static GSourceFuncs curlFuncs = {
342 &prepare, &check, &dispatch, &finalize, 0, 0
344 /*______________________________________________________________________*/
346 void glibcurl_init() {
348 /* Create source object for curl file descriptors, and hook it into the
349 default main context. */
350 curlSrc = (CurlGSource*)g_source_new(&curlFuncs, sizeof(CurlGSource));
351 g_source_attach(&curlSrc->source, NULL);
353 /* Init rest of our data */
354 memset(&curlSrc->lastPollFd, 0, sizeof(curlSrc->lastPollFd));
355 for (fd = 1; fd <= GLIBCURL_FDMAX; ++fd)
356 curlSrc->lastPollFd[fd].fd = fd;
357 curlSrc->lastPollFdMax = 0;
358 curlSrc->callPerform = 0;
361 curl_global_init(CURL_GLOBAL_ALL);
362 curlSrc->multiHandle = curl_multi_init();
364 D((stderr, "events: R=%x W=%x X=%x\n", GLIBCURL_READ, GLIBCURL_WRITE,
367 /*______________________________________________________________________*/
369 CURLM* glibcurl_handle() {
370 return curlSrc->multiHandle;
372 /*______________________________________________________________________*/
374 CURLMcode glibcurl_add(CURL *easy_handle) {
375 assert(curlSrc->multiHandle != 0);
376 curlSrc->callPerform = -1;
377 return curl_multi_add_handle(curlSrc->multiHandle, easy_handle);
379 /*______________________________________________________________________*/
381 CURLMcode glibcurl_remove(CURL *easy_handle) {
382 assert(curlSrc != 0);
383 assert(curlSrc->multiHandle != 0);
384 return curl_multi_remove_handle(curlSrc->multiHandle, easy_handle);
386 /*______________________________________________________________________*/
388 /* Call this whenever you have added a request using curl_multi_add_handle().
389 This is necessary to start new requests. It does so by triggering a call
390 to curl_multi_perform() even in the case where no open fds cause that
391 function to be called anyway. */
392 void glibcurl_start() {
393 curlSrc->callPerform = -1;
395 /*______________________________________________________________________*/
397 void glibcurl_set_callback(GlibcurlCallback function, void* data) {
398 g_source_set_callback(&curlSrc->source, (GSourceFunc)function, data,
401 /*______________________________________________________________________*/
403 void glibcurl_cleanup() {
404 /* You must call curl_multi_remove_handle() and curl_easy_cleanup() for all
405 requests before calling this. */
406 /* assert(curlSrc->callPerform == 0); */
408 curl_multi_cleanup(curlSrc->multiHandle);
409 curlSrc->multiHandle = 0;
410 curl_global_cleanup();
412 /* g_source_destroy(&curlSrc->source); */
413 g_source_unref(&curlSrc->source);
416 /*______________________________________________________________________*/
418 static void registerUnregisterFds() {
421 FD_ZERO(&curlSrc->fdRead);
422 FD_ZERO(&curlSrc->fdWrite);
423 FD_ZERO(&curlSrc->fdExc);
425 /* What fds does libcurl want us to poll? */
426 curl_multi_fdset(curlSrc->multiHandle, &curlSrc->fdRead,
427 &curlSrc->fdWrite, &curlSrc->fdExc, &curlSrc->fdMax);
428 /*fprintf(stderr, "registerUnregisterFds: fdMax=%d\n", curlSrc->fdMax);*/
429 assert(curlSrc->fdMax >= -1 && curlSrc->fdMax <= GLIBCURL_FDMAX);
431 fdMax = curlSrc->fdMax;
432 if (fdMax < curlSrc->lastPollFdMax) fdMax = curlSrc->lastPollFdMax;
434 /* Has the list of required events for any of the fds changed? */
435 for (fd = 0; fd <= fdMax; ++fd) {
437 if (FD_ISSET(fd, &curlSrc->fdRead)) events |= GLIBCURL_READ;
438 if (FD_ISSET(fd, &curlSrc->fdWrite)) events |= GLIBCURL_WRITE;
439 if (FD_ISSET(fd, &curlSrc->fdExc)) events |= GLIBCURL_EXC;
441 /* List of events unchanged => no (de)registering */
442 if (events == curlSrc->lastPollFd[fd].events) continue;
444 D((stderr, "registerUnregisterFds: fd %d: old events %x, "
445 "new events %x\n", fd, curlSrc->lastPollFd[fd].events, events));
447 /* fd is already a lastPollFd, but event type has changed => do nothing.
448 Due to the implementation of g_main_context_query(), the new event
449 flags will be picked up automatically. */
450 if (events != 0 && curlSrc->lastPollFd[fd].events != 0) {
451 curlSrc->lastPollFd[fd].events = events;
454 curlSrc->lastPollFd[fd].events = events;
456 /* Otherwise, (de)register as appropriate */
458 g_source_remove_poll(&curlSrc->source, &curlSrc->lastPollFd[fd]);
459 curlSrc->lastPollFd[fd].revents = 0;
460 D((stderr, "unregister fd %d\n", fd));
462 g_source_add_poll(&curlSrc->source, &curlSrc->lastPollFd[fd]);
463 D((stderr, "register fd %d\n", fd));
467 curlSrc->lastPollFdMax = curlSrc->fdMax;
470 /* Called before all the file descriptors are polled by the glib main loop.
471 We must have a look at all fds that libcurl wants polled. If any of them
472 are new/no longer needed, we have to (de)register them with glib. */
473 gboolean prepare(GSource* source, gint* timeout) {
474 D((stderr, "prepare\n"));
475 assert(source == &curlSrc->source);
477 if (curlSrc->multiHandle == 0) return FALSE;
479 registerUnregisterFds();
481 *timeout = GLIBCURL_TIMEOUT;
483 return curlSrc->callPerform == -1 ? TRUE : FALSE;
485 /*______________________________________________________________________*/
487 /* Called after all the file descriptors are polled by glib.
488 g_main_context_check() has copied back the revents fields (set by glib's
489 poll() call) to our GPollFD objects. How inefficient all that copying
490 is... let's add some more and copy the results of these revents into
491 libcurl's fd_sets! */
492 gboolean check(GSource* source) {
493 int fd, somethingHappened = 0;
495 if (curlSrc->multiHandle == 0) return FALSE;
497 assert(source == &curlSrc->source);
498 FD_ZERO(&curlSrc->fdRead);
499 FD_ZERO(&curlSrc->fdWrite);
500 FD_ZERO(&curlSrc->fdExc);
501 for (fd = 0; fd <= curlSrc->fdMax; ++fd) {
502 gushort revents = curlSrc->lastPollFd[fd].revents;
503 if (revents == 0) continue;
504 somethingHappened = 1;
505 /* D((stderr, "[fd%d] ", fd)); */
506 if (revents & (G_IO_IN | G_IO_PRI))
507 FD_SET((unsigned)fd, &curlSrc->fdRead);
508 if (revents & G_IO_OUT)
509 FD_SET((unsigned)fd, &curlSrc->fdWrite);
510 if (revents & (G_IO_ERR | G_IO_HUP))
511 FD_SET((unsigned)fd, &curlSrc->fdExc);
513 /* D((stderr, "check: fdMax %d\n", curlSrc->fdMax)); */
517 return curlSrc->callPerform == -1 || somethingHappened != 0 ? TRUE : FALSE;
519 /*______________________________________________________________________*/
521 gboolean dispatch(GSource* source, GSourceFunc callback,
522 gpointer user_data) {
525 assert(source == &curlSrc->source);
526 assert(curlSrc->multiHandle != 0);
528 x = curl_multi_perform(curlSrc->multiHandle, &curlSrc->callPerform);
529 /* D((stderr, "dispatched %d\n", x)); */
530 } while (x == CURLM_CALL_MULTI_PERFORM);
532 /* If no more calls to curl_multi_perform(), unregister left-over fds */
533 if (curlSrc->callPerform == 0) registerUnregisterFds();
535 if (callback != 0) (*callback)(user_data);
537 return TRUE; /* "Do not destroy me" */
539 /*______________________________________________________________________*/
541 void finalize(GSource* source) {
542 assert(source == &curlSrc->source);
543 registerUnregisterFds();