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