Author: Pekka Riikonen <priikone@silcnet.org>
- Copyright (C) 2001 Pekka Riikonen
+ Copyright (C) 2001 - 2007 Pekka Riikonen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
+ the Free Software Foundation; version 2 of the License.
+
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
*/
/* $Id$ */
-#include "silcincludes.h"
-
-/* Our "select()" for WIN32. This mimics the behaviour of select() system
- call. It does not call the Winsock's select() though. Its functions
- are derived from GLib's g_poll() and from some old Xemacs's sys_select().
-
- This makes following assumptions, which I don't know whether they
- are correct or not:
-
- o writefds are ignored, if set this will return immediately.
- o exceptfds are ignored totally
- o If all arguments except timeout are NULL then this will register
- a timeout with SetTimer and will wait just for Windows messages
- with WaitMessage.
- o MsgWaitForMultipleObjects is used to wait all kind of events, this
- includes SOCKETs and Windows messages.
- o All Windows messages are dispatched from this function.
- o The Operating System has Winsock 2.
-
- References:
+#include "silc.h"
- o http://msdn.microsoft.com/library/default.asp?
- url=/library/en-us/winui/hh/winui/messques_77zk.asp
- o http://msdn.microsoft.com/library/default.asp?
- url=/library/en-us/winsock/hh/winsock/apistart_9g1e.asp
- o http://msdn.microsoft.com/library/default.asp?
- url=/library/en-us/dnmgmt/html/msdn_getpeek.asp
- o http://developer.novell.com/support/winsock/doc/toc.htm
+const SilcScheduleOps schedule_ops;
-*/
+#define SILC_WM_EVENT WM_USER + 1
-int silc_select(int n, fd_set *readfds, fd_set *writefds,
- fd_set *exceptfds, struct timeval *timeout)
+typedef struct {
+ HWND window; /* Hidden window for receiving socket events */
+ WNDCLASS wclass; /* Window class */
+ HANDLE wakeup_sema; /* Scheduler wakeup semaphore */
+ unsigned int in_schedule : 1;
+} *SilcWin32Scheduler;
+
+/* Our select() call. This simply waits for some events to happen. It also
+ dispatches window messages so it can be used as the main loop of Windows
+ application. This doesn't wait for fds or sockets but does receive
+ notifications via wakeup semaphore when event occurs on some fd or socket.
+ The fds and sockets are scheduled via WSAAsyncSelect. */
+
+int silc_select(SilcSchedule schedule, void *context)
{
+ SilcWin32Scheduler internal = (SilcWin32Scheduler)context;
HANDLE handles[MAXIMUM_WAIT_OBJECTS];
- DWORD ready, curtime, timeo;
- int nhandles = 0, i;
+ DWORD ready, curtime;
+ LONG timeo = INFINITE;
+ UINT timer;
MSG msg;
+ int nhandles = 0;
- /* Check fd sets (ignoring the exceptfds) */
- if (readfds) {
- for (i = 0; i < n; i++)
- if (FD_ISSET(i, readfds))
- handles[nhandles++] = (HANDLE)i;
-
- FD_ZERO(readfds);
+ if (!internal->in_schedule) {
+ internal->in_schedule = TRUE;
+ silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
}
- /* If writefds is set then return immediately */
- if (writefds) {
- for (i = 0; i < n; i++)
- if (FD_ISSET(i, writefds))
- return 1;
- }
+ /* Add wakeup semaphore to events */
+ handles[nhandles++] = internal->wakeup_sema;
- timeo = (timeout ? (timeout->tv_sec * 1000) + (timeout->tv_usec / 1000) :
- INFINITE);
-
- /* If we have nothing to wait and timeout is set then register a timeout
- and wait just for windows messages. */
- if (nhandles == 0 && timeout) {
- UINT timer = SetTimer(NULL, 0, timeo, NULL);
- curtime = GetTickCount();
- while (timer) {
- WaitMessage();
-
- while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
- if (msg.message == WM_TIMER) {
- KillTimer(NULL, timer);
- return 0;
- }
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
-
- KillTimer(NULL, timer);
- if (timeo != INFINITE) {
- timeo -= GetTickCount() - curtime;
- if (timeo < 0)
- timeo = 0;
- }
- timer = SetTimer(NULL, 0, timeo, NULL);
- }
- }
+ /* Get timeout */
+ if (schedule->has_timeout)
+ timeo = ((schedule->timeout.tv_sec * 1000) +
+ (schedule->timeout.tv_usec / 1000));
+ SILC_SCHEDULE_UNLOCK(schedule);
retry:
curtime = GetTickCount();
- ready = MsgWaitForMultipleObjects(nhandles, handles, FALSE, timeo,
+ ready = MsgWaitForMultipleObjects(nhandles, handles, FALSE, timeo,
QS_ALLINPUT);
if (ready == WAIT_FAILED) {
/* Wait failed with error */
SILC_LOG_WARNING(("WaitForMultipleObjects() failed"));
+ SILC_SCHEDULE_LOCK(schedule);
+ internal->in_schedule = FALSE;
return -1;
} else if (ready >= WAIT_ABANDONED_0 &&
ready < WAIT_ABANDONED_0 + nhandles) {
/* Signal abandoned */
SILC_LOG_WARNING(("WaitForMultipleObjects() failed (ABANDONED)"));
+ SILC_SCHEDULE_LOCK(schedule);
+ internal->in_schedule = FALSE;
return -1;
+
} else if (ready == WAIT_TIMEOUT) {
/* Timeout */
+ SILC_LOG_DEBUG(("Timeout"));
+ SILC_SCHEDULE_LOCK(schedule);
+ internal->in_schedule = FALSE;
return 0;
+
} else if (ready == WAIT_OBJECT_0 + nhandles) {
/* Windows messages. The MSDN online says that if the application
creates a window then its main loop (and we're assuming that
it is our SILC Scheduler) must handle the Windows messages, so do
it here as the MSDN suggests. */
+ SILC_LOG_DEBUG(("Dispatch window messages"));
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
}
/* If timeout is set then we must update the timeout since we won't
return and we will give the wait another try. */
if (timeo != INFINITE) {
timeo -= GetTickCount() - curtime;
+ curtime = GetTickCount();
if (timeo < 0)
timeo = 0;
}
/* Give the wait another try */
- goto retry;
- } else if (ready >= WAIT_OBJECT_0 && ready < WAIT_OBJECT_0 + nhandles &&
- readfds) {
- /* Some other event, like SOCKET or something. */
-
- /* Go through all fds even though only one was set. This is to avoid
- starvation of high numbered fds. */
- ready -= WAIT_OBJECT_0;
- i = 0;
- do {
- /* Set the handle to fd set */
- FD_SET((int)handles[ready], readfds);
- i++;
-
- /* Check the status of the next handle and set it's fd to the fd
- set if data is available. */
- while (++ready < n)
- if (WaitForSingleObject(handles[ready], 0) == WAIT_OBJECT_0)
- break;
- } while (ready < n);
-
- return i;
+ goto retry;
+
+ } else if (ready >= WAIT_OBJECT_0 && ready < WAIT_OBJECT_0 + nhandles) {
+ /* Some event occurred. */
+ SILC_LOG_DEBUG(("Dispatch events"));
+ SILC_SCHEDULE_LOCK(schedule);
+ internal->in_schedule = FALSE;
+ return silc_list_count(schedule->fd_dispatch) + 1;
}
+ internal->in_schedule = FALSE;
return -1;
}
-#ifdef SILC_THREADS
+/* Window callback. We get here when some event occurs on file descriptor
+ or socket that has been scheduled. We add them to dispatch queue and
+ notify the scheduler to handle them. */
-/* Internal wakeup context. */
-typedef struct {
- HANDLE wakeup_sema;
- SilcTask wakeup_task;
-} *SilcWin32Wakeup;
+static LRESULT CALLBACK
+silc_schedule_wnd_proc(HWND hwnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
+{
+ SilcSchedule schedule = (SilcSchedule)GetWindowLongPtr(hwnd, GWL_USERDATA);
+ SilcWin32Scheduler internal;
+ SilcUInt32 fd;
+ SilcTaskFd task;
+
+ switch (wMsg) {
+ case SILC_WM_EVENT:
+ internal = (SilcWin32Scheduler)schedule->internal;
+ fd = (SilcUInt32)wParam;
+
+ SILC_LOG_DEBUG(("SILC_WM_EVENT fd %d", fd));
+ SILC_SCHEDULE_LOCK(schedule);
+
+ if (!internal->in_schedule) {
+ /* We are not in scheduler so set up the dispatch queue now */
+ internal->in_schedule = TRUE;
+ silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
+ }
+
+ /* Find task by fd */
+ if (!silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
+ NULL, (void *)&task)) {
+ SILC_SCHEDULE_UNLOCK(schedule);
+ break;
+ }
-SILC_TASK_CALLBACK(silc_schedule_wakeup_cb)
+ /* Ignore the event if the task is not valid anymore */
+ if (!task->header.valid || !task->events) {
+ SILC_SCHEDULE_UNLOCK(schedule);
+ break;
+ }
+ task->revents = 0;
+
+ /* Handle event */
+ switch (WSAGETSELECTEVENT(lParam)) {
+ case FD_READ:
+ case FD_OOB:
+ SILC_LOG_DEBUG(("FD_READ"));
+ task->revents |= SILC_TASK_READ;
+ silc_list_add(schedule->fd_dispatch, task);
+ break;
+
+ case FD_WRITE:
+ SILC_LOG_DEBUG(("FD_WRITE"));
+ task->revents |= SILC_TASK_WRITE;
+ silc_list_add(schedule->fd_dispatch, task);
+ break;
+
+ case FD_ACCEPT:
+ SILC_LOG_DEBUG(("FD_ACCEPT"));
+ task->revents |= SILC_TASK_READ;
+ silc_list_add(schedule->fd_dispatch, task);
+ break;
+
+ default:
+ break;
+ }
+
+ /* Wakeup scheduler */
+ ReleaseSemaphore(internal->wakeup_sema, 1, NULL);
+
+ SILC_SCHEDULE_UNLOCK(schedule);
+ return TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ return DefWindowProc(hwnd, wMsg, wParam, lParam);
+}
+
+/* Init Winsock2. */
+
+static SilcBool silc_net_win32_init(void)
{
- /* Nothing */
+ int ret, sopt = SO_SYNCHRONOUS_NONALERT;
+ WSADATA wdata;
+ WORD ver = MAKEWORD(2, 2);
+
+ ret = WSAStartup(ver, &wdata);
+ if (ret)
+ return FALSE;
+
+ /* Allow using the SOCKET's as file descriptors so that we can poll
+ them with SILC Scheduler. */
+ ret = setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&sopt,
+ sizeof(sopt));
+ if (ret)
+ return FALSE;
+
+ return TRUE;
}
-#endif /* SILC_THREADS */
+/* Uninit Winsock2 */
-/* Initializes the wakeup of the scheduler. In multi-threaded environment
+static void silc_net_win32_uninit(void)
+{
+ WSACleanup();
+}
+
+/* Initializes the platform specific scheduler. This for example initializes
+ the wakeup mechanism of the scheduler. In multi-threaded environment
the scheduler needs to be wakenup when tasks are added or removed from
- the task queues. This will initialize the wakeup for the scheduler.
- Any tasks that needs to be registered must be registered to the `queue'.
- It is guaranteed that the scheduler will automatically free any
- registered tasks in this queue. This is system specific routine. */
+ the task queues. Returns context to the platform specific scheduler. */
-void *silc_schedule_wakeup_init(SilcSchedule schedule)
+void *silc_schedule_internal_init(SilcSchedule schedule, void *app_context)
{
-#ifdef SILC_THREADS
- SilcWin32Wakeup wakeup;
+ SilcWin32Scheduler internal;
+ char n[32];
- wakeup = silc_calloc(1, sizeof(*wakeup));
+ /* Initialize Winsock */
+ silc_net_win32_init();
- wakeup->wakeup_sema = CreateSemaphore(NULL, 0, 100, NULL);
- if (!wakeup->wakeup_sema) {
- silc_free(wakeup);
+ internal = silc_calloc(1, sizeof(*internal));
+ if (!internal)
+ return NULL;
+
+ schedule->max_tasks = MAXIMUM_WAIT_OBJECTS;
+
+ /* Create hidden window. We need window so that we can use WSAAsyncSelect
+ to set socket events. */
+ silc_snprintf(n, sizeof(n), "SilcSchedule-%p", schedule);
+ internal->wclass.lpfnWndProc = silc_schedule_wnd_proc;
+ internal->wclass.cbWndExtra = sizeof(schedule);
+ internal->wclass.lpszClassName = (CHAR *)strdup(n);
+ RegisterClass(&internal->wclass);
+ internal->window = CreateWindow((CHAR *)internal->wclass.lpszClassName, "",
+ 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
+ if (!internal->window) {
+ SILC_LOG_ERROR(("Could not create hidden window for scheduler"));
+ DestroyWindow(internal->window);
+ UnregisterClass((CHAR *)n, NULL);
+ silc_free(internal);
return NULL;
}
- wakeup->wakeup_task =
- silc_schedule_task_add(schedule, (int)wakeup->wakeup_sema,
- silc_schedule_wakeup_cb, wakeup,
- 0, 0, SILC_TASK_FD,
- SILC_TASK_PRI_NORMAL);
- if (!wakeup->wakeup_task) {
- CloseHandle(wakeup->wakeup_sema);
- silc_free(wakeup);
+ /* Set the scheduler as the window's context */
+ SetWindowLongPtr(internal->window, GWL_USERDATA, (void *)schedule);
+ SetWindowPos(internal->window, HWND_BOTTOM, 0, 0, 0, 0, SWP_FRAMECHANGED);
+
+ internal->wakeup_sema = CreateSemaphore(NULL, 0, 100, NULL);
+ if (!internal->wakeup_sema) {
+ SILC_LOG_ERROR(("Could not create wakeup semaphore for scheduler"));
+ silc_free(internal);
return NULL;
}
- return (void *)wakeup;
-#else
- return NULL;
-#endif
+ return (void *)internal;
}
-/* Uninitializes the system specific wakeup. */
+/* Uninitializes the platform specific scheduler context. */
-void silc_schedule_wakeup_uninit(void *context)
+void silc_schedule_internal_uninit(SilcSchedule schedule, void *context)
{
-#ifdef SILC_THREADS
- SilcWin32Wakeup wakeup = (SilcWin32Wakeup)context;
+ SilcWin32Scheduler internal = (SilcWin32Scheduler)context;
+ char n[32];
- if (!wakeup)
+ if (!internal)
return;
- CloseHandle(wakeup->wakeup_sema);
- silc_free(wakeup);
-#endif
+ silc_snprintf(n, sizeof(n), "SilcSchedule-%p", schedule);
+ DestroyWindow(internal->window);
+ UnregisterClass((CHAR *)n, NULL);
+
+ CloseHandle(internal->wakeup_sema);
+ silc_net_win32_uninit();
+
+ silc_free(internal);
+}
+
+/* Schedule `task' with events `event_mask'. Zero `event_mask' unschedules. */
+
+SilcBool silc_schedule_internal_schedule_fd(SilcSchedule schedule,
+ void *context,
+ SilcTaskFd task,
+ SilcTaskEvent event_mask)
+{
+ SilcWin32Scheduler internal = (SilcWin32Scheduler)context;
+ int events = 0;
+
+ if (!internal)
+ return TRUE;
+
+ SILC_LOG_DEBUG(("Scheduling fd %d for events %d", task->fd, event_mask));
+
+ if (event_mask & SILC_TASK_READ)
+ events |= FD_READ | FD_ACCEPT | FD_OOB;
+ if (event_mask & SILC_TASK_WRITE)
+ events |= FD_WRITE;
+
+ /* Schedule for events. The silc_schedule_wnd_proc will be called to
+ deliver the events for this fd. */
+ WSAAsyncSelect(task->fd, internal->window, SILC_WM_EVENT, events);
+ task->revents = 0;
+
+ return TRUE;
}
/* Wakes up the scheduler */
-void silc_schedule_wakeup_internal(void *context)
+void silc_schedule_internal_wakeup(SilcSchedule schedule, void *context)
{
#ifdef SILC_THREADS
- SilcWin32Wakeup wakeup = (SilcWin32Wakeup)context;
+ SilcWin32Scheduler internal = (SilcWin32Scheduler)context;
+ ReleaseSemaphore(internal->wakeup_sema, 1, NULL);
+#endif /* SILC_THREADS */
+}
- if (!wakeup)
- return;
+/* Register signal */
+
+void silc_schedule_internal_signal_register(SilcSchedule schedule,
+ void *context,
+ SilcUInt32 signal,
+ SilcTaskCallback callback,
+ void *callback_context)
+{
+
+}
+
+/* Unregister signal */
+
+void silc_schedule_internal_signal_unregister(SilcSchedule schedule,
+ void *context,
+ SilcUInt32 signal)
+{
+
+}
+
+/* Call all signals */
+
+void silc_schedule_internal_signals_call(SilcSchedule schedule,
+ void *context)
+{
- ReleaseSemaphore(wakeup->wakeup_sema, 1, NULL);
-#endif
}
+
+/* Block registered signals in scheduler. */
+
+void silc_schedule_internal_signals_block(SilcSchedule schedule,
+ void *context)
+{
+
+}
+
+/* Unblock registered signals in schedule. */
+
+void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
+ void *context)
+{
+
+}
+
+const SilcScheduleOps schedule_ops =
+{
+ silc_schedule_internal_init,
+ silc_schedule_internal_uninit,
+ silc_select,
+ silc_schedule_internal_schedule_fd,
+ silc_schedule_internal_wakeup,
+ silc_schedule_internal_signal_register,
+ silc_schedule_internal_signal_unregister,
+ silc_schedule_internal_signals_call,
+ silc_schedule_internal_signals_block,
+ silc_schedule_internal_signals_unblock,
+};