Added SILC Thread Queue 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 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   HANDLE handles[MAXIMUM_WAIT_OBJECTS];
44   DWORD ready, curtime;
45   LONG timeo = INFINITE;
46   UINT timer;
47   MSG msg;
48   int nhandles = 0;
49
50   if (!internal->in_schedule) {
51     internal->in_schedule = TRUE;
52     silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
53   }
54
55   /* Add wakeup semaphore to events */
56   handles[nhandles++] = internal->wakeup_sema;
57
58   /* Get timeout */
59   if (schedule->has_timeout)
60     timeo = ((schedule->timeout.tv_sec * 1000) +
61              (schedule->timeout.tv_usec / 1000));
62
63   SILC_SCHEDULE_UNLOCK(schedule);
64  retry:
65   curtime = GetTickCount();
66   ready = MsgWaitForMultipleObjects(nhandles, handles, FALSE, timeo,
67                                     QS_ALLINPUT);
68
69   if (ready == WAIT_FAILED) {
70     /* Wait failed with error */
71     SILC_LOG_WARNING(("WaitForMultipleObjects() failed"));
72     SILC_SCHEDULE_LOCK(schedule);
73     internal->in_schedule = FALSE;
74     return -1;
75
76   } else if (ready >= WAIT_ABANDONED_0 &&
77              ready < WAIT_ABANDONED_0 + nhandles) {
78     /* Signal abandoned */
79     SILC_LOG_WARNING(("WaitForMultipleObjects() failed (ABANDONED)"));
80     SILC_SCHEDULE_LOCK(schedule);
81     internal->in_schedule = FALSE;
82     return -1;
83
84   } else if (ready == WAIT_TIMEOUT) {
85     /* Timeout */
86     SILC_LOG_DEBUG(("Timeout"));
87     SILC_SCHEDULE_LOCK(schedule);
88     internal->in_schedule = FALSE;
89     return 0;
90
91   } else if (ready == WAIT_OBJECT_0 + nhandles) {
92     /* Windows messages. The MSDN online says that if the application
93        creates a window then its main loop (and we're assuming that
94        it is our SILC Scheduler) must handle the Windows messages, so do
95        it here as the MSDN suggests. */
96     SILC_LOG_DEBUG(("Dispatch window messages"));
97     while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
98       TranslateMessage(&msg);
99       DispatchMessage(&msg);
100     }
101
102     /* If timeout is set then we must update the timeout since we won't
103        return and we will give the wait another try. */
104     if (timeo != INFINITE) {
105       timeo -= GetTickCount() - curtime;
106       curtime = GetTickCount();
107       if (timeo < 0)
108         timeo = 0;
109     }
110
111     /* Give the wait another try */
112     goto retry;
113
114   } else if (ready >= WAIT_OBJECT_0 && ready < WAIT_OBJECT_0 + nhandles) {
115     /* Some event occurred. */
116     SILC_LOG_DEBUG(("Dispatch events"));
117     SILC_SCHEDULE_LOCK(schedule);
118     internal->in_schedule = FALSE;
119     return silc_list_count(schedule->fd_dispatch) + 1;
120   }
121
122   internal->in_schedule = FALSE;
123   return -1;
124 }
125
126 /* Window callback.  We get here when some event occurs on file descriptor
127    or socket that has been scheduled.  We add them to dispatch queue and
128    notify the scheduler to handle them. */
129
130 static LRESULT CALLBACK
131 silc_schedule_wnd_proc(HWND hwnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
132 {
133   SilcSchedule schedule = (SilcSchedule)GetWindowLongPtr(hwnd, GWL_USERDATA);
134   SilcWin32Scheduler internal;
135   SilcUInt32 fd;
136   SilcTaskFd task;
137
138   switch (wMsg) {
139   case SILC_WM_EVENT:
140     internal = (SilcWin32Scheduler)schedule->internal;
141     fd = (SilcUInt32)wParam;
142
143     SILC_LOG_DEBUG(("SILC_WM_EVENT fd %d", fd));
144     SILC_SCHEDULE_LOCK(schedule);
145
146     if (!internal->in_schedule) {
147       /* We are not in scheduler so set up the dispatch queue now */
148       internal->in_schedule = TRUE;
149       silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
150     }
151
152     /* Find task by fd */
153     if (!silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
154                               NULL, (void *)&task)) {
155       SILC_SCHEDULE_UNLOCK(schedule);
156       break;
157     }
158
159     /* Ignore the event if the task is not valid anymore */
160     if (!task->header.valid || !task->events) {
161       SILC_SCHEDULE_UNLOCK(schedule);
162       break;
163     }
164     task->revents = 0;
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   SetWindowLongPtr(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   task->revents = 0;
328
329   return TRUE;
330 }
331
332 /* Wakes up the scheduler */
333
334 void silc_schedule_internal_wakeup(SilcSchedule schedule, void *context)
335 {
336 #ifdef SILC_THREADS
337   SilcWin32Scheduler internal = (SilcWin32Scheduler)context;
338   ReleaseSemaphore(internal->wakeup_sema, 1, NULL);
339 #endif /* SILC_THREADS */
340 }
341
342 /* Register signal */
343
344 void silc_schedule_internal_signal_register(SilcSchedule schedule,
345                                             void *context,
346                                             SilcUInt32 signal,
347                                             SilcTaskCallback callback,
348                                             void *callback_context)
349 {
350
351 }
352
353 /* Unregister signal */
354
355 void silc_schedule_internal_signal_unregister(SilcSchedule schedule,
356                                               void *context,
357                                               SilcUInt32 signal)
358 {
359
360 }
361
362 /* Call all signals */
363
364 void silc_schedule_internal_signals_call(SilcSchedule schedule,
365                                          void *context)
366 {
367
368 }
369
370 /* Block registered signals in scheduler. */
371
372 void silc_schedule_internal_signals_block(SilcSchedule schedule,
373                                           void *context)
374 {
375
376 }
377
378 /* Unblock registered signals in schedule. */
379
380 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
381                                             void *context)
382 {
383
384 }
385
386 const SilcScheduleOps schedule_ops =
387 {
388   silc_schedule_internal_init,
389   silc_schedule_internal_uninit,
390   silc_select,
391   silc_schedule_internal_schedule_fd,
392   silc_schedule_internal_wakeup,
393   silc_schedule_internal_signal_register,
394   silc_schedule_internal_signal_unregister,
395   silc_schedule_internal_signals_call,
396   silc_schedule_internal_signals_block,
397   silc_schedule_internal_signals_unblock,
398 };