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
24 /* Our "select()" for WIN32. This mimics the behaviour of select() system
25    call. It does not call the Winsock's select() though. Its functions
26    are derived from GLib's g_poll() and from some old Xemacs's sys_select().
27
28    This makes following assumptions, which I don't know whether they
29    are correct or not:
30
31    o writefds are ignored, if set this will return immediately.
32    o exceptfds are ignored totally
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(int n, fd_set *readfds, fd_set *writefds,
54                 fd_set *exceptfds, struct timeval *timeout)
55 {
56   HANDLE handles[MAXIMUM_WAIT_OBJECTS];
57   DWORD ready, curtime, timeo;
58   int nhandles = 0, i;
59   MSG msg;
60
61   /* Check fd sets (ignoring the exceptfds) */
62   if (readfds) {
63     for (i = 0; i < n - 1; i++)
64       if (FD_ISSET(i, readfds))
65         handles[nhandles++] = (HANDLE)i;
66
67     FD_ZERO(readfds);
68   }
69
70   /* If writefds is set then return immediately */
71   if (writefds) {
72     for (i = 0; i < n - 1; i++)
73       if (FD_ISSET(i, writefds))
74         return 1;
75   }
76
77   timeo = (timeout ? (timeout->tv_sec * 1000) + (timeout->tv_usec / 1000) :
78            INFINITE);
79
80   /* If we have nothing to wait and timeout is set then register a timeout
81      and wait just for windows messages. */
82   if (nhandles == 0 && timeout) {
83     UINT timer = SetTimer(NULL, 0, timeo, NULL);
84     curtime = GetTickCount();
85     while (timer) {
86       WaitMessage();
87       KillTimer(NULL, timer);
88
89       while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
90         if (msg.message == WM_TIMER)
91           return 0;
92         TranslateMessage(&msg); 
93         DispatchMessage(&msg); 
94       }
95
96       if (timeo != INFINITE) {
97         timeo -= GetTickCount() - curtime;
98         if (timeo < 0)
99           timeo = 0;
100       }
101       timer = SetTimer(NULL, 0, timeo, NULL);
102     }
103   }
104
105  retry:
106   curtime = GetTickCount();
107   ready = MsgWaitForMultipleObjects(nhandles, handles, FALSE, timeo, 
108                                     QS_ALLINPUT);
109
110   if (ready == WAIT_FAILED) {
111     /* Wait failed with error */
112     SILC_LOG_WARNING(("WaitForMultipleObjects() failed"));
113     return -1;
114
115   } else if (ready >= WAIT_ABANDONED_0 &&
116              ready < WAIT_ABANDONED_0 + nhandles) {
117     /* Signal abandoned */
118     SILC_LOG_WARNING(("WaitForMultipleObjects() failed (ABANDONED)"));
119     return -1;
120   } else if (ready == WAIT_TIMEOUT) {
121     /* Timeout */
122     return 0;
123   } else if (ready == WAIT_OBJECT_0 + nhandles) {
124     /* Windows messages. The MSDN online says that if the application
125        creates a window then its main loop (and we're assuming that
126        it is our SILC Scheduler) must handle the Windows messages, so do
127        it here as the MSDN suggests. */
128     while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
129       TranslateMessage(&msg); 
130       DispatchMessage(&msg); 
131     }
132
133     /* If timeout is set then we must update the timeout since we won't
134        return and we will give the wait another try. */
135     if (timeo != INFINITE) {
136       timeo -= GetTickCount() - curtime;
137       if (timeo < 0)
138         timeo = 0;
139     }
140
141     /* Give the wait another try */
142    goto retry;
143   } else if (ready >= WAIT_OBJECT_0 && ready < WAIT_OBJECT_0 + nhandles &&
144              readfds) {
145     /* Some other event, like SOCKET or something. */
146
147     /* Go through all fds even though only one was set. This is to avoid
148        starvation of high numbered fds. */
149     ready -= WAIT_OBJECT_0;
150     i = 0;
151     do {
152       /* Set the handle to fd set */
153       FD_SET((int)handles[ready], readfds);
154       i++;
155
156       /* Check the status of the next handle and set it's fd to the fd
157          set if data is available. */
158       while (++ready < n)
159         if (WaitForSingleObject(handles[ready], 0) == WAIT_OBJECT_0)
160           break;
161     } while (ready < n);
162
163     return i;
164   }
165
166   return -1;
167 }
168
169 #ifdef SILC_THREADS
170
171 /* Internal wakeup context. */
172 typedef struct {
173   HANDLE wakeup_sema;
174   SilcTask wakeup_task;
175 } *SilcWin32Wakeup;
176
177 SILC_TASK_CALLBACK(silc_schedule_wakeup_cb)
178 {
179   /* Nothing */
180 }
181
182 #endif /* SILC_THREADS */
183
184 /* Initializes the wakeup of the scheduler. In multi-threaded environment
185    the scheduler needs to be wakenup when tasks are added or removed from
186    the task queues. This will initialize the wakeup for the scheduler.
187    Any tasks that needs to be registered must be registered to the `queue'.
188    It is guaranteed that the scheduler will automatically free any
189    registered tasks in this queue. This is system specific routine. */
190
191 void *silc_schedule_wakeup_init(void *queue)
192 {
193   SilcWin32Wakeup wakeup;
194
195   wakeup = silc_calloc(1, sizeof(*wakeup));
196
197   wakeup->wakeup_sema = CreateSemaphore(NULL, 0, 100, NULL);
198   if (!wakeup->wakeup_sema) {
199     silc_free(wakeup);
200     return NULL;
201   }
202
203   wakeup->wakeup_task = silc_task_register(queue, (int)wakeup->wakeup_sema,
204                                            silc_schedule_wakeup_cb, wakeup,
205                                            0, 0, SILC_TASK_FD, 
206                                            SILC_TASK_PRI_NORMAL);
207   if (!wakeup->wakeup_task) {
208     CloseHandle(wakeup->wakeup_sema);
209     silc_free(wakeup);
210     return NULL;
211   }
212
213   return (void *)wakeup;
214 #endif
215   return NULL;
216 }
217
218 /* Uninitializes the system specific wakeup. */
219
220 void silc_schedule_wakeup_uninit(void *context)
221 {
222 #ifdef SILC_THREADS
223   SilcWin32Wakeup wakeup = (SilcWin32Wakeup)context;
224
225   if (!wakeup)
226     return;
227
228   CloseHandle(wakeup->wakeup_sema);
229   silc_free(wakeup);
230 #endif
231 }
232
233 /* Wakes up the scheduler */
234
235 void silc_schedule_wakeup_internal(void *context)
236 {
237 #ifdef SILC_THREADS
238   SilcWin32Wakeup wakeup = (SilcWin32Wakeup)context;
239
240   if (!wakeup)
241     return;
242
243   ReleaseSemaphore(wakeup->wakeup_sema, 1, NULL);
244 #endif
245 }