Ported SILC Net API (TCP & UDP), SILC Socket Stream API,
[silc.git] / lib / silcutil / win32 / silcwin32schedule.c
1 /*
2
3   silcwin32schedule.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2001 - 2007 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 /* $Id$ */
20
21 #include "silc.h"
22
23 /* Our "select()" for WIN32. This mimics the behaviour of select() system
24    call. It does not call the Winsock's select() though. Its functions
25    are derived from GLib's g_poll() and from some old Xemacs's sys_select().
26
27    This makes following assumptions, which I don't know whether they
28    are correct or not:
29
30    o SILC_TASK_WRITE is ignored, if set this will return immediately.
31    o If all arguments except timeout are NULL then this will register
32      a timeout with SetTimer and will wait just for Windows messages
33      with WaitMessage.
34    o MsgWaitForMultipleObjects is used to wait all kind of events, this
35      includes SOCKETs and Windows messages.
36    o All Windows messages are dispatched from this function.
37    o The Operating System has Winsock 2.
38
39    References:
40
41    o http://msdn.microsoft.com/library/default.asp?
42      url=/library/en-us/winui/hh/winui/messques_77zk.asp
43    o http://msdn.microsoft.com/library/default.asp?
44      url=/library/en-us/winsock/hh/winsock/apistart_9g1e.asp
45    o http://msdn.microsoft.com/library/default.asp?
46      url=/library/en-us/dnmgmt/html/msdn_getpeek.asp
47    o http://developer.novell.com/support/winsock/doc/toc.htm
48
49 */
50
51 int silc_select(SilcSchedule schedule, void *context);
52 {
53   SilcHashTableList htl;
54   SilcTaskFd task;
55   HANDLE handles[MAXIMUM_WAIT_OBJECTS];
56   DWORD ready, curtime;
57   LONG timeo;
58   MSG msg;
59   int nhandles = 0, fd;
60
61   silc_hash_table_list(schedule->fd_queue, &htl);
62   while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task)) {
63     if (!task->events)
64       continue;
65     if (nhandles >= MAXIMUM_WAIT_OBJECTS)
66       break;
67
68     if (task->events & SILC_TASK_READ)
69       handles[nhandles++] = (HANDLE)fd;
70
71     /* If writing then just set the bit and return */
72     if (task->events & SILC_TASK_WRITE) {
73       task->revents = SILC_TASK_WRITE;
74       return 1;
75     }
76
77     task->revents = 0;
78   }
79   silc_hash_table_list_reset(&htl);
80   silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
81
82   timeo = (schedule->has_timeout ? ((schedule->timeout.tv_sec * 1000) +
83                                     (schedule->timeout.tv_usec / 1000))
84            : INFINITE);
85
86   /* If we have nothing to wait and timeout is set then register a timeout
87      and wait just for windows messages. */
88   if (nhandles == 0 && schedule->has_timeout) {
89     SILC_SCHEDULE_UNLOCK(schedule);
90     UINT timer = SetTimer(NULL, 0, timeo, NULL);
91     curtime = GetTickCount();
92     while (timer) {
93       WaitMessage();
94
95       while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
96         if (msg.message == WM_TIMER) {
97           KillTimer(NULL, timer);
98           SILC_SCHEDULE_LOCK(schedule);
99           return 0;
100         }
101         TranslateMessage(&msg);
102         DispatchMessage(&msg);
103       }
104
105       KillTimer(NULL, timer);
106       if (timeo != INFINITE) {
107         timeo -= GetTickCount() - curtime;
108         curtime = GetTickCount();
109         if (timeo < 0)
110           timeo = 0;
111       }
112       timer = SetTimer(NULL, 0, timeo, NULL);
113     }
114     SILC_SCHEDULE_LOCK(schedule);
115   }
116
117   SILC_SCHEDULE_UNLOCK(schedule);
118  retry:
119   curtime = GetTickCount();
120   ready = MsgWaitForMultipleObjects(nhandles, handles, FALSE, timeo,
121                                     QS_ALLINPUT);
122   SILC_SCHEDULE_LOCK(schedule);
123
124   if (ready == WAIT_FAILED) {
125     /* Wait failed with error */
126     SILC_LOG_WARNING(("WaitForMultipleObjects() failed"));
127     return -1;
128   } else if (ready >= WAIT_ABANDONED_0 &&
129              ready < WAIT_ABANDONED_0 + nhandles) {
130     /* Signal abandoned */
131     SILC_LOG_WARNING(("WaitForMultipleObjects() failed (ABANDONED)"));
132     return -1;
133   } else if (ready == WAIT_TIMEOUT) {
134     /* Timeout */
135     return 0;
136   } else if (ready == WAIT_OBJECT_0 + nhandles) {
137     /* Windows messages. The MSDN online says that if the application
138        creates a window then its main loop (and we're assuming that
139        it is our SILC Scheduler) must handle the Windows messages, so do
140        it here as the MSDN suggests. */
141     SILC_SCHEDULE_UNLOCK(schedule);
142     while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
143       TranslateMessage(&msg);
144       DispatchMessage(&msg);
145     }
146
147     /* If timeout is set then we must update the timeout since we won't
148        return and we will give the wait another try. */
149     if (timeo != INFINITE) {
150       timeo -= GetTickCount() - curtime;
151       curtime = GetTickCount();
152       if (timeo < 0)
153         timeo = 0;
154     }
155
156     /* Give the wait another try */
157     goto retry;
158   } else if (ready >= WAIT_OBJECT_0 && ready < WAIT_OBJECT_0 + nhandles) {
159     /* Some other event, like SOCKET or something. */
160
161     /* Go through all fds even though only one was set. This is to avoid
162        starvation of high numbered fds. */
163     nhandles = silc_hash_table_count(schedule->fd_queue);
164     ready -= WAIT_OBJECT_0;
165     do {
166       if (!silc_hash_table_find(schedule->fd_queue,
167                                 SILC_32_TO_PTR((SilcUInt32)handles[ready]),
168                                 NULL, (void *)&task))
169         break;
170
171       if (task->header.valid && task->events) {
172         task->revents |= SILC_TASK_READ;
173         silc_list_add(schedule->fd_dispatch, task);
174       }
175
176       /* Check the status of the next handle and set its fd to the fd
177          set if data is available. */
178       SILC_SCHEDULE_UNLOCK(schedule);
179       while (++ready < nhandles)
180         if (WaitForSingleObject(handles[ready], 0) == WAIT_OBJECT_0)
181           break;
182       SILC_SCHEDULE_LOCK(schedule);
183     } while (ready < nhandles);
184
185     return silc_list_count(schedule->fd_dispatch);
186   }
187
188   return -1;
189 }
190
191 #ifdef SILC_THREADS
192
193 /* Internal wakeup context. */
194 typedef struct {
195   HANDLE wakeup_sema;
196   SilcTask wakeup_task;
197 } *SilcWin32Wakeup;
198
199 SILC_TASK_CALLBACK(silc_schedule_wakeup_cb)
200 {
201   /* Nothing */
202 }
203
204 #endif /* SILC_THREADS */
205
206 /* Initializes the platform specific scheduler.  This for example initializes
207    the wakeup mechanism of the scheduler.  In multi-threaded environment
208    the scheduler needs to be wakenup when tasks are added or removed from
209    the task queues.  Returns context to the platform specific scheduler. */
210
211 void *silc_schedule_internal_init(SilcSchedule schedule, void *app_context)
212 {
213 #ifdef SILC_THREADS
214   SilcWin32Wakeup wakeup;
215 #endif
216
217   schedule->max_tasks = MAXIMUM_WAIT_OBJECTS;
218
219 #ifdef SILC_THREADS
220   wakeup = silc_calloc(1, sizeof(*wakeup));
221   if (!wakeup)
222     return NULL;
223
224   wakeup->wakeup_sema = CreateSemaphore(NULL, 0, 100, NULL);
225   if (!wakeup->wakeup_sema) {
226     silc_free(wakeup);
227     return NULL;
228   }
229
230   wakeup->wakeup_task =
231     silc_schedule_task_add(schedule, (int)wakeup->wakeup_sema,
232                            silc_schedule_wakeup_cb, wakeup,
233                            0, 0, SILC_TASK_FD);
234   if (!wakeup->wakeup_task) {
235     CloseHandle(wakeup->wakeup_sema);
236     silc_free(wakeup);
237     return NULL;
238   }
239
240   return (void *)wakeup;
241 #else
242   return (void *)1;
243 #endif
244 }
245
246 /* Uninitializes the platform specific scheduler context. */
247
248 void silc_schedule_internal_uninit(SilcSchedule schedule, void *context)
249 {
250 #ifdef SILC_THREADS
251   SilcWin32Wakeup wakeup = (SilcWin32Wakeup)context;
252
253   if (!wakeup)
254     return;
255
256   CloseHandle(wakeup->wakeup_sema);
257   silc_free(wakeup);
258 #endif
259 }
260
261 /* Schedule `task' with events `event_mask'. Zero `event_mask' unschedules. */
262
263 SilcBool silc_schedule_internal_schedule_fd(SilcSchedule schedule,
264                                             void *context,
265                                             SilcTaskFd task,
266                                             SilcTaskEvent event_mask)
267 {
268   return TRUE;
269 }
270
271 /* Wakes up the scheduler */
272
273 void silc_schedule_internal_wakeup(SilcSchedule schedule, void *context)
274 {
275 #ifdef SILC_THREADS
276   SilcWin32Wakeup wakeup = (SilcWin32Wakeup)context;
277
278   if (!wakeup)
279     return;
280
281   ReleaseSemaphore(wakeup->wakeup_sema, 1, NULL);
282 #endif
283 }
284
285 /* Register signal */
286
287 void silc_schedule_internal_signal_register(SilcSchedule schedule,
288                                             void *context,
289                                             SilcUInt32 signal,
290                                             SilcTaskCallback callback,
291                                             void *callback_context)
292 {
293
294 }
295
296 /* Unregister signal */
297
298 void silc_schedule_internal_signal_unregister(SilcSchedule schedule,
299                                               void *context,
300                                               SilcUInt32 signal)
301 {
302
303 }
304
305 /* Call all signals */
306
307 void silc_schedule_internal_signals_call(SilcSchedule schedule,
308                                          void *context)
309 {
310
311 }
312
313 /* Block registered signals in scheduler. */
314
315 void silc_schedule_internal_signals_block(SilcSchedule schedule,
316                                           void *context)
317 {
318
319 }
320
321 /* Unblock registered signals in schedule. */
322
323 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
324                                             void *context)
325 {
326
327 }
328
329 const SilcScheduleOps schedule_ops =
330 {
331   silc_schedule_internal_init,
332   silc_schedule_internal_uninit,
333   silc_select,
334   silc_schedule_internal_schedule_fd,
335   silc_schedule_internal_wakeup,
336   silc_schedule_internal_signal_register,
337   silc_schedule_internal_signal_unregister,
338   silc_schedule_internal_signals_call,
339   silc_schedule_internal_signals_block,
340   silc_schedule_internal_signals_unblock,
341 };