Created SILC Runtime Toolkit git repository Part II.
[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     return TRUE;
291   }
292
293   /* Schedule the task */
294   if (silc_unlikely(!task->scheduled)) {
295     event.data.ptr = task;
296     if (epoll_ctl(internal->epfd, EPOLL_CTL_ADD, task->fd, &event)) {
297       SILC_LOG_DEBUG(("epoll_ctl (ADD): %s", strerror(errno)));
298       return FALSE;
299     }
300     task->scheduled = TRUE;
301     return TRUE;
302   }
303
304   /* Schedule for specific mask */
305   event.data.ptr = task;
306   if (epoll_ctl(internal->epfd, EPOLL_CTL_MOD, task->fd, &event)) {
307     SILC_LOG_DEBUG(("epoll_ctl (MOD): %s", strerror(errno)));
308     return FALSE;
309   }
310 #endif /* HAVE_EPOLL_WAIT */
311   return TRUE;
312 }
313
314 #ifdef SILC_THREADS
315
316 SILC_TASK_CALLBACK(silc_schedule_wakeup_cb)
317 {
318   SilcUnixScheduler internal = (SilcUnixScheduler)context;
319   unsigned char c;
320
321   SILC_LOG_DEBUG(("Wokeup"));
322
323   (void)read(internal->wakeup_pipe[0], &c, 1);
324 }
325
326 SILC_TASK_CALLBACK(silc_schedule_wakeup_init)
327 {
328   SilcUnixScheduler internal = schedule->internal;
329
330   internal->wakeup_task =
331     silc_schedule_task_add(schedule, internal->wakeup_pipe[0],
332                            silc_schedule_wakeup_cb, internal,
333                            0, 0, SILC_TASK_FD);
334   if (!internal->wakeup_task) {
335     SILC_LOG_WARNING(("Could not add a wakeup task, threads won't work"));
336     close(internal->wakeup_pipe[0]);
337     return;
338   }
339   silc_schedule_internal_schedule_fd(schedule, internal,
340                                      (SilcTaskFd)internal->wakeup_task,
341                                      SILC_TASK_READ);
342 }
343 #endif /* SILC_THREADS */
344
345 /* Initializes the platform specific scheduler.  This for example initializes
346    the wakeup mechanism of the scheduler.  In multi-threaded environment
347    the scheduler needs to be woken up when tasks are added or removed from
348    the task queues.  Returns context to the platform specific scheduler. */
349
350 void *silc_schedule_internal_init(SilcSchedule schedule,
351                                   void *app_context)
352 {
353   SilcUnixScheduler internal;
354   int i;
355
356   internal = silc_scalloc(schedule->stack, 1, sizeof(*internal));
357   if (!internal)
358     return NULL;
359
360 #if defined(HAVE_EPOLL_WAIT)
361   internal->epfd = epoll_create(4);
362   if (internal->epfd < 0) {
363     SILC_LOG_ERROR(("epoll_create() failed: %s", strerror(errno)));
364     return NULL;
365   }
366   internal->fds = silc_calloc(4, sizeof(*internal->fds));
367   if (!internal->fds) {
368     close(internal->epfd);
369     return NULL;
370   }
371   internal->fds_count = 4;
372 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
373   getrlimit(RLIMIT_NOFILE, &internal->nofile);
374
375   if (schedule->max_tasks > 0) {
376     internal->nofile.rlim_cur = schedule->max_tasks;
377     if (schedule->max_tasks > internal->nofile.rlim_max)
378       internal->nofile.rlim_max = schedule->max_tasks;
379     setrlimit(RLIMIT_NOFILE, &internal->nofile);
380     getrlimit(RLIMIT_NOFILE, &internal->nofile);
381     schedule->max_tasks = internal->nofile.rlim_max;
382   }
383
384   internal->fds = silc_calloc(internal->nofile.rlim_cur,
385                               sizeof(*internal->fds));
386   if (!internal->fds)
387     return NULL;
388   internal->fds_count = internal->nofile.rlim_cur;
389 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
390
391   sigemptyset(&internal->signals);
392
393 #ifdef SILC_THREADS
394   if (pipe(internal->wakeup_pipe)) {
395     SILC_LOG_ERROR(("pipe() fails: %s", strerror(errno)));
396     return NULL;
397   }
398
399   silc_schedule_task_add_timeout(schedule, silc_schedule_wakeup_init,
400                                  internal, 0, 0);
401 #endif /* SILC_THREADS */
402
403   internal->app_context = app_context;
404
405   for (i = 0; i < SIGNAL_COUNT; i++) {
406     signal_call[i].sig = 0;
407     signal_call[i].call = FALSE;
408     signal_call[i].schedule = schedule;
409   }
410
411   return (void *)internal;
412 }
413
414 void silc_schedule_internal_signals_block(SilcSchedule schedule,
415                                           void *context);
416 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
417                                             void *context);
418
419 /* Uninitializes the platform specific scheduler context. */
420
421 void silc_schedule_internal_uninit(SilcSchedule schedule, void *context)
422 {
423   SilcUnixScheduler internal = (SilcUnixScheduler)context;
424
425   if (!internal)
426     return;
427
428 #ifdef SILC_THREADS
429   close(internal->wakeup_pipe[0]);
430   close(internal->wakeup_pipe[1]);
431 #endif
432
433 #if defined(HAVE_EPOLL_WAIT)
434   close(internal->epfd);
435   silc_free(internal->fds);
436 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
437   silc_free(internal->fds);
438 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
439 }
440
441 /* Wakes up the scheduler */
442
443 void silc_schedule_internal_wakeup(SilcSchedule schedule, void *context)
444 {
445 #ifdef SILC_THREADS
446   SilcUnixScheduler internal = (SilcUnixScheduler)context;
447
448   if (!internal || !internal->wakeup_task)
449     return;
450
451   SILC_LOG_DEBUG(("Wakeup"));
452
453   (void)write(internal->wakeup_pipe[1], "!", 1);
454 #endif
455 }
456
457 /* Signal handler */
458
459 static void silc_schedule_internal_sighandler(int signal)
460 {
461   int i;
462
463   SILC_LOG_DEBUG(("Start"));
464
465   for (i = 0; i < SIGNAL_COUNT; i++) {
466     if (signal_call[i].sig == signal) {
467       signal_call[i].call = TRUE;
468       signal_call[i].schedule->signal_tasks = TRUE;
469       SILC_LOG_DEBUG(("Scheduling signal %d to be called",
470                       signal_call[i].sig));
471       break;
472     }
473   }
474 }
475
476 void silc_schedule_internal_signal_register(SilcSchedule schedule,
477                                             void *context,
478                                             SilcUInt32 sig,
479                                             SilcTaskCallback callback,
480                                             void *callback_context)
481 {
482   SilcUnixScheduler internal = (SilcUnixScheduler)context;
483   int i;
484
485   if (!internal)
486     return;
487
488   SILC_LOG_DEBUG(("Registering signal %d", sig));
489
490   silc_schedule_internal_signals_block(schedule, context);
491
492   for (i = 0; i < SIGNAL_COUNT; i++) {
493     if (!signal_call[i].sig) {
494       signal_call[i].sig = sig;
495       signal_call[i].callback = callback;
496       signal_call[i].context = callback_context;
497       signal_call[i].schedule = schedule;
498       signal_call[i].call = FALSE;
499       signal(sig, silc_schedule_internal_sighandler);
500       break;
501     }
502   }
503
504   silc_schedule_internal_signals_unblock(schedule, context);
505   sigaddset(&internal->signals, sig);
506 }
507
508 void silc_schedule_internal_signal_unregister(SilcSchedule schedule,
509                                               void *context,
510                                               SilcUInt32 sig)
511 {
512   SilcUnixScheduler internal = (SilcUnixScheduler)context;
513   int i;
514
515   if (!internal)
516     return;
517
518   SILC_LOG_DEBUG(("Unregistering signal %d", sig));
519
520   silc_schedule_internal_signals_block(schedule, context);
521
522   for (i = 0; i < SIGNAL_COUNT; i++) {
523     if (signal_call[i].sig == sig) {
524       signal_call[i].sig = 0;
525       signal_call[i].callback = NULL;
526       signal_call[i].context = NULL;
527       signal_call[i].schedule = NULL;
528       signal_call[i].call = FALSE;
529       signal(sig, SIG_DFL);
530     }
531   }
532
533   silc_schedule_internal_signals_unblock(schedule, context);
534   sigdelset(&internal->signals, sig);
535 }
536
537 /* Call all signals */
538
539 void silc_schedule_internal_signals_call(SilcSchedule schedule, void *context)
540 {
541   SilcUnixScheduler internal = (SilcUnixScheduler)context;
542   int i;
543
544   SILC_LOG_DEBUG(("Start"));
545
546   if (!internal)
547     return;
548
549   silc_schedule_internal_signals_block(schedule, context);
550
551   for (i = 0; i < SIGNAL_COUNT; i++) {
552     if (signal_call[i].call &&
553         signal_call[i].callback) {
554       SILC_LOG_DEBUG(("Calling signal %d callback",
555                       signal_call[i].sig));
556       silc_schedule_internal_signals_unblock(schedule, context);
557       signal_call[i].callback(schedule, internal->app_context,
558                               SILC_TASK_INTERRUPT,
559                               signal_call[i].sig,
560                               signal_call[i].context);
561       signal_call[i].call = FALSE;
562       silc_schedule_internal_signals_block(schedule, context);
563     }
564   }
565
566   silc_schedule_internal_signals_unblock(schedule, context);
567 }
568
569 /* Block registered signals in scheduler. */
570
571 void silc_schedule_internal_signals_block(SilcSchedule schedule, void *context)
572 {
573   SilcUnixScheduler internal = (SilcUnixScheduler)context;
574
575   if (!internal)
576     return;
577
578   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
579 }
580
581 /* Unblock registered signals in schedule. */
582
583 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
584                                             void *context)
585 {
586   SilcUnixScheduler internal = (SilcUnixScheduler)context;
587
588   if (!internal)
589     return;
590
591   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
592 }
593
594 const SilcScheduleOps schedule_ops =
595 {
596   silc_schedule_internal_init,
597   silc_schedule_internal_uninit,
598 #if defined(HAVE_EPOLL_WAIT)
599   silc_epoll,
600 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
601   silc_poll,
602 #else
603   silc_select,
604 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
605   silc_schedule_internal_schedule_fd,
606   silc_schedule_internal_wakeup,
607   silc_schedule_internal_signal_register,
608   silc_schedule_internal_signal_unregister,
609   silc_schedule_internal_signals_call,
610   silc_schedule_internal_signals_block,
611   silc_schedule_internal_signals_unblock,
612 };