From: Pekka Riikonen Date: Sun, 30 Dec 2007 12:47:31 +0000 (+0000) Subject: Added asynchronous event tasks to SILC Scheduler. Added X-Git-Tag: 1.2.beta1~47 X-Git-Url: http://git.silcnet.org/gitweb/?p=crypto.git;a=commitdiff_plain;h=60180da59ffdbbd12058dded66e3c8a547cd0852 Added asynchronous event tasks to SILC Scheduler. Added concept of parent and child schedulers. --- diff --git a/CHANGES.RUNTIME b/CHANGES.RUNTIME index 05b131c5..33f5b70d 100644 --- a/CHANGES.RUNTIME +++ b/CHANGES.RUNTIME @@ -1,3 +1,32 @@ +Sun Dec 30 14:35:33 EET 2007 Pekka Riikonen + + * Implemented asynchronous events to SILC Scheduler. Added + silc_schedule_task_add_event, silc_schedule_event_connect, + silc_schedule_event_dissconnect, silc_schedule_task_del_event + and silc_schedule_event_signal. Affected files are + lib/silcutil/silcschedule.[ch], silcschedule_i.h. + + * Added concept of child and parent scheduler to SILC Scheduler + API. silc_schedule_init now takes optional parent argument. + Each child scheduler is still independent, only the event tasks + are shared among parent and children. Affected files are + lib/silcutil/silcschedule.[ch]. + + * The SILC FSM real thread now adds the created SilcSchedule + as the thread's global scheduler. Affected file is + lib/silcutil/silcfsm.[ch]. + + * Moved generic string and data hashing and comparison functions + from lib/silcutil/silcutil.[ch] to lib/silcutil/silchashtable.[ch] + as they are usable by the hash table. Added case sensitive + and insensitive string hashing and comparison funtions. + Changed string and data hashing to use Bob Jenkin's one-at-a-time + hash function. + + * Moved SILC_PARAM_* types from silcbuffmt.h to silctypes.h + under a generic SilcParam type. Affected files are + lib/silcutil/silcbuffmt.[ch] and lib/silcutil/silctypes.h. + Wed Dec 26 13:10:30 EET 2007 Pekka Riikonen * Added silc_schedule_[set|get]_global to diff --git a/TODO b/TODO index 31f8c01a..4c61e988 100644 --- a/TODO +++ b/TODO @@ -109,49 +109,10 @@ Runtime library, lib/silcutil/ o The SILC Event signals. Asynchronous events that can be created, connected to and signalled. Either own event routines or glued into - SilcSchedule: - - SilcTask silc_schedule_task_add_event(SilcSchedule schedule, - const char *event, ...); - SilcBool silc_schedule_event_connect(SilcSchedule schedule, - const char *event, - SilcTaskCallback event_callback, - void *context); - SilcBool silc_schedule_event_signal(SilcSchedule schedule, - const char *event, ...); - - Example: - silc_schedule_task_add_event(schedule, "connected", - SILC_PARAM_UI32_INT, - SILC_PARAM_BUFFER, - SILC_PARAM_END); - silc_schedule_event_connect(schedule, "connected", connected_cb, ctx); - silc_schedule_event_signal(schedule, "connected", integer, buf, - SILC_PARAM_END); - SILC_TASK_CALLBACK(connected_cb) - { - FooCtx ctx = context; - va_list args; - SilcUInt32 integer; - SilcBuffer buf; - - va_start(args, context); - integer = va_arg(args, SilcUInt32); - buf = va_arg(args, SilcBuffer); - va_end(args); - ... - } - - Problems: Events would be SilcSchedule specific, and would not work on - multi-thread/multi-scheduler system. The events should be copyable - between schedulers. Another problem is the signal delivery. Do we - deliver them synchronously possibly from any thread to any other thread - or do we deliver them through the target schedulers. If we use the - schedulers then signalling would be asynchronous (data must be - duplicated and later freed) which is not very nice. + SilcSchedule. (***DONE) o If the event signals are added, the SILC_PARAM_* stuff needs to be - moved from silcbuffmt.h to silctypes.h or something similar. + moved from silcbuffmt.h to silctypes.h or something similar. (***DONE) o In case the SILC Events are done we shall create a new concept of parent and child SilcSchedule's. When new SilcSchedule is created a @@ -164,10 +125,7 @@ Runtime library, lib/silcutil/ would be linked and could be accessed from any of the schedulers. It should be possible to retrieve the parent and enumerate all children from any of the schedulers. - - SilcSchedule silc_schedule_init(int max_tasks, void *app_context, - SilcSchedule parent); - SilcSchedule silc_schedule_get_parent(SilcSchedule schedule); + (***DONE) o Additional scheduler changes: optimize silc_schedule_wakeup. Wakeup only if the scheduler is actually waiting something. If it is @@ -209,25 +167,7 @@ Runtime library, lib/silcutil/ o silc_malloc et. al. to respect --with-alignment. - o Add '%@' format to silc_snprintf functions. It marks for external - rendering function of following type: - - /* Snprintf rendering function. The `data' is rendered into a string - and allocated string is returned. If NULL is returned the - rendering is skipped and ignored. If the returned string does - not fit to the destination buffer it may be truncated. */ - typedef char *(*SilcSnprintfRender)(void *data); - - It can work like following: - - char *id_renderer(void *data) - { - char tmp[32]; - id_to_str(tmp, sizeof(tmp), (SilcID *)data); - return strdup(tmp); - } - - silc_snprintf(buf, sizeof(buf), "Client ID %@", id_renderer, client_id); + o Add '%@' format to silc_snprintf functions. (***DONE) o SILC Tls (Thread-local storage) API to lib/silcutil/silcthread.[ch]. @@ -347,6 +287,10 @@ Crypto Library, lib/silccrypt/ All PKCS routines should now take callbacks as argument and they should be delivered to SilcPKCSObject and SilcPKCSAlgorithm too. (***DONE) + o The asynchronous functions to perhaps to _async to preserve backwards + compatibility with synchronous versions, and make easier to migrate + from 1.1 to 1.2. + o Change PKCS Algorithm API to take SilcPKCSAlgorithm as argument to encrypt, decrypt, sign and verify functions. We may need to for exmaple check the alg->hash, supported hash functions. Maybe deliver it also diff --git a/lib/silcclient/client.c b/lib/silcclient/client.c index 1a925206..c27d055e 100644 --- a/lib/silcclient/client.c +++ b/lib/silcclient/client.c @@ -1040,7 +1040,7 @@ SilcBool silc_client_init(SilcClient client, const char *username, silc_rng_global_init(client->rng); /* Initialize the scheduler */ - client->schedule = silc_schedule_init(0, client, NULL); + client->schedule = silc_schedule_init(0, client, NULL, NULL); if (!client->schedule) return FALSE; diff --git a/lib/silcutil/silcfsm.c b/lib/silcutil/silcfsm.c index 5e6fe2a5..2ded9832 100644 --- a/lib/silcutil/silcfsm.c +++ b/lib/silcutil/silcfsm.c @@ -770,12 +770,15 @@ void *silc_fsm_thread(void *context) /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule cannot be used in this thread. Application may still use it if it wants but we use our own. */ - fsm->schedule = silc_schedule_init(0, old, silc_schedule_get_stack(old)); + fsm->schedule = silc_schedule_init(0, old, silc_schedule_get_stack(old), old); if (silc_unlikely(!fsm->schedule)) { fsm->schedule = old; return NULL; } + /* The new scheduler is a global scheduler in this thread */ + silc_schedule_set_global(fsm->schedule); + /* Start the FSM thread */ if (silc_unlikely(!silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_run, fsm, 0, 0))) { @@ -787,6 +790,9 @@ void *silc_fsm_thread(void *context) /* Run the scheduler */ silc_schedule(fsm->schedule); + /* Reset global scheduler */ + silc_schedule_set_global(NULL); + /* Free resources */ silc_schedule_uninit(fsm->schedule); diff --git a/lib/silcutil/silcfsm.h b/lib/silcutil/silcfsm.h index 366edf70..503b8f43 100644 --- a/lib/silcutil/silcfsm.h +++ b/lib/silcutil/silcfsm.h @@ -498,8 +498,11 @@ SilcBool silc_fsm_init(SilcFSM fsm, * for the FSM thread. If you need scheduler in the real thread it is * strongly recommended that you use the SilcSchedule that is allocated * for the thread. You can retrieve the SilcSchedule from the thread - * using silc_fsm_get_schedule function. Note that, the allocated - * SilcSchedule will become invalid after the thread finishes. + * using silc_fsm_get_schedule function. The new scheduler is a child + * scheduler of the original scheduler used with `fsm'. Note that, the + * allocated SilcSchedule will become invalid after the thread finishes. + * Note also that the scheduler is automatically set as global scheduler + * in that thread by calling silc_schedule_set_global. * * If `real_thread' is FALSE the silc_fsm_get_schedule will return * the SilcSchedule that was originally given to silc_fsm_alloc or diff --git a/lib/silcutil/silcschedule.c b/lib/silcutil/silcschedule.c index 476d11f1..5550181a 100644 --- a/lib/silcutil/silcschedule.c +++ b/lib/silcutil/silcschedule.c @@ -22,6 +22,14 @@ /************************** Types and definitions ***************************/ +/* Connected event context */ +typedef struct SilcScheduleEventConnectionStruct { + SilcSchedule schedule; + SilcTaskEventCallback callback; + void *context; + struct SilcScheduleEventConnectionStruct *next; +} *SilcScheduleEventConnection; + /* Platform specific implementation */ extern const SilcScheduleOps schedule_ops; @@ -29,7 +37,7 @@ static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task); static void silc_schedule_dispatch_fd(SilcSchedule schedule); static void silc_schedule_dispatch_timeout(SilcSchedule schedule, SilcBool dispatch_all); - +SILC_TASK_CALLBACK(silc_schedule_event_del_timeout); /************************ Static utility functions **************************/ @@ -196,10 +204,11 @@ static void silc_schedule_select_timeout(SilcSchedule schedule) static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task) { - SilcTaskFd ftask; + SilcSchedule parent; if (silc_unlikely(task == SILC_ALL_TASKS)) { SilcTask task; + SilcEventTask etask; SilcHashTableList htl; void *fd; @@ -216,19 +225,59 @@ static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task) silc_free(task); } + /* Delete even tasks */ + parent = silc_schedule_get_parent(schedule); + silc_hash_table_list(parent->events, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&etask)) { + silc_hash_table_del_by_context(parent->events, etask->event, etask); + silc_free(etask->event); + silc_free(etask); + } + silc_hash_table_list_reset(&htl); return; } - if (silc_likely(task->type == 1)) { - /* Delete from timeout queue */ - silc_list_del(schedule->timeout_queue, task); + switch (task->type) { + case SILC_TASK_FD: + { + /* Delete from fd queue */ + SilcTaskFd ftask = (SilcTaskFd)task; + silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(ftask->fd)); + } + break; - /* Put to free list */ - silc_list_add(schedule->free_tasks, task); - } else { - /* Delete from fd queue */ - ftask = (SilcTaskFd)task; - silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(ftask->fd)); + case SILC_TASK_TIMEOUT: + { + /* Delete from timeout queue */ + silc_list_del(schedule->timeout_queue, task); + + /* Put to free list */ + silc_list_add(schedule->free_tasks, task); + } + break; + + case SILC_TASK_EVENT: + { + SilcEventTask etask = (SilcEventTask)task; + SilcScheduleEventConnection conn; + + parent = silc_schedule_get_parent(schedule); + + /* Delete event */ + silc_hash_table_del_by_context(parent->events, etask->event, etask); + + /* Remove all connections */ + silc_list_start(etask->connections); + while ((conn = silc_list_get(etask->connections))) + silc_free(conn); + + silc_free(etask->event); + silc_free(etask); + } + break; + + default: + break; } } @@ -313,7 +362,7 @@ void silc_schedule_stats(SilcSchedule schedule) context that is delivered to task callbacks. */ SilcSchedule silc_schedule_init(int max_tasks, void *app_context, - SilcStack stack) + SilcStack stack, SilcSchedule parent) { SilcSchedule schedule; @@ -343,10 +392,15 @@ SilcSchedule silc_schedule_init(int max_tasks, void *app_context, silc_list_init(schedule->timeout_queue, struct SilcTaskStruct, next); silc_list_init(schedule->free_tasks, struct SilcTaskStruct, next); + /* Get the parent */ + if (parent && parent->parent) + parent = parent->parent; + schedule->stack = stack; schedule->app_context = app_context; schedule->valid = TRUE; schedule->max_tasks = max_tasks; + schedule->parent = parent; /* Allocate scheduler lock */ silc_mutex_alloc(&schedule->lock); @@ -552,6 +606,13 @@ void silc_schedule_wakeup(SilcSchedule schedule) #endif } +/* Returns parent scheduler */ + +SilcSchedule silc_schedule_get_parent(SilcSchedule schedule) +{ + return schedule->parent ? schedule->parent : schedule; +} + /* Returns the application specific context that was saved into the scheduler in silc_schedule_init function. The context is also returned to application in task callback functions, but this function @@ -775,6 +836,8 @@ SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd, SilcBool silc_schedule_task_del(SilcSchedule schedule, SilcTask task) { + SilcSchedule parent; + if (!schedule) { schedule = silc_schedule_get_global(); SILC_VERIFY(schedule); @@ -815,20 +878,34 @@ SilcBool silc_schedule_task_del(SilcSchedule schedule, SilcTask task) schedule->notify_context); } + /* Delete even tasks */ + parent = silc_schedule_get_parent(schedule); + silc_hash_table_list(parent->events, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&task)) + task->valid = FALSE; + silc_hash_table_list_reset(&htl); + SILC_SCHEDULE_UNLOCK(schedule); return TRUE; } - SILC_LOG_DEBUG(("Unregistering task %p", task)); + SILC_LOG_DEBUG(("Unregistering task %p, type %d", task, task->type)); SILC_SCHEDULE_LOCK(schedule); task->valid = FALSE; /* Call notify callback */ - if (schedule->notify) - schedule->notify(schedule, FALSE, task, !task->type, 0, 0, 0, 0, - schedule->notify_context); + if (schedule->notify && task->type != SILC_TASK_EVENT) + schedule->notify(schedule, FALSE, task, task->type == SILC_TASK_FD, + 0, 0, 0, 0, schedule->notify_context); SILC_SCHEDULE_UNLOCK(schedule); + if (task->type == SILC_TASK_EVENT) { + /* Schedule removal of deleted event task */ + parent = silc_schedule_get_parent(schedule); + silc_schedule_task_add_timeout(parent, silc_schedule_event_del_timeout, + task, 0, 1); + } + return TRUE; } @@ -1140,3 +1217,365 @@ void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd) { silc_schedule_set_listen_fd(schedule, fd, 0, FALSE); } + +/*************************** Asynchronous Events ****************************/ + +/* Add event */ + +SilcTask silc_schedule_task_add_event(SilcSchedule schedule, + const char *event, ...) +{ + SilcEventTask task; + SilcSchedule parent; + + if (!schedule) { + schedule = silc_schedule_get_global(); + SILC_VERIFY(schedule); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return NULL; + } + } + + /* Get parent scheduler */ + parent = silc_schedule_get_parent(schedule); + + SILC_LOG_DEBUG(("Adding event '%s' to scheduler %p", event, parent)); + + SILC_SCHEDULE_LOCK(parent); + + /* Create events hash table if not already done */ + if (!parent->events) { + parent->events = silc_hash_table_alloc(NULL, 3, + silc_hash_string, NULL, + silc_hash_string_compare, NULL, + NULL, NULL, FALSE); + if (!parent->events) { + SILC_SCHEDULE_UNLOCK(parent); + return NULL; + } + } + + /* Check if this event is added already */ + if (silc_hash_table_find(parent->events, (void *)event, NULL, NULL)) { + SILC_SCHEDULE_UNLOCK(parent); + return NULL; + } + + /* Add new event */ + task = silc_calloc(1, sizeof(*task)); + if (!task) { + SILC_SCHEDULE_UNLOCK(parent); + return NULL; + } + + task->header.type = SILC_TASK_EVENT; + task->header.valid = TRUE; + task->event = silc_strdup(event); + if (!task->event) { + SILC_SCHEDULE_UNLOCK(parent); + silc_free(task); + return NULL; + } + silc_list_init(task->connections, struct SilcScheduleEventConnectionStruct, + next); + + if (!silc_hash_table_add(parent->events, task->event, task)) { + SILC_SCHEDULE_UNLOCK(parent); + silc_free(task->event); + silc_free(task); + return NULL; + } + + SILC_SCHEDULE_UNLOCK(parent); + + return (SilcTask)task; +} + +/* Connect to event task */ + +SilcBool silc_schedule_event_connect(SilcSchedule schedule, + const char *event, SilcTask task, + SilcTaskEventCallback callback, + void *context) +{ + SilcSchedule parent; + SilcScheduleEventConnection conn; + SilcEventTask etask; + + if (!schedule) { + schedule = silc_schedule_get_global(); + SILC_VERIFY(schedule); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + } + + if (!event && !task) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + + if (task && task->type != SILC_TASK_EVENT) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + + /* Get parent scheduler */ + parent = silc_schedule_get_parent(schedule); + + SILC_SCHEDULE_LOCK(parent); + + if (!task) { + /* Get the event task */ + if (!silc_hash_table_find(parent->events, (void *)event, NULL, + (void *)&task)) { + SILC_SCHEDULE_UNLOCK(parent); + return FALSE; + } + } + etask = (SilcEventTask)task; + + /* See if task is deleted */ + if (task->valid == FALSE) { + SILC_SCHEDULE_UNLOCK(parent); + silc_set_errno(SILC_ERR_NOT_VALID); + return FALSE; + } + + SILC_LOG_DEBUG(("Connect callback %p with context %p to event '%s'", + callback, context, etask->event)); + + /* See if already connected */ + silc_list_start(etask->connections); + while ((conn = silc_list_get(etask->connections))) { + if (conn->callback == callback && conn->context == context) { + SILC_SCHEDULE_UNLOCK(parent); + silc_set_errno(SILC_ERR_ALREADY_EXISTS); + return FALSE; + } + } + + conn = silc_calloc(1, sizeof(*conn)); + if (!conn) { + SILC_SCHEDULE_UNLOCK(parent); + return FALSE; + } + + /* Connect to the event */ + conn->schedule = schedule; + conn->callback = callback; + conn->context = context; + silc_list_add(etask->connections, conn); + + SILC_SCHEDULE_UNLOCK(parent); + + return TRUE; +} + +/* Disconnect from event */ + +SilcBool silc_schedule_event_disconnect(SilcSchedule schedule, + const char *event, SilcTask task, + SilcTaskEventCallback callback, + void *context) +{ + SilcSchedule parent; + SilcScheduleEventConnection conn; + SilcEventTask etask; + + if (!schedule) { + schedule = silc_schedule_get_global(); + SILC_VERIFY(schedule); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + } + + if (!event && !task) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + + if (task && task->type != SILC_TASK_EVENT) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + + /* Get parent scheduler */ + parent = silc_schedule_get_parent(schedule); + + SILC_SCHEDULE_LOCK(parent); + + if (!task) { + /* Get the event task */ + if (!silc_hash_table_find(parent->events, (void *)event, NULL, + (void *)&task)) { + SILC_SCHEDULE_UNLOCK(parent); + return FALSE; + } + } + etask = (SilcEventTask)task; + + /* See if task is deleted */ + if (task->valid == FALSE) { + SILC_SCHEDULE_UNLOCK(parent); + silc_set_errno(SILC_ERR_NOT_VALID); + return FALSE; + } + + SILC_LOG_DEBUG(("Disconnect callback %p with context %p from event '%s'", + callback, context, etask->event)); + + /* Disconnect */ + silc_list_start(etask->connections); + while ((conn = silc_list_get(etask->connections))) { + if (conn->callback == callback && conn->context == context) { + silc_list_del(etask->connections, conn); + silc_free(conn); + SILC_SCHEDULE_UNLOCK(parent); + return TRUE; + } + } + + SILC_SCHEDULE_UNLOCK(parent); + silc_set_errno(SILC_ERR_NOT_FOUND); + return FALSE; +} + +/* Signal event */ + +SilcBool silc_schedule_event_signal(SilcSchedule schedule, const char *event, + SilcTask task, ...) +{ + SilcSchedule parent; + SilcScheduleEventConnection conn; + SilcEventTask etask; + SilcBool stop; + va_list ap, cp; + + if (silc_unlikely(!schedule)) { + schedule = silc_schedule_get_global(); + SILC_VERIFY(schedule); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + } + + if (silc_unlikely(!event && !task)) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + + if (silc_unlikely(task && task->type != SILC_TASK_EVENT)) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + + /* Get parent scheduler */ + parent = silc_schedule_get_parent(schedule); + + SILC_SCHEDULE_LOCK(parent); + + if (!task) { + /* Get the event task */ + if (!silc_hash_table_find(parent->events, (void *)event, NULL, + (void *)&task)) { + SILC_SCHEDULE_UNLOCK(parent); + return FALSE; + } + } + etask = (SilcEventTask)task; + + /* See if task is deleted */ + if (task->valid == FALSE) { + SILC_SCHEDULE_UNLOCK(parent); + silc_set_errno(SILC_ERR_NOT_VALID); + return FALSE; + } + + SILC_LOG_DEBUG(("Signal event '%s'", etask->event)); + + va_start(ap, task); + + /* Deliver the signal */ + silc_list_start(etask->connections); + while ((conn = silc_list_get(etask->connections))) { + SILC_SCHEDULE_UNLOCK(parent); + + silc_va_copy(cp, ap); + stop = conn->callback(conn->schedule, conn->schedule->app_context, + task, conn->context, cp); + va_end(cp); + + SILC_SCHEDULE_LOCK(parent); + + /* Stop signal if wanted or if the task was deleted */ + if (!stop || !task->valid) + break; + } + + va_end(ap); + + SILC_SCHEDULE_UNLOCK(parent); + + return TRUE; +} + +/* Delete event */ + +SilcBool silc_schedule_task_del_event(SilcSchedule schedule, const char *event) +{ + SilcSchedule parent; + SilcTask task; + + if (!schedule) { + schedule = silc_schedule_get_global(); + SILC_VERIFY(schedule); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + } + + if (!event) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + + /* Get parent scheduler */ + parent = silc_schedule_get_parent(schedule); + + SILC_SCHEDULE_LOCK(parent); + + /* Get the event task */ + if (!silc_hash_table_find(parent->events, (void *)event, NULL, + (void *)&task)) { + SILC_SCHEDULE_UNLOCK(parent); + return FALSE; + } + + /* See if already deleted */ + if (task->valid == FALSE) + return TRUE; + + SILC_LOG_DEBUG(("Delete event '%s'", ((SilcEventTask)task)->event)); + + SILC_SCHEDULE_UNLOCK(parent); + + silc_schedule_task_del(parent, task); + + return TRUE; +} + +/* Timeout to remove deleted event task */ + +SILC_TASK_CALLBACK(silc_schedule_event_del_timeout) +{ + SILC_SCHEDULE_LOCK(schedule); + silc_schedule_task_remove(schedule, context); + SILC_SCHEDULE_UNLOCK(schedule); +} diff --git a/lib/silcutil/silcschedule.h b/lib/silcutil/silcschedule.h index af2c8cf3..c37ecc66 100644 --- a/lib/silcutil/silcschedule.h +++ b/lib/silcutil/silcschedule.h @@ -158,6 +158,39 @@ typedef void (*SilcTaskCallback)(SilcSchedule schedule, void *app_context, SilcTaskEvent type, SilcUInt32 fd, void *context); +/****f* silcutil/SilcScheduleAPI/SilcTaskEventCallback + * + * SYNOPSIS + * + * typedef void (*SilcTaskEventCallback)(SilcSchedule schedule, + * void *app_context, + * SilcTask task, void *context, + * va_list va); + * + * DESCRIPTION + * + * Task callback for event tasks added with silc_schedule_task_add_event. + * The callback of this type is called when an event task is signalled. + * The signal is delivered to all that have connected to the event. + * + * The `task' is the event task. The `context' is the context given as + * argument to silc_schedule_event_connect. The `schedule' is the + * scheduler given as argument to silc_schedule_event_connect. + * + * If FALSE is returned in this callback function the signal delivery to + * other connected entities is stopped. Normally, TRUE is returned. + * If the `task' is deleted in this callback, the signal delivery is also + * stopped. + * + * To specify task event callback function in the application using the + * SILC_TASK_EVENT_CALLBACK macro is recommended. + * + ***/ +typedef SilcBool (*SilcTaskEventCallback)(SilcSchedule schedule, + void *app_context, + SilcTask task, void *context, + va_list va); + /****f* silcutil/SilcScheduleAPI/SilcTaskNotifyCb * * SYNOPSIS @@ -237,6 +270,25 @@ void func(SilcSchedule schedule, void *app_context, SilcTaskEvent type, \ SilcUInt32 fd, void *context) /***/ +/****d* silcutil/SilcScheduleAPI/SILC_TASK_EVENT_CALLBACK + * + * NAME + * + * #define SILC_TASK_EVENT_CALLBACK ... + * + * DESCRIPTION + * + * Generic macro to declare event task callback functions. This defines a + * function with name `func' as a event task callback function. + * + * SOURCE + */ +#define SILC_TASK_EVENT_CALLBACK(func) \ +SilcBool func(SilcSchedule schedule, void *app_context, \ + SilcTask task, void *context, va_list va) + +/***/ + /* Prototypes */ #include "silcschedule_i.h" @@ -246,15 +298,13 @@ void func(SilcSchedule schedule, void *app_context, SilcTaskEvent type, \ * SYNOPSIS * * SilcSchedule silc_schedule_init(int max_tasks, void *app_context, - * SilcStack stack); + * SilcStack stack, SilcSchedule parent); * * DESCRIPTION * - * Initializes the scheduler. This returns the scheduler context that - * is given as argument usually to all silc_schedule_* functions. - * The `app_context' is application specific context that is delivered - * to all task callbacks. The caller must free that context. The - * 'app_context' can be for example the application itself. + * Initializes the scheduler. This returns the scheduler context or NULL + * on error. The `app_context' is application specific context that is + * delivered to all task callbacks. The caller must free that context. * * The `max_tasks' is the maximum number of file descriptor and socket * tasks in the scheduler. Set value to 0 to use default. Operating @@ -262,6 +312,14 @@ void func(SilcSchedule schedule, void *app_context, SilcTaskEvent type, \ * limit can be significantly increased when this function is called in * priviliged mode (as super user). * + * If `parent' is non-NULL it will be the parent of the new scheduler. + * If it is NULL this will create a new parent scheduler. If `parent' + * is already a child scheduler, this will create a new child to the + * child's parent. Even if `parent' is non-NULL the new child scheduler + * is still independent scheduler and will run independently of its + * parent. However, each child and parent will share event tasks + * added with silc_schedule_task_add_event. + * * If `stack' is non-NULL all memory allocation for the scheduler is done * from the `stack'. Scheduler's stack may be retrieved by calling * silc_schedule_get_stack. A stack is created for scheduler always even @@ -272,7 +330,7 @@ void func(SilcSchedule schedule, void *app_context, SilcTaskEvent type, \ * ***/ SilcSchedule silc_schedule_init(int max_tasks, void *app_context, - SilcStack stack); + SilcStack stack, SilcSchedule parent); /****f* silcutil/SilcScheduleAPI/silc_schedule_uninit * @@ -376,6 +434,19 @@ SilcBool silc_schedule_one(SilcSchedule schedule, int timeout_usecs); ***/ void silc_schedule_wakeup(SilcSchedule schedule); +/****f* silcutil/SilcScheduleAPI/silc_schedule_get_parent + * + * SYNOPSIS + * + * SilcSchedule silc_schedule_get_parent(SilcSchedule schedule); + * + * DESCRIPTION + * + * Returns the parent scheduler of the `schedule'. Never returns NULL. + * + ***/ +SilcSchedule silc_schedule_get_parent(SilcSchedule schedule); + /****f* silcutil/SilcScheduleAPI/silc_schedule_get_context * * SYNOPSIS @@ -550,6 +621,69 @@ SilcSchedule silc_schedule_get_global(void); silc_schedule_task_add(schedule, sig, callback, context, 0, 0, \ SILC_TASK_SIGNAL) +/****f* silcutil/SilcScheduleAPI/silc_schedule_task_add_event + * + * SYNOPSIS + * + * SilcTask + * silc_schedule_task_add_event(SilcSchedule schedule, + * const char *event, ...); + * + * DESCRIPTION + * + * Adds an event task to scheduler. These tasks are asynchronous events + * that one or more receivers may connect to and receive information or + * data when the event is signalled. Event tasks are fast and may be + * used to efficiently deliver events and data to multiple receivers. The + * `event' is the name of the event, and can be used to connect to the + * event and to signal it. + * + * The events are global among the `scheduler', its parent scheduler and + * any of its child schedulers. It does not matter to which scheduler + * event is added to, connected to or signalled. Signal will reach any + * connected entity, as long as it is the parent or one of the fellow + * children of `schedule'. + * + * To connect to an event call silc_schedule_event_connect. + * To disconnect from event call silc_schedule_event_disconnect. + * To signal event call silc_schedule_event_signal. + * To delete event task call silc_schedule_task_del or + * silc_schedule_task_del_event. + * + * The variable argument list is used to describe the arguments of the + * event. The variable arguments are a list of zero or more SilcParam + * values. This function returns the event task context or NULL on error. + * + * EXAMPLE + * + * // Register 'connected' event + * silc_schedule_task_add_event(schedule, "connected", + * SILC_PARAM_UINT32, + * SILC_PARAM_BUFFER); + * + * // Connect to 'connected' event + * silc_schedule_event_connect(schedule, "connected", NULL, + * connected_cb, ctx); + * + * // Signal 'connected' event + * silc_schedule_event_signal(schedule, "connected", NULL, integer, buf); + * + * // 'connected' event handler + * SILC_TASK_CALLBACK(connected_cb) + * { + * FooCtx ctx = context; + * SilcUInt32 integer; + * SilcBuffer buf; + * + * integer = va_arg(va, SilcUInt32); + * buf = va_arg(va, SilcBuffer); + * ... + * } + * + ***/ +SilcTask silc_schedule_task_add_event(SilcSchedule schedule, + const char *event, ...); + /****f* silcutil/SilcScheduleAPI/silc_schedule_task_del * * SYNOPSIS @@ -667,6 +801,26 @@ SilcBool silc_schedule_task_del_by_all(SilcSchedule schedule, int fd, SilcTaskCallback callback, void *context); +/****f* silcutil/SilcScheduleAPI/silc_schedule_task_del_event + * + * SYNOPSIS + * + * void silc_schedule_task_del_event(SilcSchedule schedule, + * const char *event); + * + * DESCRIPTION + * + * Deletes event task by the event name `event'. Returns FALSE if the + * event does not exist. Events can be deleted by calling the + * silc_schedule_task_del also. + * + * If `schedule' is NULL this will call silc_schedule_get_global to try to + * get global scheduler. + * + ***/ +SilcBool silc_schedule_task_del_event(SilcSchedule schedule, + const char *event); + /****f* silcutil/SilcScheduleAPI/silc_schedule_set_listen_fd * * SYNOPSIS @@ -739,4 +893,100 @@ SilcTaskEvent silc_schedule_get_fd_events(SilcSchedule schedule, ***/ void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd); +/****f* silcutil/SilcScheduleAPI/silc_schedule_event_connect + * + * SYNOPSIS + * + * SilcBool silc_schedule_event_connect(SilcSchedule schedule, + * const char *event, SilcTask task, + * SilcTaskEventCallback callback, + * void *context); + * + * DESCRIPTION + * + * Connects to an event task. The `event' or `task' must be non-NULL. + * If `event' is non-NULL it is the name of the event to connect to. If + * the `task' is non-NULL it is the event task to connect to. The event + * SilcTask pointer is returned by silc_schedule_task_add_event when the + * even is added to scheduler. + * + * The `callback' with `context' and with `schedule' are called when the + * even task is signalled with silc_schedule_event_signal. + * + * Returns FALSE on error or if the `callback' with `context' has already + * been connected. Otherwise, returns TRUE. + * + * EXAMPLE + * + * silc_schedule_event_connect(schedule, "foo event", NULL, + * foo_signal_callback, foo_context); + * + ***/ +SilcBool silc_schedule_event_connect(SilcSchedule schedule, + const char *event, SilcTask task, + SilcTaskEventCallback callback, + void *context); + +/****f* silcutil/SilcScheduleAPI/silc_schedule_event_disconnect + * + * SYNOPSIS + * + * SilcBool silc_schedule_event_disconnect(SilcSchedule schedule, + * const char *event, SilcTask task, + * SilcTaskEventCallback callback, + * void *context); + * + * DESCRIPTION + * + * Disconnects the `callback' and `context' from an event task. The `event' + * or `task' must be non-NULL. If `event' is non-NULL it is the name of + * the event. If `task' is non-NULL it is the event task. + * + * Returns FALSE on error or if the `callback' with `context' has not been + * connected. Otherwise, returns TRUE. + * + * EXAMPLE + * + * silc_schedule_event_connect(schedule, "foo event", NULL, + * foo_signal_callback, foo_context); + * + ***/ +SilcBool silc_schedule_event_disconnect(SilcSchedule schedule, + const char *event, SilcTask task, + SilcTaskEventCallback callback, + void *context); + +/****f* silcutil/SilcScheduleAPI/silc_schedule_event_signal + * + * SYNOPSIS + * + * SilcBool silc_schedule_event_signal(SilcSchedule schedule, + * const char *event, + * SilcTask task, ...); + * + * DESCRIPTION + * + * Signals an event task. The `event' or `task' must be non-NULL. If + * `event' is non-NULL it is the name of the event to signal. If the `task' + * is non-NULL it is the task to be signalled. It is marginally faster + * to use the `task' pointer directly instead of `event' to send the signal. + * + * The variable arguments are the arguments to be sent in the signal to + * the connected entities. The silc_schedule_task_add_event defines what + * arguments must be sent to each signal. + * + * Signal delivery is synchronous; the signal is delivered inside this + * function. If a receiver was originally in another thread, the signal + * is delivered in the thread where this function is called. This means + * that concurrency control (locking) is required if the application uses + * events in multiple threads. + * + * EXAMPLE + * + * silc_schedule_event_signal(schedule, "foo event", NULL, intarg, buffer); + * + ***/ +SilcBool silc_schedule_event_signal(SilcSchedule schedule, const char *event, + SilcTask task, ...); + #endif diff --git a/lib/silcutil/silcschedule_i.h b/lib/silcutil/silcschedule_i.h index 6c8d462d..a88df592 100644 --- a/lib/silcutil/silcschedule_i.h +++ b/lib/silcutil/silcschedule_i.h @@ -38,12 +38,15 @@ typedef enum { automatically from the scheduler. It is safe to re-register the task in task callback. It is also safe to unregister a task in the task callback. */ - SILC_TASK_TIMEOUT, + SILC_TASK_TIMEOUT = 1, /* Platform specific process signal task. On Unix systems this is one of the signals described in signal(7). On other platforms this may not be available at all. Only one callback per signal may be added. */ - SILC_TASK_SIGNAL + SILC_TASK_SIGNAL = 2, + + /* Asynchronous event task. */ + SILC_TASK_EVENT = 3, } SilcTaskType; /* Task header */ @@ -51,7 +54,7 @@ struct SilcTaskStruct { struct SilcTaskStruct *next; SilcTaskCallback callback; void *context; - unsigned int type : 1; /* 0 = fd, 1 = timeout */ + unsigned int type : 2; /* SilcTaskType */ unsigned int valid : 1; /* Set if task is valid */ }; @@ -66,17 +69,26 @@ typedef struct SilcTaskFdStruct { struct SilcTaskStruct header; unsigned int scheduled : 1; unsigned int events : 14; - unsigned int revents : 15; + unsigned int revents : 14; SilcUInt32 fd; } *SilcTaskFd; +/* Event task */ +typedef struct SilcEventTaskStruct { + struct SilcTaskStruct header; + char *event; + SilcList connections; +} *SilcEventTask; + /* Scheduler context */ struct SilcScheduleStruct { + SilcSchedule parent; /* Parent scheduler */ void *internal; void *app_context; /* Application specific context */ SilcTaskNotifyCb notify; /* Notify callback */ void *notify_context; /* Notify context */ SilcStack stack; /* Stack */ + SilcHashTable events; /* Event tasks */ SilcHashTable fd_queue; /* FD task queue */ SilcList fd_dispatch; /* Dispatched FDs */ SilcList timeout_queue; /* Timeout queue */ diff --git a/lib/silcutil/tests/test_silcschedule.c b/lib/silcutil/tests/test_silcschedule.c index a86ba68e..28d25632 100644 --- a/lib/silcutil/tests/test_silcschedule.c +++ b/lib/silcutil/tests/test_silcschedule.c @@ -12,10 +12,11 @@ typedef void (*Callback)(void *context); #endif SilcSchedule schedule; +int c = 0; void notify_cb(SilcSchedule schedule, SilcBool added, SilcTask task, - SilcBool fd_task, SilcUInt32 fd, long sec, long usec, - void *context) + SilcBool fd_task, SilcUInt32 fd, SilcTaskEvent event, + long sec, long usec, void *context) { SILC_LOG_DEBUG(("Notify cb, %s %s task, fd %d, sec %d usec %d", added ? "added" : "deleted", fd_task ? "fd" :"timeout", @@ -31,6 +32,10 @@ SILC_TASK_CALLBACK(timeout) { int i = (int)context; SILC_LOG_DEBUG(("Timeout task %d", i)); + + SILC_LOG_DEBUG(("Send 'timeout' signal")); + if (!silc_schedule_event_signal(NULL, "timeout", NULL)) + SILC_LOG_DEBUG(("Error sending signal, error %d", silc_errno)); } SILC_TASK_CALLBACK(cont2) @@ -82,13 +87,38 @@ SILC_TASK_CALLBACK(start) SILC_TASK_CALLBACK(interrupt) { - SILC_LOG_DEBUG(("SIGINT signal")); + SILC_LOG_DEBUG(("SIGINT signal, send 'interrupted' event signal")); + if (!silc_schedule_event_signal(schedule, "interrupted", NULL, + schedule)) + SILC_LOG_DEBUG(("Error sending signal, error %d", silc_errno)); +} + +SILC_TASK_EVENT_CALLBACK(timeout_event_cb) +{ + SILC_LOG_DEBUG(("timeout event signalled")); + if (c++ == 100) { + silc_schedule_task_del_event(NULL, "timeout"); + return FALSE; + } + + return TRUE; +} + +SILC_TASK_EVENT_CALLBACK(interrupted_event) +{ + SilcSchedule ptr = va_arg(va, void *); + SILC_LOG_DEBUG(("interrupted event signalled, ptr %p", ptr)); + silc_schedule_event_disconnect(NULL, "interrupted", NULL, + interrupted_event, NULL); silc_schedule_stop(schedule); + return TRUE; } int main(int argc, char **argv) { SilcBool success = FALSE; + SilcSchedule child, child2; + SilcTask timeout_event; if (argc > 1 && !strcmp(argv[1], "-d")) { silc_log_debug(TRUE); @@ -98,19 +128,56 @@ int main(int argc, char **argv) } SILC_LOG_DEBUG(("Allocating scheduler")); - schedule = silc_schedule_init(NUM_FTASK, NULL, NULL); + schedule = silc_schedule_init(NUM_FTASK, NULL, NULL, NULL); if (!schedule) goto err; silc_schedule_set_notify(schedule, notify_cb, NULL); + SILC_LOG_DEBUG(("Allocate child scheduler")); + child = silc_schedule_init(0, NULL, NULL, schedule); + if (!child) + goto err; + + SILC_LOG_DEBUG(("Allocate another child scheduler")); + child2 = silc_schedule_init(0, NULL, NULL, child); + if (!child2) + goto err; + + SILC_LOG_DEBUG(("Add 'interrupted' event to child scheduler")); + if (!silc_schedule_task_add_event(child, "interrupted", + SILC_PARAM_PTR)) + goto err; + + SILC_LOG_DEBUG(("Add 'timeout' event to parent scheduler")); + timeout_event = + silc_schedule_task_add_event(schedule, "timeout"); + if (!timeout_event) + goto err; + + SILC_LOG_DEBUG(("Connect to 'interrupted' event in parent scheduler")); + if (!silc_schedule_event_connect(schedule, "interrupted", NULL, + interrupted_event, NULL)) + goto err; + + SILC_LOG_DEBUG(("Connect to 'timeout' event in child2 scheduler")); + if (!silc_schedule_event_connect(child2, NULL, timeout_event, + timeout_event_cb, NULL)) + goto err; + + SILC_LOG_DEBUG(("Add parent as global scheduler")); + silc_schedule_set_global(schedule); + silc_schedule_task_add_signal(schedule, SIGINT, interrupt, NULL); - silc_schedule_task_add_timeout(schedule, start, NULL, 1, 0); + if (!silc_schedule_task_add_timeout(schedule, start, NULL, 1, 0)) + goto err; SILC_LOG_DEBUG(("Running scheduler")); silc_schedule(schedule); silc_schedule_uninit(schedule); + silc_schedule_uninit(child); + silc_schedule_uninit(child2); success = TRUE; diff --git a/lib/silcutil/unix/silcunixschedule.c b/lib/silcutil/unix/silcunixschedule.c index 0f2f201b..d81cfa75 100644 --- a/lib/silcutil/unix/silcunixschedule.c +++ b/lib/silcutil/unix/silcunixschedule.c @@ -495,6 +495,7 @@ void silc_schedule_internal_signal_register(SilcSchedule schedule, signal_call[i].sig = sig; signal_call[i].callback = callback; signal_call[i].context = callback_context; + signal_call[i].schedule = schedule; signal_call[i].call = FALSE; signal(sig, silc_schedule_internal_sighandler); break; @@ -524,6 +525,7 @@ void silc_schedule_internal_signal_unregister(SilcSchedule schedule, signal_call[i].sig = 0; signal_call[i].callback = NULL; signal_call[i].context = NULL; + signal_call[i].schedule = NULL; signal_call[i].call = FALSE; signal(sig, SIG_DFL); }