concept of parent and child schedulers.
+Sun Dec 30 14:35:33 EET 2007 Pekka Riikonen <priikone@silcnet.org>
+
+ * 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 <priikone@silcnet.org>
* Added silc_schedule_[set|get]_global to
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
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
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].
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
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;
/* 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))) {
/* Run the scheduler */
silc_schedule(fsm->schedule);
+ /* Reset global scheduler */
+ silc_schedule_set_global(NULL);
+
/* Free resources */
silc_schedule_uninit(fsm->schedule);
* 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
/************************** 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;
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 **************************/
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;
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;
}
}
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;
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);
#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
SilcBool silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
{
+ SilcSchedule parent;
+
if (!schedule) {
schedule = silc_schedule_get_global();
SILC_VERIFY(schedule);
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;
}
{
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);
+}
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
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"
* 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
* 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
*
***/
SilcSchedule silc_schedule_init(int max_tasks, void *app_context,
- SilcStack stack);
+ SilcStack stack, SilcSchedule parent);
/****f* silcutil/SilcScheduleAPI/silc_schedule_uninit
*
***/
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
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
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
***/
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
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 */
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 */
};
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 */
#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",
{
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)
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);
}
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;
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;
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);
}