Annotation of embedaddon/bird/proto/bfd/io.c, revision 1.1.1.1

1.1       misho       1: /*
                      2:  *     BIRD -- I/O and event loop
                      3:  *
                      4:  *     Can be freely distributed and used under the terms of the GNU GPL.
                      5:  */
                      6: 
                      7: #include <stdio.h>
                      8: #include <stdlib.h>
                      9: #include <unistd.h>
                     10: #include <errno.h>
                     11: #include <fcntl.h>
                     12: #include <poll.h>
                     13: #include <pthread.h>
                     14: #include <time.h>
                     15: #include <sys/time.h>
                     16: 
                     17: #include "nest/bird.h"
                     18: #include "proto/bfd/io.h"
                     19: 
                     20: #include "lib/buffer.h"
                     21: #include "lib/heap.h"
                     22: #include "lib/lists.h"
                     23: #include "lib/resource.h"
                     24: #include "lib/event.h"
                     25: #include "lib/socket.h"
                     26: 
                     27: 
                     28: struct birdloop
                     29: {
                     30:   pool *pool;
                     31:   pthread_t thread;
                     32:   pthread_mutex_t mutex;
                     33: 
                     34:   btime last_time;
                     35:   btime real_time;
                     36:   u8 use_monotonic_clock;
                     37: 
                     38:   u8 stop_called;
                     39:   u8 poll_active;
                     40:   u8 wakeup_masked;
                     41:   int wakeup_fds[2];
                     42: 
                     43:   BUFFER(timer2 *) timers;
                     44:   list event_list;
                     45:   list sock_list;
                     46:   uint sock_num;
                     47: 
                     48:   BUFFER(sock *) poll_sk;
                     49:   BUFFER(struct pollfd) poll_fd;
                     50:   u8 poll_changed;
                     51:   u8 close_scheduled;
                     52: };
                     53: 
                     54: 
                     55: /*
                     56:  *     Current thread context
                     57:  */
                     58: 
                     59: static pthread_key_t current_loop_key;
                     60: 
                     61: static inline struct birdloop *
                     62: birdloop_current(void)
                     63: {
                     64:   return pthread_getspecific(current_loop_key);
                     65: }
                     66: 
                     67: static inline void
                     68: birdloop_set_current(struct birdloop *loop)
                     69: {
                     70:   pthread_setspecific(current_loop_key, loop);
                     71: }
                     72: 
                     73: static inline void
                     74: birdloop_init_current(void)
                     75: {
                     76:   pthread_key_create(&current_loop_key, NULL);
                     77: }
                     78: 
                     79: 
                     80: /*
                     81:  *     Time clock
                     82:  */
                     83: 
                     84: static void times_update_alt(struct birdloop *loop);
                     85: 
                     86: static void
                     87: times_init(struct birdloop *loop)
                     88: {
                     89:   struct timespec ts;
                     90:   int rv;
                     91: 
                     92:   rv = clock_gettime(CLOCK_MONOTONIC, &ts);
                     93:   if (rv < 0)
                     94:   {
                     95:     log(L_WARN "Monotonic clock is missing");
                     96: 
                     97:     loop->use_monotonic_clock = 0;
                     98:     loop->last_time = 0;
                     99:     loop->real_time = 0;
                    100:     times_update_alt(loop);
                    101:     return;
                    102:   }
                    103: 
                    104:   if ((ts.tv_sec < 0) || (((s64) ts.tv_sec) > ((s64) 1 << 40)))
                    105:     log(L_WARN "Monotonic clock is crazy");
                    106: 
                    107:   loop->use_monotonic_clock = 1;
                    108:   loop->last_time = ((s64) ts.tv_sec S) + (ts.tv_nsec / 1000);
                    109:   loop->real_time = 0;
                    110: }
                    111: 
                    112: static void
                    113: times_update_pri(struct birdloop *loop)
                    114: {
                    115:   struct timespec ts;
                    116:   int rv;
                    117: 
                    118:   rv = clock_gettime(CLOCK_MONOTONIC, &ts);
                    119:   if (rv < 0)
                    120:     die("clock_gettime: %m");
                    121: 
                    122:   btime new_time = ((s64) ts.tv_sec S) + (ts.tv_nsec / 1000);
                    123: 
                    124:   if (new_time < loop->last_time)
                    125:     log(L_ERR "Monotonic clock is broken");
                    126: 
                    127:   loop->last_time = new_time;
                    128:   loop->real_time = 0;
                    129: }
                    130: 
                    131: static void
                    132: times_update_alt(struct birdloop *loop)
                    133: {
                    134:   struct timeval tv;
                    135:   int rv;
                    136: 
                    137:   rv = gettimeofday(&tv, NULL);
                    138:   if (rv < 0)
                    139:     die("gettimeofday: %m");
                    140: 
                    141:   btime new_time = ((s64) tv.tv_sec S) + tv.tv_usec;
                    142:   btime delta = new_time - loop->real_time;
                    143: 
                    144:   if ((delta < 0) || (delta > (60 S)))
                    145:   {
                    146:     if (loop->real_time)
                    147:       log(L_WARN "Time jump, delta %d us", (int) delta);
                    148: 
                    149:     delta = 100 MS;
                    150:   }
                    151: 
                    152:   loop->last_time += delta;
                    153:   loop->real_time = new_time;
                    154: }
                    155: 
                    156: static void
                    157: times_update(struct birdloop *loop)
                    158: {
                    159:   if (loop->use_monotonic_clock)
                    160:     times_update_pri(loop);
                    161:   else
                    162:     times_update_alt(loop);
                    163: }
                    164: 
                    165: btime
                    166: current_time(void)
                    167: {
                    168:   return birdloop_current()->last_time;
                    169: }
                    170: 
                    171: 
                    172: /*
                    173:  *     Wakeup code for birdloop
                    174:  */
                    175: 
                    176: static void
                    177: pipe_new(int *pfds)
                    178: {
                    179:   int rv = pipe(pfds);
                    180:   if (rv < 0)
                    181:     die("pipe: %m");
                    182: 
                    183:   if (fcntl(pfds[0], F_SETFL, O_NONBLOCK) < 0)
                    184:     die("fcntl(O_NONBLOCK): %m");
                    185: 
                    186:   if (fcntl(pfds[1], F_SETFL, O_NONBLOCK) < 0)
                    187:     die("fcntl(O_NONBLOCK): %m");
                    188: }
                    189: 
                    190: void
                    191: pipe_drain(int fd)
                    192: {
                    193:   char buf[64];
                    194:   int rv;
                    195:   
                    196:  try:
                    197:   rv = read(fd, buf, 64);
                    198:   if (rv < 0)
                    199:   {
                    200:     if (errno == EINTR)
                    201:       goto try;
                    202:     if (errno == EAGAIN)
                    203:       return;
                    204:     die("wakeup read: %m");
                    205:   }
                    206:   if (rv == 64)
                    207:     goto try;
                    208: }
                    209: 
                    210: void
                    211: pipe_kick(int fd)
                    212: {
                    213:   u64 v = 1;
                    214:   int rv;
                    215: 
                    216:  try:
                    217:   rv = write(fd, &v, sizeof(u64));
                    218:   if (rv < 0)
                    219:   {
                    220:     if (errno == EINTR)
                    221:       goto try;
                    222:     if (errno == EAGAIN)
                    223:       return;
                    224:     die("wakeup write: %m");
                    225:   }
                    226: }
                    227: 
                    228: static inline void
                    229: wakeup_init(struct birdloop *loop)
                    230: {
                    231:   pipe_new(loop->wakeup_fds);
                    232: }
                    233: 
                    234: static inline void
                    235: wakeup_drain(struct birdloop *loop)
                    236: {
                    237:   pipe_drain(loop->wakeup_fds[0]);
                    238: }
                    239: 
                    240: static inline void
                    241: wakeup_do_kick(struct birdloop *loop) 
                    242: {
                    243:   pipe_kick(loop->wakeup_fds[1]);
                    244: }
                    245: 
                    246: static inline void
                    247: wakeup_kick(struct birdloop *loop)
                    248: {
                    249:   if (!loop->wakeup_masked)
                    250:     wakeup_do_kick(loop);
                    251:   else
                    252:     loop->wakeup_masked = 2;
                    253: }
                    254: 
                    255: 
                    256: /*
                    257:  *     Events
                    258:  */
                    259: 
                    260: static inline uint
                    261: events_waiting(struct birdloop *loop)
                    262: {
                    263:   return !EMPTY_LIST(loop->event_list);
                    264: }
                    265: 
                    266: static inline void
                    267: events_init(struct birdloop *loop)
                    268: {
                    269:   init_list(&loop->event_list);
                    270: }
                    271: 
                    272: static void
                    273: events_fire(struct birdloop *loop)
                    274: {
                    275:   times_update(loop);
                    276:   ev_run_list(&loop->event_list);
                    277: }
                    278: 
                    279: void
                    280: ev2_schedule(event *e)
                    281: {
                    282:   struct birdloop *loop = birdloop_current();
                    283: 
                    284:   if (loop->poll_active && EMPTY_LIST(loop->event_list))
                    285:     wakeup_kick(loop);
                    286: 
                    287:   if (e->n.next)
                    288:     rem_node(&e->n);
                    289: 
                    290:   add_tail(&loop->event_list, &e->n);
                    291: }
                    292: 
                    293: 
                    294: /*
                    295:  *     Timers
                    296:  */
                    297: 
                    298: #define TIMER_LESS(a,b)                ((a)->expires < (b)->expires)
                    299: #define TIMER_SWAP(heap,a,b,t) (t = heap[a], heap[a] = heap[b], heap[b] = t, \
                    300:                                   heap[a]->index = (a), heap[b]->index = (b))
                    301: 
                    302: static inline uint timers_count(struct birdloop *loop)
                    303: { return loop->timers.used - 1; }
                    304: 
                    305: static inline timer2 *timers_first(struct birdloop *loop)
                    306: { return (loop->timers.used > 1) ? loop->timers.data[1] : NULL; }
                    307: 
                    308: 
                    309: static void
                    310: tm2_free(resource *r)
                    311: {
                    312:   timer2 *t = (timer2 *) r;
                    313: 
                    314:   tm2_stop(t);
                    315: }
                    316: 
                    317: static void
                    318: tm2_dump(resource *r)
                    319: {
                    320:   timer2 *t = (timer2 *) r;
                    321: 
                    322:   debug("(code %p, data %p, ", t->hook, t->data);
                    323:   if (t->randomize)
                    324:     debug("rand %d, ", t->randomize);
                    325:   if (t->recurrent)
                    326:     debug("recur %d, ", t->recurrent);
                    327:   if (t->expires)
                    328:     debug("expires in %d ms)\n", (t->expires - current_time()) TO_MS);
                    329:   else
                    330:     debug("inactive)\n");
                    331: }
                    332: 
                    333: 
                    334: static struct resclass tm2_class = {
                    335:   "Timer",
                    336:   sizeof(timer2),
                    337:   tm2_free,
                    338:   tm2_dump,
                    339:   NULL,
                    340:   NULL
                    341: };
                    342: 
                    343: timer2 *
                    344: tm2_new(pool *p)
                    345: {
                    346:   timer2 *t = ralloc(p, &tm2_class);
                    347:   t->index = -1;
                    348:   return t;
                    349: }
                    350: 
                    351: void
                    352: tm2_set(timer2 *t, btime when)
                    353: {
                    354:   struct birdloop *loop = birdloop_current();
                    355:   uint tc = timers_count(loop);
                    356: 
                    357:   if (!t->expires)
                    358:   {
                    359:     t->index = ++tc;
                    360:     t->expires = when;
                    361:     BUFFER_PUSH(loop->timers) = t;
                    362:     HEAP_INSERT(loop->timers.data, tc, timer2 *, TIMER_LESS, TIMER_SWAP);
                    363:   }
                    364:   else if (t->expires < when)
                    365:   {
                    366:     t->expires = when;
                    367:     HEAP_INCREASE(loop->timers.data, tc, timer2 *, TIMER_LESS, TIMER_SWAP, t->index);
                    368:   }
                    369:   else if (t->expires > when)
                    370:   {
                    371:     t->expires = when;
                    372:     HEAP_DECREASE(loop->timers.data, tc, timer2 *, TIMER_LESS, TIMER_SWAP, t->index);
                    373:   }
                    374: 
                    375:   if (loop->poll_active && (t->index == 1))
                    376:     wakeup_kick(loop);
                    377: }
                    378: 
                    379: void
                    380: tm2_start(timer2 *t, btime after)
                    381: {
                    382:   tm2_set(t, current_time() + MAX(after, 0));
                    383: }
                    384: 
                    385: void
                    386: tm2_stop(timer2 *t)
                    387: {
                    388:   if (!t->expires)
                    389:     return;
                    390: 
                    391:   struct birdloop *loop = birdloop_current();
                    392:   uint tc = timers_count(loop);
                    393: 
                    394:   HEAP_DELETE(loop->timers.data, tc, timer2 *, TIMER_LESS, TIMER_SWAP, t->index);
                    395:   BUFFER_POP(loop->timers);
                    396: 
                    397:   t->index = -1;
                    398:   t->expires = 0;
                    399: }
                    400: 
                    401: static void
                    402: timers_init(struct birdloop *loop)
                    403: {
                    404:   BUFFER_INIT(loop->timers, loop->pool, 4);
                    405:   BUFFER_PUSH(loop->timers) = NULL;
                    406: }
                    407: 
                    408: static void
                    409: timers_fire(struct birdloop *loop)
                    410: {
                    411:   btime base_time;
                    412:   timer2 *t;
                    413: 
                    414:   times_update(loop);
                    415:   base_time = loop->last_time;
                    416: 
                    417:   while (t = timers_first(loop))
                    418:   {
                    419:     if (t->expires > base_time)
                    420:       return;
                    421: 
                    422:     if (t->recurrent)
                    423:     {
                    424:       btime when = t->expires + t->recurrent;
                    425:       
                    426:       if (when <= loop->last_time)
                    427:        when = loop->last_time + t->recurrent;
                    428: 
                    429:       if (t->randomize)
                    430:        when += random() % (t->randomize + 1);
                    431: 
                    432:       tm2_set(t, when);
                    433:     }
                    434:     else
                    435:       tm2_stop(t);
                    436: 
                    437:     t->hook(t);
                    438:   }
                    439: }
                    440: 
                    441: 
                    442: /*
                    443:  *     Sockets
                    444:  */
                    445: 
                    446: static void
                    447: sockets_init(struct birdloop *loop)
                    448: {
                    449:   init_list(&loop->sock_list);
                    450:   loop->sock_num = 0;
                    451: 
                    452:   BUFFER_INIT(loop->poll_sk, loop->pool, 4);
                    453:   BUFFER_INIT(loop->poll_fd, loop->pool, 4);
                    454:   loop->poll_changed = 1;      /* add wakeup fd */
                    455: }
                    456: 
                    457: static void
                    458: sockets_add(struct birdloop *loop, sock *s)
                    459: {
                    460:   add_tail(&loop->sock_list, &s->n);
                    461:   loop->sock_num++;
                    462: 
                    463:   s->index = -1;
                    464:   loop->poll_changed = 1;
                    465: 
                    466:   if (loop->poll_active)
                    467:     wakeup_kick(loop);
                    468: }
                    469: 
                    470: void
                    471: sk_start(sock *s)
                    472: {
                    473:   struct birdloop *loop = birdloop_current();
                    474: 
                    475:   sockets_add(loop, s);
                    476: }
                    477: 
                    478: static void
                    479: sockets_remove(struct birdloop *loop, sock *s)
                    480: {
                    481:   rem_node(&s->n);
                    482:   loop->sock_num--;
                    483: 
                    484:   if (s->index >= 0)
                    485:     loop->poll_sk.data[s->index] = NULL;
                    486: 
                    487:   s->index = -1;
                    488:   loop->poll_changed = 1;
                    489: 
                    490:   /* Wakeup moved to sk_stop() */
                    491: }
                    492: 
                    493: void
                    494: sk_stop(sock *s)
                    495: {
                    496:   struct birdloop *loop = birdloop_current();
                    497: 
                    498:   sockets_remove(loop, s);
                    499: 
                    500:   if (loop->poll_active)
                    501:   {
                    502:     loop->close_scheduled = 1;
                    503:     wakeup_kick(loop);
                    504:   }
                    505:   else
                    506:     close(s->fd);
                    507: 
                    508:   s->fd = -1;
                    509: }
                    510: 
                    511: static inline uint sk_want_events(sock *s)
                    512: { return (s->rx_hook ? POLLIN : 0) | ((s->ttx != s->tpos) ? POLLOUT : 0); }
                    513: 
                    514: /*
                    515: FIXME: this should be called from sock code
                    516: 
                    517: static void
                    518: sockets_update(struct birdloop *loop, sock *s)
                    519: {
                    520:   if (s->index >= 0)
                    521:     loop->poll_fd.data[s->index].events = sk_want_events(s);
                    522: }
                    523: */
                    524: 
                    525: static void
                    526: sockets_prepare(struct birdloop *loop)
                    527: {
                    528:   BUFFER_SET(loop->poll_sk, loop->sock_num + 1);
                    529:   BUFFER_SET(loop->poll_fd, loop->sock_num + 1);
                    530: 
                    531:   struct pollfd *pfd = loop->poll_fd.data;
                    532:   sock **psk = loop->poll_sk.data;
                    533:   int i = 0;
                    534:   node *n;
                    535: 
                    536:   WALK_LIST(n, loop->sock_list)
                    537:   {
                    538:     sock *s = SKIP_BACK(sock, n, n);
                    539: 
                    540:     ASSERT(i < loop->sock_num);
                    541: 
                    542:     s->index = i;
                    543:     *psk = s;
                    544:     pfd->fd = s->fd;
                    545:     pfd->events = sk_want_events(s);
                    546:     pfd->revents = 0;
                    547: 
                    548:     pfd++;
                    549:     psk++;
                    550:     i++;
                    551:   }
                    552: 
                    553:   ASSERT(i == loop->sock_num);
                    554: 
                    555:   /* Add internal wakeup fd */
                    556:   *psk = NULL;
                    557:   pfd->fd = loop->wakeup_fds[0];
                    558:   pfd->events = POLLIN;
                    559:   pfd->revents = 0;
                    560: 
                    561:   loop->poll_changed = 0;
                    562: }
                    563: 
                    564: static void
                    565: sockets_close_fds(struct birdloop *loop)
                    566: {
                    567:   struct pollfd *pfd = loop->poll_fd.data;
                    568:   sock **psk = loop->poll_sk.data;
                    569:   int poll_num = loop->poll_fd.used - 1;
                    570: 
                    571:   int i;
                    572:   for (i = 0; i < poll_num; i++)
                    573:     if (psk[i] == NULL)
                    574:       close(pfd[i].fd);
                    575: 
                    576:   loop->close_scheduled = 0;
                    577: }
                    578: 
                    579: int sk_read(sock *s, int revents);
                    580: int sk_write(sock *s);
                    581: 
                    582: static void
                    583: sockets_fire(struct birdloop *loop)
                    584: {
                    585:   struct pollfd *pfd = loop->poll_fd.data;
                    586:   sock **psk = loop->poll_sk.data;
                    587:   int poll_num = loop->poll_fd.used - 1;
                    588: 
                    589:   times_update(loop);
                    590: 
                    591:   /* Last fd is internal wakeup fd */
                    592:   if (pfd[poll_num].revents & POLLIN)
                    593:     wakeup_drain(loop);
                    594: 
                    595:   int i;
                    596:   for (i = 0; i < poll_num; pfd++, psk++, i++)
                    597:   {
                    598:     int e = 1;
                    599: 
                    600:     if (! pfd->revents)
                    601:       continue;
                    602: 
                    603:     if (pfd->revents & POLLNVAL)
                    604:       die("poll: invalid fd %d", pfd->fd);
                    605: 
                    606:     if (pfd->revents & POLLIN)
                    607:       while (e && *psk && (*psk)->rx_hook)
                    608:        e = sk_read(*psk, 0);
                    609: 
                    610:     e = 1;
                    611:     if (pfd->revents & POLLOUT)
                    612:       while (e && *psk)
                    613:        e = sk_write(*psk);
                    614:   }
                    615: }
                    616: 
                    617: 
                    618: /*
                    619:  *     Birdloop
                    620:  */
                    621: 
                    622: static void * birdloop_main(void *arg);
                    623: 
                    624: struct birdloop *
                    625: birdloop_new(void)
                    626: {
                    627:   /* FIXME: this init should be elsewhere and thread-safe */
                    628:   static int init = 0;
                    629:   if (!init)
                    630:     { birdloop_init_current(); init = 1; }
                    631: 
                    632:   pool *p = rp_new(NULL, "Birdloop root");
                    633:   struct birdloop *loop = mb_allocz(p, sizeof(struct birdloop));
                    634:   loop->pool = p;
                    635:   pthread_mutex_init(&loop->mutex, NULL);
                    636: 
                    637:   times_init(loop);
                    638:   wakeup_init(loop);
                    639: 
                    640:   events_init(loop);
                    641:   timers_init(loop);
                    642:   sockets_init(loop);
                    643: 
                    644:   return loop;
                    645: }
                    646: 
                    647: void
                    648: birdloop_start(struct birdloop *loop)
                    649: {
                    650:   int rv = pthread_create(&loop->thread, NULL, birdloop_main, loop);
                    651:   if (rv)
                    652:     die("pthread_create(): %M", rv);
                    653: }
                    654: 
                    655: void
                    656: birdloop_stop(struct birdloop *loop)
                    657: {
                    658:   pthread_mutex_lock(&loop->mutex);
                    659:   loop->stop_called = 1;
                    660:   wakeup_do_kick(loop);
                    661:   pthread_mutex_unlock(&loop->mutex);
                    662: 
                    663:   int rv = pthread_join(loop->thread, NULL);
                    664:   if (rv)
                    665:     die("pthread_join(): %M", rv);
                    666: }
                    667: 
                    668: void
                    669: birdloop_free(struct birdloop *loop)
                    670: {
                    671:   rfree(loop->pool);
                    672: }
                    673: 
                    674: 
                    675: void
                    676: birdloop_enter(struct birdloop *loop)
                    677: {
                    678:   /* TODO: these functions could save and restore old context */
                    679:   pthread_mutex_lock(&loop->mutex);
                    680:   birdloop_set_current(loop);
                    681: }
                    682: 
                    683: void
                    684: birdloop_leave(struct birdloop *loop)
                    685: {
                    686:   /* TODO: these functions could save and restore old context */
                    687:   birdloop_set_current(NULL);
                    688:   pthread_mutex_unlock(&loop->mutex);
                    689: }
                    690: 
                    691: void
                    692: birdloop_mask_wakeups(struct birdloop *loop)
                    693: {
                    694:   pthread_mutex_lock(&loop->mutex);
                    695:   loop->wakeup_masked = 1;
                    696:   pthread_mutex_unlock(&loop->mutex);
                    697: }
                    698: 
                    699: void
                    700: birdloop_unmask_wakeups(struct birdloop *loop)
                    701: {
                    702:   pthread_mutex_lock(&loop->mutex);
                    703:   if (loop->wakeup_masked == 2)
                    704:     wakeup_do_kick(loop);
                    705:   loop->wakeup_masked = 0;
                    706:   pthread_mutex_unlock(&loop->mutex);
                    707: }
                    708: 
                    709: static void *
                    710: birdloop_main(void *arg)
                    711: {
                    712:   struct birdloop *loop = arg;
                    713:   timer2 *t;
                    714:   int rv, timeout;
                    715: 
                    716:   birdloop_set_current(loop);
                    717: 
                    718:   pthread_mutex_lock(&loop->mutex);
                    719:   while (1)
                    720:   {
                    721:     events_fire(loop);
                    722:     timers_fire(loop);
                    723: 
                    724:     times_update(loop);
                    725:     if (events_waiting(loop))
                    726:       timeout = 0;
                    727:     else if (t = timers_first(loop))
                    728:       timeout = (tm2_remains(t) TO_MS) + 1;
                    729:     else
                    730:       timeout = -1;
                    731: 
                    732:     if (loop->poll_changed)
                    733:       sockets_prepare(loop);
                    734: 
                    735:     loop->poll_active = 1;
                    736:     pthread_mutex_unlock(&loop->mutex);
                    737: 
                    738:   try:
                    739:     rv = poll(loop->poll_fd.data, loop->poll_fd.used, timeout);
                    740:     if (rv < 0)
                    741:     {
                    742:       if (errno == EINTR || errno == EAGAIN)
                    743:        goto try;
                    744:       die("poll: %m");
                    745:     }
                    746: 
                    747:     pthread_mutex_lock(&loop->mutex);
                    748:     loop->poll_active = 0;
                    749: 
                    750:     if (loop->close_scheduled)
                    751:       sockets_close_fds(loop);
                    752: 
                    753:     if (loop->stop_called)
                    754:       break;
                    755: 
                    756:     if (rv)
                    757:       sockets_fire(loop);
                    758: 
                    759:     timers_fire(loop);
                    760:   }
                    761: 
                    762:   loop->stop_called = 0;
                    763:   pthread_mutex_unlock(&loop->mutex);
                    764: 
                    765:   return NULL;
                    766: }
                    767: 
                    768: 

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>