updates.
[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 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; either version 2 of the License, or
12   (at your option) any later version.
13   
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19 */
20 /* $Id$ */
21
22 #include "silcincludes.h"
23 #includd "silcschedule_i.h"
24
25 /* Our "select()" for WIN32. This mimics the behaviour of select() system
26    call. It does not call the Winsock's select() though. Its functions
27    are derived from GLib's g_poll() and from some old Xemacs's sys_select().
28
29    This makes following assumptions, which I don't know whether they
30    are correct or not:
31
32    o SILC_TASK_WRITE is ignored, if set this will return immediately.
33    o If all arguments except timeout are NULL then this will register
34      a timeout with SetTimer and will wait just for Windows messages
35      with WaitMessage.
36    o MsgWaitForMultipleObjects is used to wait all kind of events, this
37      includes SOCKETs and Windows messages.
38    o All Windows messages are dispatched from this function.
39    o The Operating System has Winsock 2.
40
41    References:
42
43    o http://msdn.microsoft.com/library/default.asp?
44      url=/library/en-us/winui/hh/winui/messques_77zk.asp 
45    o http://msdn.microsoft.com/library/default.asp?
46      url=/library/en-us/winsock/hh/winsock/apistart_9g1e.asp
47    o http://msdn.microsoft.com/library/default.asp?
48      url=/library/en-us/dnmgmt/html/msdn_getpeek.asp
49    o http://developer.novell.com/support/winsock/doc/toc.htm
50
51 */
52
53 int silc_select(SilcScheduleFd fds, uint32 fds_count, struct timeval *timeout)
54 {
55   HANDLE handles[MAXIMUM_WAIT_OBJECTS];
56   DWORD ready, curtime, timeo;
57   int nhandles = 0, i;
58   MSG msg;
59
60   for (i = 0; i < fds_count; i++) {
61     if (!fds[i].events)
62       continue;
63
64     if (fds[i].events & SILC_TASK_READ)
65       handles[nhandles++] = (HANDLE)i;
66
67     if (fds[i].events & SILC_TASK_WRITE)
68       return 1;
69
70     fds[i].revents = 0;
71   }
72
73   timeo = (timeout ? (timeout->tv_sec * 1000) + (timeout->tv_usec / 1000) :
74            INFINITE);
75
76   /* If we have nothing to wait and timeout is set then register a timeout
77      and wait just for windows messages. */
78   if (nhandles == 0 && timeout) {
79     UINT timer = SetTimer(NULL, 0, timeo, NULL);
80     curtime = GetTickCount();
81     while (timer) {
82       WaitMessage();
83
84       while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
85         if (msg.message == WM_TIMER) {
86           KillTimer(NULL, timer);
87           return 0;
88         }
89         TranslateMessage(&msg); 
90         DispatchMessage(&msg); 
91       }
92
93       KillTimer(NULL, timer);
94       if (timeo != INFINITE) {
95         timeo -= GetTickCount() - curtime;
96         if (timeo < 0)
97           timeo = 0;
98       }
99       timer = SetTimer(NULL, 0, timeo, NULL);
100     }
101   }
102
103  retry:
104   curtime = GetTickCount();
105   ready = MsgWaitForMultipleObjects(nhandles, handles, FALSE, timeo, 
106                                     QS_ALLINPUT);
107
108   if (ready == WAIT_FAILED) {
109     /* Wait failed with error */
110     SILC_LOG_WARNING(("WaitForMultipleObjects() failed"));
111     return -1;
112   } else if (ready >= WAIT_ABANDONED_0 &&
113              ready < WAIT_ABANDONED_0 + nhandles) {
114     /* Signal abandoned */
115     SILC_LOG_WARNING(("WaitForMultipleObjects() failed (ABANDONED)"));
116     return -1;
117   } else if (ready == WAIT_TIMEOUT) {
118     /* Timeout */
119     return 0;
120   } else if (ready == WAIT_OBJECT_0 + nhandles) {
121     /* Windows messages. The MSDN online says that if the application
122        creates a window then its main loop (and we're assuming that
123        it is our SILC Scheduler) must handle the Windows messages, so do
124        it here as the MSDN suggests. */
125     while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
126       TranslateMessage(&msg); 
127       DispatchMessage(&msg); 
128     }
129
130     /* If timeout is set then we must update the timeout since we won't
131        return and we will give the wait another try. */
132     if (timeo != INFINITE) {
133       timeo -= GetTickCount() - curtime;
134       if (timeo < 0)
135         timeo = 0;
136     }
137
138     /* Give the wait another try */
139    goto retry;
140   } else if (ready >= WAIT_OBJECT_0 && ready < WAIT_OBJECT_0 + nhandles &&
141              readfds) {
142     /* Some other event, like SOCKET or something. */
143
144     /* Go through all fds even though only one was set. This is to avoid
145        starvation of high numbered fds. */
146     ready -= WAIT_OBJECT_0;
147     do {
148       for (i = 0; i < fds_count; i++) {
149         if (!fds[i].events)
150           continue;
151         
152         if (fds[i].fd == (int)handles[ready]) {
153           fds[i].revents |= SILC_TASK_READ;
154           break;
155         }
156       }
157
158       /* Check the status of the next handle and set its fd to the fd
159          set if data is available. */
160       while (++ready < fds_count)
161         if (WaitForSingleObject(handles[ready], 0) == WAIT_OBJECT_0)
162           break;
163     } while (ready < fds_count);
164
165     return i + 1;
166   }
167
168   return -1;
169 }
170
171 #ifdef SILC_THREADS
172
173 /* Internal wakeup context. */
174 typedef struct {
175   HANDLE wakeup_sema;
176   SilcTask wakeup_task;
177 } *SilcWin32Wakeup;
178
179 SILC_TASK_CALLBACK(silc_schedule_wakeup_cb)
180 {
181   /* Nothing */
182 }
183
184 #endif /* SILC_THREADS */
185
186 /* Initializes the wakeup of the scheduler. In multi-threaded environment
187    the scheduler needs to be wakenup when tasks are added or removed from
188    the task queues. This will initialize the wakeup for the scheduler.
189    Any tasks that needs to be registered must be registered to the `queue'.
190    It is guaranteed that the scheduler will automatically free any
191    registered tasks in this queue. This is system specific routine. */
192
193 void *silc_schedule_wakeup_init(SilcSchedule schedule)
194 {
195 #ifdef SILC_THREADS
196   SilcWin32Wakeup wakeup;
197
198   wakeup = silc_calloc(1, sizeof(*wakeup));
199
200   wakeup->wakeup_sema = CreateSemaphore(NULL, 0, 100, NULL);
201   if (!wakeup->wakeup_sema) {
202     silc_free(wakeup);
203     return NULL;
204   }
205
206   wakeup->wakeup_task = 
207     silc_schedule_task_add(schedule, (int)wakeup->wakeup_sema,
208                            silc_schedule_wakeup_cb, wakeup,
209                            0, 0, SILC_TASK_FD, 
210                            SILC_TASK_PRI_NORMAL);
211   if (!wakeup->wakeup_task) {
212     CloseHandle(wakeup->wakeup_sema);
213     silc_free(wakeup);
214     return NULL;
215   }
216
217   return (void *)wakeup;
218 #else
219   return NULL;
220 #endif
221 }
222
223 /* Uninitializes the system specific wakeup. */
224
225 void silc_schedule_wakeup_uninit(void *context)
226 {
227 #ifdef SILC_THREADS
228   SilcWin32Wakeup wakeup = (SilcWin32Wakeup)context;
229
230   if (!wakeup)
231     return;
232
233   CloseHandle(wakeup->wakeup_sema);
234   silc_free(wakeup);
235 #endif
236 }
237
238 /* Wakes up the scheduler */
239
240 void silc_schedule_wakeup_internal(void *context)
241 {
242 #ifdef SILC_THREADS
243   SilcWin32Wakeup wakeup = (SilcWin32Wakeup)context;
244
245   if (!wakeup)
246     return;
247
248   ReleaseSemaphore(wakeup->wakeup_sema, 1, NULL);
249 #endif
250 }