dbus-mainloop.c revision 7caf646fdf595946eb28202e2df3f847d28c7151
1/* -*- mode: C; c-file-style: "gnu" -*- */ 2/* dbus-mainloop.c Main loop utility 3 * 4 * Copyright (C) 2003 Red Hat, Inc. 5 * 6 * Licensed under the Academic Free License version 1.2 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 * 22 */ 23 24#include "dbus-mainloop.h" 25 26#include <dbus/dbus-list.h> 27#include <dbus/dbus-sysdeps.h> 28 29struct DBusLoop 30{ 31 int refcount; 32 DBusList *callbacks; 33 int callback_list_serial; 34 int watch_count; 35 int timeout_count; 36 int depth; /**< number of recursive runs */ 37 DBusList *need_dispatch; 38}; 39 40typedef enum 41{ 42 CALLBACK_WATCH, 43 CALLBACK_TIMEOUT 44} CallbackType; 45 46typedef struct 47{ 48 CallbackType type; 49 void *data; 50 DBusFreeFunction free_data_func; 51} Callback; 52 53typedef struct 54{ 55 Callback callback; 56 DBusWatchFunction function; 57 DBusWatch *watch; 58 /* last watch handle failed due to OOM */ 59 unsigned int last_iteration_oom : 1; 60} WatchCallback; 61 62typedef struct 63{ 64 Callback callback; 65 DBusTimeout *timeout; 66 DBusTimeoutFunction function; 67 unsigned long last_tv_sec; 68 unsigned long last_tv_usec; 69} TimeoutCallback; 70 71#define WATCH_CALLBACK(callback) ((WatchCallback*)callback) 72#define TIMEOUT_CALLBACK(callback) ((TimeoutCallback*)callback) 73 74static WatchCallback* 75watch_callback_new (DBusWatch *watch, 76 DBusWatchFunction function, 77 void *data, 78 DBusFreeFunction free_data_func) 79{ 80 WatchCallback *cb; 81 82 cb = dbus_new (WatchCallback, 1); 83 if (cb == NULL) 84 return NULL; 85 86 cb->watch = watch; 87 cb->function = function; 88 cb->last_iteration_oom = FALSE; 89 cb->callback.type = CALLBACK_WATCH; 90 cb->callback.data = data; 91 cb->callback.free_data_func = free_data_func; 92 93 return cb; 94} 95 96static TimeoutCallback* 97timeout_callback_new (DBusTimeout *timeout, 98 DBusTimeoutFunction function, 99 void *data, 100 DBusFreeFunction free_data_func) 101{ 102 TimeoutCallback *cb; 103 104 cb = dbus_new (TimeoutCallback, 1); 105 if (cb == NULL) 106 return NULL; 107 108 cb->timeout = timeout; 109 cb->function = function; 110 _dbus_get_current_time (&cb->last_tv_sec, 111 &cb->last_tv_usec); 112 cb->callback.type = CALLBACK_TIMEOUT; 113 cb->callback.data = data; 114 cb->callback.free_data_func = free_data_func; 115 116 return cb; 117} 118 119static void 120callback_free (Callback *cb) 121{ 122 if (cb->free_data_func) 123 (* cb->free_data_func) (cb->data); 124 125 dbus_free (cb); 126} 127 128static dbus_bool_t 129add_callback (DBusLoop *loop, 130 Callback *cb) 131{ 132 if (!_dbus_list_append (&loop->callbacks, cb)) 133 return FALSE; 134 135 loop->callback_list_serial += 1; 136 137 switch (cb->type) 138 { 139 case CALLBACK_WATCH: 140 loop->watch_count += 1; 141 break; 142 case CALLBACK_TIMEOUT: 143 loop->timeout_count += 1; 144 break; 145 } 146 147 return TRUE; 148} 149 150static void 151remove_callback (DBusLoop *loop, 152 DBusList *link) 153{ 154 Callback *cb = link->data; 155 156 switch (cb->type) 157 { 158 case CALLBACK_WATCH: 159 loop->watch_count -= 1; 160 break; 161 case CALLBACK_TIMEOUT: 162 loop->timeout_count -= 1; 163 break; 164 } 165 166 callback_free (cb); 167 _dbus_list_remove_link (&loop->callbacks, link); 168 loop->callback_list_serial += 1; 169} 170 171DBusLoop* 172_dbus_loop_new (void) 173{ 174 DBusLoop *loop; 175 176 loop = dbus_new0 (DBusLoop, 1); 177 if (loop == NULL) 178 return NULL; 179 180 loop->refcount = 1; 181 182 return loop; 183} 184 185void 186_dbus_loop_ref (DBusLoop *loop) 187{ 188 _dbus_assert (loop != NULL); 189 _dbus_assert (loop->refcount > 0); 190 191 loop->refcount += 1; 192} 193 194void 195_dbus_loop_unref (DBusLoop *loop) 196{ 197 _dbus_assert (loop != NULL); 198 _dbus_assert (loop->refcount > 0); 199 200 loop->refcount -= 1; 201 if (loop->refcount == 0) 202 { 203 while (loop->need_dispatch) 204 { 205 DBusConnection *connection = _dbus_list_pop_first (&loop->need_dispatch); 206 207 dbus_connection_unref (connection); 208 } 209 210 dbus_free (loop); 211 } 212} 213 214dbus_bool_t 215_dbus_loop_add_watch (DBusLoop *loop, 216 DBusWatch *watch, 217 DBusWatchFunction function, 218 void *data, 219 DBusFreeFunction free_data_func) 220{ 221 WatchCallback *wcb; 222 223 wcb = watch_callback_new (watch, function, data, free_data_func); 224 if (wcb == NULL) 225 return FALSE; 226 227 if (!add_callback (loop, (Callback*) wcb)) 228 { 229 wcb->callback.free_data_func = NULL; /* don't want to have this side effect */ 230 callback_free ((Callback*) wcb); 231 return FALSE; 232 } 233 234 return TRUE; 235} 236 237void 238_dbus_loop_remove_watch (DBusLoop *loop, 239 DBusWatch *watch, 240 DBusWatchFunction function, 241 void *data) 242{ 243 DBusList *link; 244 245 link = _dbus_list_get_first_link (&loop->callbacks); 246 while (link != NULL) 247 { 248 DBusList *next = _dbus_list_get_next_link (&loop->callbacks, link); 249 Callback *this = link->data; 250 251 if (this->type == CALLBACK_WATCH && 252 WATCH_CALLBACK (this)->watch == watch && 253 this->data == data && 254 WATCH_CALLBACK (this)->function == function) 255 { 256 remove_callback (loop, link); 257 258 return; 259 } 260 261 link = next; 262 } 263 264 _dbus_warn ("could not find watch %p function %p data %p to remove\n", 265 watch, function, data); 266} 267 268dbus_bool_t 269_dbus_loop_add_timeout (DBusLoop *loop, 270 DBusTimeout *timeout, 271 DBusTimeoutFunction function, 272 void *data, 273 DBusFreeFunction free_data_func) 274{ 275 TimeoutCallback *tcb; 276 277 tcb = timeout_callback_new (timeout, function, data, free_data_func); 278 if (tcb == NULL) 279 return FALSE; 280 281 if (!add_callback (loop, (Callback*) tcb)) 282 { 283 tcb->callback.free_data_func = NULL; /* don't want to have this side effect */ 284 callback_free ((Callback*) tcb); 285 return FALSE; 286 } 287 288 return TRUE; 289} 290 291void 292_dbus_loop_remove_timeout (DBusLoop *loop, 293 DBusTimeout *timeout, 294 DBusTimeoutFunction function, 295 void *data) 296{ 297 DBusList *link; 298 299 link = _dbus_list_get_first_link (&loop->callbacks); 300 while (link != NULL) 301 { 302 DBusList *next = _dbus_list_get_next_link (&loop->callbacks, link); 303 Callback *this = link->data; 304 305 if (this->type == CALLBACK_TIMEOUT && 306 TIMEOUT_CALLBACK (this)->timeout == timeout && 307 this->data == data && 308 TIMEOUT_CALLBACK (this)->function == function) 309 { 310 remove_callback (loop, link); 311 312 return; 313 } 314 315 link = next; 316 } 317 318 _dbus_warn ("could not find timeout %p function %p data %p to remove\n", 319 timeout, function, data); 320} 321 322/* Convolutions from GLib, there really must be a better way 323 * to do this. 324 */ 325static dbus_bool_t 326check_timeout (unsigned long tv_sec, 327 unsigned long tv_usec, 328 TimeoutCallback *tcb, 329 int *timeout) 330{ 331 long sec; 332 long msec; 333 unsigned long expiration_tv_sec; 334 unsigned long expiration_tv_usec; 335 long interval_seconds; 336 long interval_milliseconds; 337 int interval; 338 339 interval = dbus_timeout_get_interval (tcb->timeout); 340 341 interval_seconds = interval / 1000; 342 interval_milliseconds = interval - interval_seconds * 1000; 343 344 expiration_tv_sec = tcb->last_tv_sec + interval_seconds; 345 expiration_tv_usec = tcb->last_tv_usec + interval_milliseconds * 1000; 346 if (expiration_tv_usec >= 1000000) 347 { 348 expiration_tv_usec -= 1000000; 349 expiration_tv_sec += 1; 350 } 351 352 sec = expiration_tv_sec - tv_sec; 353 msec = (expiration_tv_usec - tv_usec) / 1000; 354 355#if 0 356 printf ("Interval is %ld seconds %ld msecs\n", 357 interval_seconds, 358 interval_milliseconds); 359 printf ("Now is %lu seconds %lu usecs\n", 360 tv_sec, tv_usec); 361 printf ("Exp is %lu seconds %lu usecs\n", 362 expiration_tv_sec, expiration_tv_usec); 363 printf ("Pre-correction, remaining sec %ld msec %ld\n", sec, msec); 364#endif 365 366 /* We do the following in a rather convoluted fashion to deal with 367 * the fact that we don't have an integral type big enough to hold 368 * the difference of two timevals in millseconds. 369 */ 370 if (sec < 0 || (sec == 0 && msec < 0)) 371 msec = 0; 372 else 373 { 374 if (msec < 0) 375 { 376 msec += 1000; 377 sec -= 1; 378 } 379 380 if (sec > interval_seconds || 381 (sec == interval_seconds && msec > interval_milliseconds)) 382 { 383 _dbus_verbose ("System clock went backward interval_seconds %ld interval_msecs %ld sec %ld msec %ld last_tv_sec %lu last_tv_usec %lu tv_sec %lu tv_usec %lu\n", 384 interval_seconds, interval_milliseconds, sec, msec, tcb->last_tv_sec, 385 tcb->last_tv_usec, tv_sec, tv_usec); 386 387 /* The system time has been set backwards, reset the timeout */ 388 389 tcb->last_tv_sec = tv_sec; 390 tcb->last_tv_usec = tv_usec; 391 392 msec = MIN (_DBUS_INT_MAX, interval); 393 } 394 else 395 { 396 msec = MIN (_DBUS_INT_MAX, (unsigned int)msec + 1000 * (unsigned int)sec); 397 } 398 } 399 400 *timeout = msec; 401 402#if 0 403 printf ("Timeout expires in %d milliseconds\n", *timeout); 404#endif 405 406 return msec == 0; 407} 408 409static void 410_dbus_loop_dispatch (DBusLoop *loop) 411{ 412 next: 413 while (loop->need_dispatch != NULL) 414 { 415 DBusConnection *connection = _dbus_list_pop_first (&loop->need_dispatch); 416 417 while (TRUE) 418 { 419 DBusDispatchStatus status; 420 421 status = dbus_connection_dispatch (connection); 422 423 if (status == DBUS_DISPATCH_COMPLETE) 424 { 425 dbus_connection_unref (connection); 426 goto next; 427 } 428 else 429 { 430 if (status == DBUS_DISPATCH_NEED_MEMORY) 431 _dbus_wait_for_memory (); 432 } 433 } 434 } 435} 436 437dbus_bool_t 438_dbus_loop_queue_dispatch (DBusLoop *loop, 439 DBusConnection *connection) 440{ 441 442 if (_dbus_list_append (&loop->need_dispatch, connection)) 443 { 444 dbus_connection_ref (connection); 445 return TRUE; 446 } 447 else 448 return FALSE; 449} 450 451/* Returns TRUE if we have any timeouts or ready file descriptors, 452 * which is just used in test code as a debug hack 453 */ 454 455dbus_bool_t 456_dbus_loop_iterate (DBusLoop *loop, 457 dbus_bool_t block) 458{ 459 dbus_bool_t retval; 460 DBusPollFD *fds; 461 int n_fds; 462 WatchCallback **watches_for_fds; 463 int i; 464 DBusList *link; 465 int n_ready; 466 int initial_serial; 467 long timeout; 468 dbus_bool_t oom_watch_pending; 469 int orig_depth; 470 471 retval = FALSE; 472 473 fds = NULL; 474 watches_for_fds = NULL; 475 oom_watch_pending = FALSE; 476 orig_depth = loop->depth; 477 478#if 0 479 _dbus_verbose (" iterate %d timeouts %d watches\n", 480 loop->timeout_count, loop->watch_count); 481#endif 482 483 if (loop->callbacks == NULL) 484 { 485 _dbus_loop_quit (loop); 486 goto next_iteration; 487 } 488 489 /* count enabled watches */ 490 n_fds = 0; 491 link = _dbus_list_get_first_link (&loop->callbacks); 492 while (link != NULL) 493 { 494 DBusList *next = _dbus_list_get_next_link (&loop->callbacks, link); 495 Callback *cb = link->data; 496 if (cb->type == CALLBACK_WATCH) 497 { 498 WatchCallback *wcb = WATCH_CALLBACK (cb); 499 500 if (!wcb->last_iteration_oom && 501 dbus_watch_get_enabled (wcb->watch)) 502 ++n_fds; 503 } 504 505 link = next; 506 } 507 508 /* fill our array of fds and watches */ 509 if (n_fds > 0) 510 { 511 fds = dbus_new0 (DBusPollFD, n_fds); 512 while (fds == NULL) 513 { 514 _dbus_wait_for_memory (); 515 fds = dbus_new0 (DBusPollFD, n_fds); 516 } 517 518 watches_for_fds = dbus_new (WatchCallback*, n_fds); 519 while (watches_for_fds == NULL) 520 { 521 _dbus_wait_for_memory (); 522 watches_for_fds = dbus_new (WatchCallback*, n_fds); 523 } 524 525 i = 0; 526 link = _dbus_list_get_first_link (&loop->callbacks); 527 while (link != NULL) 528 { 529 DBusList *next = _dbus_list_get_next_link (&loop->callbacks, link); 530 Callback *cb = link->data; 531 if (cb->type == CALLBACK_WATCH) 532 { 533 unsigned int flags; 534 WatchCallback *wcb = WATCH_CALLBACK (cb); 535 536 if (wcb->last_iteration_oom) 537 { 538 /* we skip this one this time, but reenable it next time, 539 * and have a timeout on this iteration 540 */ 541 wcb->last_iteration_oom = FALSE; 542 oom_watch_pending = TRUE; 543 } 544 else if (dbus_watch_get_enabled (wcb->watch)) 545 { 546 watches_for_fds[i] = wcb; 547 548 flags = dbus_watch_get_flags (wcb->watch); 549 550 fds[i].fd = dbus_watch_get_fd (wcb->watch); 551 if (flags & DBUS_WATCH_READABLE) 552 fds[i].events |= _DBUS_POLLIN; 553 if (flags & DBUS_WATCH_WRITABLE) 554 fds[i].events |= _DBUS_POLLOUT; 555 556 ++i; 557 } 558 } 559 560 link = next; 561 } 562 563 _dbus_assert (i == n_fds); 564 } 565 566 timeout = -1; 567 if (loop->timeout_count > 0) 568 { 569 unsigned long tv_sec; 570 unsigned long tv_usec; 571 572 retval = TRUE; 573 574 _dbus_get_current_time (&tv_sec, &tv_usec); 575 576 link = _dbus_list_get_first_link (&loop->callbacks); 577 while (link != NULL) 578 { 579 DBusList *next = _dbus_list_get_next_link (&loop->callbacks, link); 580 Callback *cb = link->data; 581 582 if (cb->type == CALLBACK_TIMEOUT && 583 dbus_timeout_get_enabled (TIMEOUT_CALLBACK (cb)->timeout)) 584 { 585 TimeoutCallback *tcb = TIMEOUT_CALLBACK (cb); 586 int msecs_remaining; 587 588 check_timeout (tv_sec, tv_usec, tcb, &msecs_remaining); 589 590 if (timeout < 0) 591 timeout = msecs_remaining; 592 else 593 timeout = MIN (msecs_remaining, timeout); 594 595 _dbus_assert (timeout >= 0); 596 597 if (timeout == 0) 598 break; /* it's not going to get shorter... */ 599 } 600 601 link = next; 602 } 603 } 604 605 /* Never block if we have stuff to dispatch */ 606 if (!block || loop->need_dispatch != NULL) 607 { 608 timeout = 0; 609#if 0 610 printf ("timeout is 0 as we aren't blocking\n"); 611#endif 612 } 613 614 /* if a watch is OOM, don't wait longer than the OOM 615 * wait to re-enable it 616 */ 617 if (oom_watch_pending) 618 timeout = MIN (timeout, _dbus_get_oom_wait ()); 619 620 n_ready = _dbus_poll (fds, n_fds, timeout); 621 622 initial_serial = loop->callback_list_serial; 623 624 if (loop->timeout_count > 0) 625 { 626 unsigned long tv_sec; 627 unsigned long tv_usec; 628 629 _dbus_get_current_time (&tv_sec, &tv_usec); 630 631 /* It'd be nice to avoid this O(n) thingy here */ 632 link = _dbus_list_get_first_link (&loop->callbacks); 633 while (link != NULL) 634 { 635 DBusList *next = _dbus_list_get_next_link (&loop->callbacks, link); 636 Callback *cb = link->data; 637 638 if (initial_serial != loop->callback_list_serial) 639 goto next_iteration; 640 641 if (loop->depth != orig_depth) 642 goto next_iteration; 643 644 if (cb->type == CALLBACK_TIMEOUT && 645 dbus_timeout_get_enabled (TIMEOUT_CALLBACK (cb)->timeout)) 646 { 647 TimeoutCallback *tcb = TIMEOUT_CALLBACK (cb); 648 int msecs_remaining; 649 650 if (check_timeout (tv_sec, tv_usec, 651 tcb, &msecs_remaining)) 652 { 653 /* Save last callback time and fire this timeout */ 654 tcb->last_tv_sec = tv_sec; 655 tcb->last_tv_usec = tv_usec; 656 657#if 0 658 printf (" invoking timeout\n"); 659#endif 660 661 (* tcb->function) (tcb->timeout, 662 cb->data); 663 } 664 } 665 666 link = next; 667 } 668 } 669 670 if (n_ready > 0) 671 { 672 i = 0; 673 while (i < n_fds) 674 { 675 /* FIXME I think this "restart if we change the watches" 676 * approach could result in starving watches 677 * toward the end of the list. 678 */ 679 if (initial_serial != loop->callback_list_serial) 680 goto next_iteration; 681 682 if (loop->depth != orig_depth) 683 goto next_iteration; 684 685 if (fds[i].revents != 0) 686 { 687 WatchCallback *wcb; 688 unsigned int condition; 689 690 wcb = watches_for_fds[i]; 691 692 condition = 0; 693 if (fds[i].revents & _DBUS_POLLIN) 694 condition |= DBUS_WATCH_READABLE; 695 if (fds[i].revents & _DBUS_POLLOUT) 696 condition |= DBUS_WATCH_WRITABLE; 697 if (fds[i].revents & _DBUS_POLLHUP) 698 condition |= DBUS_WATCH_HANGUP; 699 if (fds[i].revents & _DBUS_POLLERR) 700 condition |= DBUS_WATCH_ERROR; 701 702 /* condition may still be 0 if we got some 703 * weird POLLFOO thing like POLLWRBAND 704 */ 705 706 if (condition != 0 && 707 dbus_watch_get_enabled (wcb->watch)) 708 { 709 if (!(* wcb->function) (wcb->watch, 710 condition, 711 ((Callback*)wcb)->data)) 712 wcb->last_iteration_oom = TRUE; 713 714 retval = TRUE; 715 } 716 } 717 718 ++i; 719 } 720 } 721 722 next_iteration: 723 dbus_free (fds); 724 dbus_free (watches_for_fds); 725 726 if (loop->need_dispatch != NULL) 727 { 728 retval = TRUE; 729 _dbus_loop_dispatch (loop); 730 } 731 732 return retval; 733} 734 735void 736_dbus_loop_run (DBusLoop *loop) 737{ 738 int our_exit_depth; 739 740 _dbus_loop_ref (loop); 741 742 our_exit_depth = loop->depth; 743 loop->depth += 1; 744 745 while (loop->depth != our_exit_depth) 746 _dbus_loop_iterate (loop, TRUE); 747 748 _dbus_loop_unref (loop); 749} 750 751void 752_dbus_loop_quit (DBusLoop *loop) 753{ 754 _dbus_assert (loop->depth > 0); 755 756 loop->depth -= 1; 757} 758 759int 760_dbus_get_oom_wait (void) 761{ 762#ifdef DBUS_BUILD_TESTS 763 /* make tests go fast */ 764 return 0; 765#else 766 return 500; 767#endif 768} 769 770void 771_dbus_wait_for_memory (void) 772{ 773 _dbus_sleep_milliseconds (_dbus_get_oom_wait ()); 774} 775 776