Rewrote SILC scheduler on WIN32.
[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 const SilcScheduleOps schedule_ops;
24
25 #define SILC_WM_EVENT WM_USER + 1
26
27 typedef struct {
28   HWND window;                  /* Hidden window for receiving socket events */
29   WNDCLASS wclass;              /* Window class */
30   HANDLE wakeup_sema;           /* Scheduler wakeup semaphore */
31   unsigned int in_schedule   : 1;
32 } *SilcWin32Scheduler;
33
34 /* Our select() call.  This simply waits for some events to happen.  It also
35    dispatches window messages so it can be used as the main loop of Windows
36    application.  This doesn't wait for fds or sockets but does receive
37    notifications via wakeup semaphore when event occurs on some fd or socket.
38    The fds and sockets are scheduled via WSAAsyncSelect. */
39
40 int silc_select(SilcSchedule schedule, void *context)
41 {
42   SilcWin32Scheduler internal = (SilcWin32Scheduler)context;
43   SilcHashTableList htl;
44   HANDLE handles[MAXIMUM_WAIT_OBJECTS];
45   DWORD ready, curtime;
46   LONG timeo = INFINITE;
47   UINT timer;
48   MSG msg;
49   int nhandles = 0;
50
51   if (!internal->in_schedule) {
52     internal->in_schedule = TRUE;
53     silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
54   }
55
56   /* Add wakeup semaphore to events */
57   handles[nhandles++] = (HANDLE)internal->wakeup_sema;
58
59   /* Get timeout */
60   if (schedule->has_timeout)
61     timeo = ((schedule->timeout.tv_sec * 1000) +
62              (schedule->timeout.tv_usec / 1000));
63
64   SILC_SCHEDULE_UNLOCK(schedule);
65  retry:
66   curtime = GetTickCount();
67   ready = MsgWaitForMultipleObjects(nhandles, handles, FALSE, timeo,
68                                     QS_ALLINPUT);
69
70   if (ready == WAIT_FAILED) {
71     /* Wait failed with error */
72     SILC_LOG_WARNING(("WaitForMultipleObjects() failed"));
73     SILC_SCHEDULE_LOCK(schedule);
74     internal->in_schedule = FALSE;
75     return -1;
76
77   } else if (ready >= WAIT_ABANDONED_0 &&
78              ready < WAIT_ABANDONED_0 + nhandles) {
79     /* Signal abandoned */
80     SILC_LOG_WARNING(("WaitForMultipleObjects() failed (ABANDONED)"));
81     SILC_SCHEDULE_LOCK(schedule);
82     internal->in_schedule = FALSE;
83     return -1;
84
85   } else if (ready == WAIT_TIMEOUT) {
86     /* Timeout */
87     SILC_LOG_DEBUG(("Timeout"));
88     SILC_SCHEDULE_LOCK(schedule);
89     internal->in_schedule = FALSE;
90     return 0;
91
92   } else if (ready == WAIT_OBJECT_0 + nhandles) {
93     /* Windows messages. The MSDN online says that if the application
94        creates a window then its main loop (and we're assuming that
95        it is our SILC Scheduler) must handle the Windows messages, so do
96        it here as the MSDN suggests. */
97     SILC_LOG_DEBUG(("Dispatch window messages"));
98     while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
99       TranslateMessage(&msg);
100       DispatchMessage(&msg);
101     }
102
103     /* If timeout is set then we must update the timeout since we won't
104        return and we will give the wait another try. */
105     if (timeo != INFINITE) {
106       timeo -= GetTickCount() - curtime;
107       curtime = GetTickCount();
108       if (timeo < 0)
109         timeo = 0;
110     }
111
112     /* Give the wait another try */
113     goto retry;
114
115   } else if (ready >= WAIT_OBJECT_0 && ready < WAIT_OBJECT_0 + nhandles) {
116     /* Some event occurred. */
117     SILC_LOG_DEBUG(("Dispatch events"));
118     SILC_SCHEDULE_LOCK(schedule);
119     internal->in_schedule = FALSE;
120     return silc_list_count(schedule->fd_dispatch) + 1;
121   }
122
123   internal->in_schedule = FALSE;
124   return -1;
125 }
126
127 /* Window callback.  We get here when some event occurs on file descriptor
128    or socket that has been scheduled.  We add them to dispatch queue and
129    notify the scheduler handle them. */
130
131 static LRESULT CALLBACK
132 silc_schedule_wnd_proc(HWND hwnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
133 {
134   SilcSchedule schedule = (SilcSchedule)GetWindowLong(hwnd, GWL_USERDATA);
135   SilcWin32Scheduler internal;
136   SilcUInt32 fd;
137   SilcTaskFd task;
138
139   switch (wMsg) {
140   case SILC_WM_EVENT:
141     internal = (SilcWin32Scheduler)schedule->internal;
142     fd = (SilcUInt32)wParam;
143
144     SILC_LOG_DEBUG(("SILC_WM_EVENT fd %d", fd));
145     SILC_SCHEDULE_LOCK(schedule);
146
147     if (!internal->in_schedule) {
148       /* We are not in scheduler so set up the dispatch queue now */
149       internal->in_schedule = TRUE;
150       silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
151     }
152
153     /* Find task by fd */
154     if (!silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
155                               NULL, (void *)&task)) {
156       SILC_SCHEDULE_UNLOCK(schedule);
157       break;
158     }
159
160     /* Ignore the event if the task not valid anymore */
161     if (!task->header.valid || !task->events) {
162       SILC_SCHEDULE_UNLOCK(schedule);
163       break;
164     }
165
166     /* Handle event */
167     switch (WSAGETSELECTEVENT(lParam)) {
168     case FD_READ:
169     case FD_OOB:
170       SILC_LOG_DEBUG(("FD_READ"));
171       task->revents |= SILC_TASK_READ;
172       silc_list_add(schedule->fd_dispatch, task);
173       break;
174
175     case FD_WRITE:
176       SILC_LOG_DEBUG(("FD_WRITE"));
177       task->revents |= SILC_TASK_WRITE;
178       silc_list_add(schedule->fd_dispatch, task);
179       break;
180
181     case FD_ACCEPT:
182       SILC_LOG_DEBUG(("FD_ACCEPT"));
183       task->revents |= SILC_TASK_READ;
184       silc_list_add(schedule->fd_dispatch, task);
185       break;
186
187     default:
188       break;
189     }
190
191     /* Wakeup scheduler */
192     ReleaseSemaphore(internal->wakeup_sema, 1, NULL);
193
194     SILC_SCHEDULE_UNLOCK(schedule);
195     return TRUE;
196     break;
197
198   default:
199     break;
200   }
201
202   return DefWindowProc(hwnd, wMsg, wParam, lParam);
203 }
204
205 /* Init Winsock2. */
206
207 static SilcBool silc_net_win32_init(void)
208 {
209   int ret, sopt = SO_SYNCHRONOUS_NONALERT;
210   WSADATA wdata;
211   WORD ver = MAKEWORD(2, 2);
212
213   ret = WSAStartup(ver, &wdata);
214   if (ret)
215     return FALSE;
216
217   /* Allow using the SOCKET's as file descriptors so that we can poll
218      them with SILC Scheduler. */
219   ret = setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&sopt,
220                    sizeof(sopt));
221   if (ret)
222     return FALSE;
223
224   return TRUE;
225 }
226
227 /* Uninit Winsock2 */
228
229 static void silc_net_win32_uninit(void)
230 {
231   WSACleanup();
232 }
233
234 /* Initializes the platform specific scheduler.  This for example initializes
235    the wakeup mechanism of the scheduler.  In multi-threaded environment
236    the scheduler needs to be wakenup when tasks are added or removed from
237    the task queues.  Returns context to the platform specific scheduler. */
238
239 void *silc_schedule_internal_init(SilcSchedule schedule, void *app_context)
240 {
241   SilcWin32Scheduler internal;
242   char n[32];
243
244   /* Initialize Winsock */
245   silc_net_win32_init();
246
247   internal = silc_calloc(1, sizeof(*internal));
248   if (!internal)
249     return NULL;
250
251   schedule->max_tasks = MAXIMUM_WAIT_OBJECTS;
252
253   /* Create hidden window.  We need window so that we can use WSAAsyncSelect
254      to set socket events.  */
255   silc_snprintf(n, sizeof(n), "SilcSchedule-%p", schedule);
256   internal->wclass.lpfnWndProc = silc_schedule_wnd_proc;
257   internal->wclass.cbWndExtra = sizeof(schedule);
258   internal->wclass.lpszClassName = (CHAR *)strdup(n);
259   RegisterClass(&internal->wclass);
260   internal->window = CreateWindow((CHAR *)internal->wclass.lpszClassName, "",
261                                   0, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
262   if (!internal->window) {
263     SILC_LOG_ERROR(("Could not create hidden window for scheduler"));
264     DestroyWindow(internal->window);
265     UnregisterClass((CHAR *)n, NULL);
266     silc_free(internal);
267     return NULL;
268   }
269
270   /* Set the scheduler as the window's context */
271   SetWindowLong(internal->window, GWL_USERDATA, (void *)schedule);
272   SetWindowPos(internal->window, HWND_BOTTOM, 0, 0, 0, 0, SWP_FRAMECHANGED);
273
274   internal->wakeup_sema = CreateSemaphore(NULL, 0, 100, NULL);
275   if (!internal->wakeup_sema) {
276     SILC_LOG_ERROR(("Could not create wakeup semaphore for scheduler"));
277     silc_free(internal);
278     return NULL;
279   }
280
281   return (void *)internal;
282 }
283
284 /* Uninitializes the platform specific scheduler context. */
285
286 void silc_schedule_internal_uninit(SilcSchedule schedule, void *context)
287 {
288   SilcWin32Scheduler internal = (SilcWin32Scheduler)context;
289   char n[32];
290
291   if (!internal)
292     return;
293
294   silc_snprintf(n, sizeof(n), "SilcSchedule-%p", schedule);
295   DestroyWindow(internal->window);
296   UnregisterClass((CHAR *)n, NULL);
297
298   CloseHandle(internal->wakeup_sema);
299   silc_net_win32_uninit();
300
301   silc_free(internal);
302 }
303
304 /* Schedule `task' with events `event_mask'. Zero `event_mask' unschedules. */
305
306 SilcBool silc_schedule_internal_schedule_fd(SilcSchedule schedule,
307                                             void *context,
308                                             SilcTaskFd task,
309                                             SilcTaskEvent event_mask)
310 {
311   SilcWin32Scheduler internal = (SilcWin32Scheduler)context;
312   int events = 0;
313
314   if (!internal)
315     return TRUE;
316
317   SILC_LOG_DEBUG(("Scheduling fd %d for events %d", task->fd, event_mask));
318
319   if (event_mask & SILC_TASK_READ)
320     events |= FD_READ | FD_ACCEPT | FD_OOB;
321   if (event_mask & SILC_TASK_WRITE)
322     events |= FD_WRITE;
323
324   /* Schedule for events.  The silc_schedule_wnd_proc will be called to
325      deliver the events for this fd. */
326   WSAAsyncSelect(task->fd, internal->window, SILC_WM_EVENT, events);
327
328   return TRUE;
329 }
330
331 /* Wakes up the scheduler */
332
333 void silc_schedule_internal_wakeup(SilcSchedule schedule, void *context)
334 {
335 #ifdef SILC_THREADS
336   SilcWin32Scheduler internal = (SilcWin32Scheduler)context;
337   ReleaseSemaphore(internal->wakeup_sema, 1, NULL);
338 #endif /* SILC_THREADS */
339 }
340
341 /* Register signal */
342
343 void silc_schedule_internal_signal_register(SilcSchedule schedule,
344                                             void *context,
345                                             SilcUInt32 signal,
346                                             SilcTaskCallback callback,
347                                             void *callback_context)
348 {
349
350 }
351
352 /* Unregister signal */
353
354 void silc_schedule_internal_signal_unregister(SilcSchedule schedule,
355                                               void *context,
356                                               SilcUInt32 signal)
357 {
358
359 }
360
361 /* Call all signals */
362
363 void silc_schedule_internal_signals_call(SilcSchedule schedule,
364                                          void *context)
365 {
366
367 }
368
369 /* Block registered signals in scheduler. */
370
371 void silc_schedule_internal_signals_block(SilcSchedule schedule,
372                                           void *context)
373 {
374
375 }
376
377 /* Unblock registered signals in schedule. */
378
379 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
380                                             void *context)
381 {
382
383 }
384
385 const SilcScheduleOps schedule_ops =
386 {
387   silc_schedule_internal_init,
388   silc_schedule_internal_uninit,
389   silc_select,
390   silc_schedule_internal_schedule_fd,
391   silc_schedule_internal_wakeup,
392   silc_schedule_internal_signal_register,
393   silc_schedule_internal_signal_unregister,
394   silc_schedule_internal_signals_call,
395   silc_schedule_internal_signals_block,
396   silc_schedule_internal_signals_unblock,
397 };