Merge branch 'silc.1.1.branch' of silc repo
[runtime.git] / lib / silcutil / unix / silcunixschedule.c
1 /*
2
3   silcunixschedule.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 1998 - 2008 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
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 */
19
20 #include "silcruntime.h"
21
22 #if defined(HAVE_EPOLL_WAIT)
23 #include <sys/epoll.h>
24 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
25 #include <poll.h>
26 #endif
27
28 const SilcScheduleOps schedule_ops;
29
30 /* Internal context. */
31 typedef struct {
32 #if defined(HAVE_EPOLL_WAIT)
33   struct epoll_event *fds;
34   SilcUInt32 fds_count;
35   int epfd;
36 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
37   struct rlimit nofile;
38   struct pollfd *fds;
39   SilcUInt32 fds_count;
40 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
41   void *app_context;
42   int wakeup_pipe[2];
43   SilcTask wakeup_task;
44   sigset_t signals;
45   sigset_t signals_blocked;
46 } *SilcUnixScheduler;
47
48 typedef struct {
49   SilcUInt32 sig;
50   SilcTaskCallback callback;
51   void *context;
52   SilcBool call;
53   SilcSchedule schedule;
54 } SilcUnixSignal;
55
56 #define SIGNAL_COUNT 32
57 SilcUnixSignal signal_call[SIGNAL_COUNT];
58
59 #if defined(HAVE_EPOLL_WAIT)
60
61 /* Linux's fast epoll system (level triggered) */
62
63 int silc_epoll(SilcSchedule schedule, void *context)
64 {
65   SilcUnixScheduler internal = context;
66   SilcTaskFd task;
67   struct epoll_event *fds = internal->fds;
68   SilcUInt32 fds_count = internal->fds_count;
69   int ret, i, timeout = -1;
70
71   /* Allocate larger fd table if needed */
72   i = silc_hash_table_count(schedule->fd_queue);
73   if (i > fds_count) {
74     fds = silc_realloc(internal->fds, sizeof(*internal->fds) *
75                        (fds_count + (i / 2)));
76     if (silc_likely(fds)) {
77       internal->fds = fds;
78       internal->fds_count = fds_count = fds_count + (i / 2);
79     }
80   }
81
82   if (schedule->has_timeout)
83     timeout = ((schedule->timeout.tv_sec * 1000) +
84                (schedule->timeout.tv_usec / 1000));
85
86   SILC_SCHEDULE_UNLOCK(schedule);
87   ret = epoll_wait(internal->epfd, fds, fds_count, timeout);
88   SILC_SCHEDULE_LOCK(schedule);
89   if (ret <= 0)
90     return ret;
91
92   silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
93
94   for (i = 0; i < ret; i++) {
95     task = fds[i].data.ptr;
96     task->revents = 0;
97     if (!task->header.valid || !task->events) {
98       epoll_ctl(internal->epfd, EPOLL_CTL_DEL, task->fd, &fds[i]);
99       continue;
100     }
101     if (fds[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP | EPOLLERR))
102       task->revents |= SILC_TASK_READ;
103     if (fds[i].events & EPOLLOUT)
104       task->revents |= SILC_TASK_WRITE;
105     silc_list_add(schedule->fd_dispatch, task);
106   }
107
108   return ret;
109 }
110
111 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
112
113 /* Calls normal poll() system call. */
114
115 int silc_poll(SilcSchedule schedule, void *context)
116 {
117   SilcUnixScheduler internal = context;
118   SilcHashTableList htl;
119   SilcTaskFd task;
120   struct pollfd *fds = internal->fds;
121   SilcUInt32 fds_count = internal->fds_count;
122   int fd, ret, i = 0, timeout = -1;
123   void *fdp;
124
125   silc_hash_table_list(schedule->fd_queue, &htl);
126   while (silc_hash_table_get(&htl, &fdp, (void *)&task)) {
127     if (!task->events)
128       continue;
129     fd = SILC_PTR_TO_32(fdp);
130
131     /* Allocate larger fd table if needed */
132     if (i >= fds_count) {
133       struct rlimit nofile;
134
135       fds = silc_realloc(internal->fds, sizeof(*internal->fds) *
136                          (fds_count + (fds_count / 2)));
137       if (silc_unlikely(!fds))
138         break;
139       internal->fds = fds;
140       internal->fds_count = fds_count = fds_count + (fds_count / 2);
141       internal->nofile.rlim_cur = fds_count;
142       if (fds_count > internal->nofile.rlim_max)
143         internal->nofile.rlim_max = fds_count;
144       if (setrlimit(RLIMIT_NOFILE, &nofile) < 0)
145         break;
146     }
147
148     fds[i].fd = fd;
149     fds[i].events = 0;
150     task->revents = fds[i].revents = 0;
151
152     if (task->events & SILC_TASK_READ)
153       fds[i].events |= (POLLIN | POLLPRI);
154     if (task->events & SILC_TASK_WRITE)
155       fds[i].events |= POLLOUT;
156     i++;
157   }
158   silc_hash_table_list_reset(&htl);
159   silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
160
161   if (schedule->has_timeout)
162     timeout = ((schedule->timeout.tv_sec * 1000) +
163                (schedule->timeout.tv_usec / 1000));
164
165   fds_count = i;
166   SILC_SCHEDULE_UNLOCK(schedule);
167   ret = poll(fds, fds_count, timeout);
168   SILC_SCHEDULE_LOCK(schedule);
169   if (ret <= 0)
170     return ret;
171
172   for (i = 0; i < fds_count; i++) {
173     if (!fds[i].revents)
174       continue;
175     if (!silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fds[i].fd),
176                               NULL, (void *)&task))
177       continue;
178     if (!task->header.valid || !task->events)
179       continue;
180
181     fd = fds[i].revents;
182     if (fd & (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL))
183       task->revents |= SILC_TASK_READ;
184     if (fd & POLLOUT)
185       task->revents |= SILC_TASK_WRITE;
186     silc_list_add(schedule->fd_dispatch, task);
187   }
188
189   return ret;
190 }
191
192 #else
193
194 /* Calls normal select() system call. */
195
196 int silc_select(SilcSchedule schedule, void *context)
197 {
198   SilcHashTableList htl;
199   SilcTaskFd task;
200   fd_set in, out;
201   int fd, max_fd = 0, ret;
202   void *fdp;
203
204   FD_ZERO(&in);
205   FD_ZERO(&out);
206
207   silc_hash_table_list(schedule->fd_queue, &htl);
208   while (silc_hash_table_get(&htl, &fdp, (void *)&task)) {
209     if (!task->events)
210       continue;
211     fd = SILC_PTR_TO_32(fdp);
212
213 #ifdef FD_SETSIZE
214     if (fd >= FD_SETSIZE)
215       break;
216 #endif /* FD_SETSIZE */
217
218     if (fd > max_fd)
219       max_fd = fd;
220
221     if (task->events & SILC_TASK_READ)
222       FD_SET(fd, &in);
223     if (task->events & SILC_TASK_WRITE)
224       FD_SET(fd, &out);
225
226     task->revents = 0;
227   }
228   silc_hash_table_list_reset(&htl);
229   silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
230
231   SILC_SCHEDULE_UNLOCK(schedule);
232   ret = select(max_fd + 1, &in, &out, NULL, (schedule->has_timeout ?
233                                              &schedule->timeout : NULL));
234   SILC_SCHEDULE_LOCK(schedule);
235   if (ret <= 0)
236     return ret;
237
238   silc_hash_table_list(schedule->fd_queue, &htl);
239   while (silc_hash_table_get(&htl, &fdp, (void *)&task)) {
240     if (!task->header.valid || !task->events)
241       continue;
242     fd = SILC_PTR_TO_32(fdp);
243
244 #ifdef FD_SETSIZE
245     if (fd >= FD_SETSIZE)
246       break;
247 #endif /* FD_SETSIZE */
248
249     if (FD_ISSET(fd, &in))
250       task->revents |= SILC_TASK_READ;
251     if (FD_ISSET(fd, &out))
252       task->revents |= SILC_TASK_WRITE;
253     silc_list_add(schedule->fd_dispatch, task);
254   }
255   silc_hash_table_list_reset(&htl);
256
257   return ret;
258 }
259
260 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
261
262 /* Schedule `task' with events `event_mask'. Zero `event_mask' unschedules. */
263
264 SilcBool silc_schedule_internal_schedule_fd(SilcSchedule schedule,
265                                             void *context,
266                                             SilcTaskFd task,
267                                             SilcTaskEvent event_mask)
268 {
269 #if defined(HAVE_EPOLL_WAIT)
270   SilcUnixScheduler internal = (SilcUnixScheduler)context;
271   struct epoll_event event;
272
273   if (!internal)
274     return TRUE;
275
276   SILC_LOG_DEBUG(("Scheduling fd %lu, mask %x", task->fd, event_mask));
277
278   memset(&event, 0, sizeof(event));
279   if (event_mask & SILC_TASK_READ)
280     event.events |= (EPOLLIN | EPOLLPRI);
281   if (event_mask & SILC_TASK_WRITE)
282     event.events |= EPOLLOUT;
283
284   /* Zero mask unschedules task */
285   if (silc_unlikely(!event.events)) {
286     if (epoll_ctl(internal->epfd, EPOLL_CTL_DEL, task->fd, &event)) {
287       SILC_LOG_DEBUG(("epoll_ctl (DEL): %s", strerror(errno)));
288       return FALSE;
289     }
290     task->scheduled = FALSE;
291     return TRUE;
292   }
293
294   /* Schedule the task */
295   if (silc_unlikely(!task->scheduled)) {
296     event.data.ptr = task;
297     if (epoll_ctl(internal->epfd, EPOLL_CTL_ADD, task->fd, &event)) {
298       SILC_LOG_DEBUG(("epoll_ctl (ADD): %s", strerror(errno)));
299       return FALSE;
300     }
301     task->scheduled = TRUE;
302     return TRUE;
303   }
304
305   /* Schedule for specific mask */
306   event.data.ptr = task;
307   if (epoll_ctl(internal->epfd, EPOLL_CTL_MOD, task->fd, &event)) {
308     SILC_LOG_DEBUG(("epoll_ctl (MOD): %s", strerror(errno)));
309     return FALSE;
310   }
311 #endif /* HAVE_EPOLL_WAIT */
312   return TRUE;
313 }
314
315 #ifdef SILC_THREADS
316
317 SILC_TASK_CALLBACK(silc_schedule_wakeup_cb)
318 {
319   SilcUnixScheduler internal = (SilcUnixScheduler)context;
320   unsigned char c;
321
322   SILC_LOG_DEBUG(("Wokeup"));
323
324   (void)read(internal->wakeup_pipe[0], &c, 1);
325 }
326
327 SILC_TASK_CALLBACK(silc_schedule_wakeup_init)
328 {
329   SilcUnixScheduler internal = schedule->internal;
330
331   internal->wakeup_task =
332     silc_schedule_task_add(schedule, internal->wakeup_pipe[0],
333                            silc_schedule_wakeup_cb, internal,
334                            0, 0, SILC_TASK_FD);
335   if (!internal->wakeup_task) {
336     SILC_LOG_WARNING(("Could not add a wakeup task, threads won't work"));
337     close(internal->wakeup_pipe[0]);
338     return;
339   }
340   silc_schedule_internal_schedule_fd(schedule, internal,
341                                      (SilcTaskFd)internal->wakeup_task,
342                                      SILC_TASK_READ);
343 }
344 #endif /* SILC_THREADS */
345
346 /* Initializes the platform specific scheduler.  This for example initializes
347    the wakeup mechanism of the scheduler.  In multi-threaded environment
348    the scheduler needs to be woken up when tasks are added or removed from
349    the task queues.  Returns context to the platform specific scheduler. */
350
351 void *silc_schedule_internal_init(SilcSchedule schedule,
352                                   void *app_context)
353 {
354   SilcUnixScheduler internal;
355   int i;
356
357   internal = silc_scalloc(schedule->stack, 1, sizeof(*internal));
358   if (!internal)
359     return NULL;
360
361 #if defined(HAVE_EPOLL_WAIT)
362   internal->epfd = epoll_create(4);
363   if (internal->epfd < 0) {
364     SILC_LOG_ERROR(("epoll_create() failed: %s", strerror(errno)));
365     return NULL;
366   }
367   internal->fds = silc_calloc(4, sizeof(*internal->fds));
368   if (!internal->fds) {
369     close(internal->epfd);
370     return NULL;
371   }
372   internal->fds_count = 4;
373 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
374   getrlimit(RLIMIT_NOFILE, &internal->nofile);
375
376   if (schedule->max_tasks > 0) {
377     internal->nofile.rlim_cur = schedule->max_tasks;
378     if (schedule->max_tasks > internal->nofile.rlim_max)
379       internal->nofile.rlim_max = schedule->max_tasks;
380     setrlimit(RLIMIT_NOFILE, &internal->nofile);
381     getrlimit(RLIMIT_NOFILE, &internal->nofile);
382     schedule->max_tasks = internal->nofile.rlim_max;
383   }
384
385   internal->fds = silc_calloc(internal->nofile.rlim_cur,
386                               sizeof(*internal->fds));
387   if (!internal->fds)
388     return NULL;
389   internal->fds_count = internal->nofile.rlim_cur;
390 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
391
392   sigemptyset(&internal->signals);
393
394 #ifdef SILC_THREADS
395   if (pipe(internal->wakeup_pipe)) {
396     SILC_LOG_ERROR(("pipe() fails: %s", strerror(errno)));
397     return NULL;
398   }
399
400   silc_schedule_task_add_timeout(schedule, silc_schedule_wakeup_init,
401                                  internal, 0, 0);
402 #endif /* SILC_THREADS */
403
404   internal->app_context = app_context;
405
406   for (i = 0; i < SIGNAL_COUNT; i++) {
407     signal_call[i].sig = 0;
408     signal_call[i].call = FALSE;
409     signal_call[i].schedule = schedule;
410   }
411
412   return (void *)internal;
413 }
414
415 void silc_schedule_internal_signals_block(SilcSchedule schedule,
416                                           void *context);
417 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
418                                             void *context);
419
420 /* Uninitializes the platform specific scheduler context. */
421
422 void silc_schedule_internal_uninit(SilcSchedule schedule, void *context)
423 {
424   SilcUnixScheduler internal = (SilcUnixScheduler)context;
425
426   if (!internal)
427     return;
428
429 #ifdef SILC_THREADS
430   close(internal->wakeup_pipe[0]);
431   close(internal->wakeup_pipe[1]);
432 #endif
433
434 #if defined(HAVE_EPOLL_WAIT)
435   close(internal->epfd);
436   silc_free(internal->fds);
437 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
438   silc_free(internal->fds);
439 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
440 }
441
442 /* Wakes up the scheduler */
443
444 void silc_schedule_internal_wakeup(SilcSchedule schedule, void *context)
445 {
446 #ifdef SILC_THREADS
447   SilcUnixScheduler internal = (SilcUnixScheduler)context;
448
449   if (!internal || !internal->wakeup_task)
450     return;
451
452   SILC_LOG_DEBUG(("Wakeup"));
453
454   (void)write(internal->wakeup_pipe[1], "!", 1);
455 #endif
456 }
457
458 /* Signal handler */
459
460 static void silc_schedule_internal_sighandler(int signal)
461 {
462   int i;
463
464   SILC_LOG_DEBUG(("Start"));
465
466   for (i = 0; i < SIGNAL_COUNT; i++) {
467     if (signal_call[i].sig == signal) {
468       signal_call[i].call = TRUE;
469       signal_call[i].schedule->signal_tasks = TRUE;
470       SILC_LOG_DEBUG(("Scheduling signal %d to be called",
471                       signal_call[i].sig));
472       break;
473     }
474   }
475 }
476
477 void silc_schedule_internal_signal_register(SilcSchedule schedule,
478                                             void *context,
479                                             SilcUInt32 sig,
480                                             SilcTaskCallback callback,
481                                             void *callback_context)
482 {
483   SilcUnixScheduler internal = (SilcUnixScheduler)context;
484   int i;
485
486   if (!internal)
487     return;
488
489   SILC_LOG_DEBUG(("Registering signal %d", sig));
490
491   silc_schedule_internal_signals_block(schedule, context);
492
493   for (i = 0; i < SIGNAL_COUNT; i++) {
494     if (!signal_call[i].sig) {
495       signal_call[i].sig = sig;
496       signal_call[i].callback = callback;
497       signal_call[i].context = callback_context;
498       signal_call[i].schedule = schedule;
499       signal_call[i].call = FALSE;
500       signal(sig, silc_schedule_internal_sighandler);
501       break;
502     }
503   }
504
505   silc_schedule_internal_signals_unblock(schedule, context);
506   sigaddset(&internal->signals, sig);
507 }
508
509 void silc_schedule_internal_signal_unregister(SilcSchedule schedule,
510                                               void *context,
511                                               SilcUInt32 sig)
512 {
513   SilcUnixScheduler internal = (SilcUnixScheduler)context;
514   int i;
515
516   if (!internal)
517     return;
518
519   SILC_LOG_DEBUG(("Unregistering signal %d", sig));
520
521   silc_schedule_internal_signals_block(schedule, context);
522
523   for (i = 0; i < SIGNAL_COUNT; i++) {
524     if (signal_call[i].sig == sig) {
525       signal_call[i].sig = 0;
526       signal_call[i].callback = NULL;
527       signal_call[i].context = NULL;
528       signal_call[i].schedule = NULL;
529       signal_call[i].call = FALSE;
530       signal(sig, SIG_DFL);
531     }
532   }
533
534   silc_schedule_internal_signals_unblock(schedule, context);
535   sigdelset(&internal->signals, sig);
536 }
537
538 /* Call all signals */
539
540 void silc_schedule_internal_signals_call(SilcSchedule schedule, void *context)
541 {
542   SilcUnixScheduler internal = (SilcUnixScheduler)context;
543   int i;
544
545   SILC_LOG_DEBUG(("Start"));
546
547   if (!internal)
548     return;
549
550   silc_schedule_internal_signals_block(schedule, context);
551
552   for (i = 0; i < SIGNAL_COUNT; i++) {
553     if (signal_call[i].call &&
554         signal_call[i].callback) {
555       SILC_LOG_DEBUG(("Calling signal %d callback",
556                       signal_call[i].sig));
557       silc_schedule_internal_signals_unblock(schedule, context);
558       signal_call[i].callback(schedule, internal->app_context,
559                               SILC_TASK_INTERRUPT,
560                               signal_call[i].sig,
561                               signal_call[i].context);
562       signal_call[i].call = FALSE;
563       silc_schedule_internal_signals_block(schedule, context);
564     }
565   }
566
567   silc_schedule_internal_signals_unblock(schedule, context);
568 }
569
570 /* Block registered signals in scheduler. */
571
572 void silc_schedule_internal_signals_block(SilcSchedule schedule, void *context)
573 {
574   SilcUnixScheduler internal = (SilcUnixScheduler)context;
575
576   if (!internal)
577     return;
578
579   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
580 }
581
582 /* Unblock registered signals in schedule. */
583
584 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
585                                             void *context)
586 {
587   SilcUnixScheduler internal = (SilcUnixScheduler)context;
588
589   if (!internal)
590     return;
591
592   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
593 }
594
595 const SilcScheduleOps schedule_ops =
596 {
597   silc_schedule_internal_init,
598   silc_schedule_internal_uninit,
599 #if defined(HAVE_EPOLL_WAIT)
600   silc_epoll,
601 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
602   silc_poll,
603 #else
604   silc_select,
605 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
606   silc_schedule_internal_schedule_fd,
607   silc_schedule_internal_wakeup,
608   silc_schedule_internal_signal_register,
609   silc_schedule_internal_signal_unregister,
610   silc_schedule_internal_signals_call,
611   silc_schedule_internal_signals_block,
612   silc_schedule_internal_signals_unblock,
613 };