X-Git-Url: http://git.silcnet.org/gitweb/?p=crypto.git;a=blobdiff_plain;f=lib%2Fsilcutil%2Fsilcschedule.c;h=5550181a7da686d070e2103a6a1d8ac33140e9dd;hp=6d3f09e1c3d3794f4776ea5211a4115c1492494c;hb=60180da59ffdbbd12058dded66e3c8a547cd0852;hpb=196824372226561334cd638d6471267cbffb354c diff --git a/lib/silcutil/silcschedule.c b/lib/silcutil/silcschedule.c index 6d3f09e1..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,17 +204,18 @@ 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; - SilcUInt32 fd; + void *fd; /* Delete from fd queue */ silc_hash_table_list(schedule->fd_queue, &htl); - while (silc_hash_table_get(&htl, (void *)&fd, (void *)&task)) - silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(fd)); + while (silc_hash_table_get(&htl, &fd, (void *)&task)) + silc_hash_table_del(schedule->fd_queue, fd); silc_hash_table_list_reset(&htl); /* Delete from timeout queue */ @@ -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; } } @@ -290,14 +339,14 @@ void silc_schedule_stats(SilcSchedule schedule) { SilcTaskFd ftask; fprintf(stdout, "Schedule %p statistics:\n\n", schedule); - fprintf(stdout, "Num FD tasks : %lu (%lu bytes allocated)\n", + fprintf(stdout, "Num FD tasks : %d (%lu bytes allocated)\n", silc_hash_table_count(schedule->fd_queue), sizeof(*ftask) * silc_hash_table_count(schedule->fd_queue)); - fprintf(stdout, "Num Timeout tasks : %d (%d bytes allocated)\n", + fprintf(stdout, "Num Timeout tasks : %d (%lu bytes allocated)\n", silc_list_count(schedule->timeout_queue), sizeof(struct SilcTaskTimeoutStruct) * silc_list_count(schedule->timeout_queue)); - fprintf(stdout, "Num Timeout freelist : %d (%d bytes allocated)\n", + fprintf(stdout, "Num Timeout freelist : %d (%lu bytes allocated)\n", silc_list_count(schedule->free_tasks), sizeof(struct SilcTaskTimeoutStruct) * silc_list_count(schedule->free_tasks)); @@ -312,30 +361,46 @@ void silc_schedule_stats(SilcSchedule schedule) scheduler can handle. The `app_context' is application specific context that is delivered to task callbacks. */ -SilcSchedule silc_schedule_init(int max_tasks, void *app_context) +SilcSchedule silc_schedule_init(int max_tasks, void *app_context, + SilcStack stack, SilcSchedule parent) { SilcSchedule schedule; - SILC_LOG_DEBUG(("Initializing scheduler")); + /* Initialize Tls, in case it hasn't been done yet */ + silc_thread_tls_init(); + + stack = silc_stack_alloc(0, stack); + if (!stack) + return NULL; - schedule = silc_calloc(1, sizeof(*schedule)); + /* Allocate scheduler from the stack */ + schedule = silc_scalloc(stack, 1, sizeof(*schedule)); if (!schedule) return NULL; + SILC_LOG_DEBUG(("Initializing scheduler %p", schedule)); + + /* Allocate Fd task hash table dynamically */ schedule->fd_queue = - silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL, + silc_hash_table_alloc(NULL, 0, silc_hash_uint, NULL, NULL, NULL, silc_schedule_fd_destructor, NULL, TRUE); if (!schedule->fd_queue) { - silc_free(schedule); + silc_stack_free(stack); return NULL; } 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); @@ -345,7 +410,7 @@ SilcSchedule silc_schedule_init(int max_tasks, void *app_context) if (!schedule->internal) { silc_hash_table_free(schedule->fd_queue); silc_mutex_free(schedule->lock); - silc_free(schedule); + silc_stack_free(stack); return NULL; } @@ -365,7 +430,9 @@ SilcBool silc_schedule_uninit(SilcSchedule schedule) { SilcTask task; - SILC_LOG_DEBUG(("Uninitializing scheduler")); + SILC_VERIFY(schedule); + + SILC_LOG_DEBUG(("Uninitializing scheduler %p", schedule)); if (schedule->valid == TRUE) return FALSE; @@ -397,7 +464,7 @@ SilcBool silc_schedule_uninit(SilcSchedule schedule) schedule_ops.uninit(schedule, schedule->internal); silc_mutex_free(schedule->lock); - silc_free(schedule); + silc_stack_free(schedule->stack); return TRUE; } @@ -409,6 +476,7 @@ SilcBool silc_schedule_uninit(SilcSchedule schedule) void silc_schedule_stop(SilcSchedule schedule) { SILC_LOG_DEBUG(("Stopping scheduler")); + SILC_VERIFY(schedule); SILC_SCHEDULE_LOCK(schedule); schedule->valid = FALSE; SILC_SCHEDULE_UNLOCK(schedule); @@ -473,6 +541,11 @@ static SilcBool silc_schedule_iterate(SilcSchedule schedule, int timeout_usecs) /* There is some data available now */ SILC_LOG_DEBUG(("Running fd tasks")); silc_schedule_dispatch_fd(schedule); + + /* If timeout was very short, dispatch also timeout tasks */ + if (schedule->has_timeout && schedule->timeout.tv_sec == 0 && + schedule->timeout.tv_usec < 50000) + silc_schedule_dispatch_timeout(schedule, FALSE); continue; } else { @@ -533,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 @@ -543,6 +623,55 @@ void *silc_schedule_get_context(SilcSchedule schedule) return schedule->app_context; } +/* Return the stack of the scheduler */ + +SilcStack silc_schedule_get_stack(SilcSchedule schedule) +{ + return schedule->stack; +} + +/* Set notify callback */ + +void silc_schedule_set_notify(SilcSchedule schedule, + SilcTaskNotifyCb notify, void *context) +{ + schedule->notify = notify; + schedule->notify_context = context; +} + +/* Set global scheduler */ + +void silc_schedule_set_global(SilcSchedule schedule) +{ + SilcTls tls = silc_thread_get_tls(); + + if (!tls) { + /* Try to initialize Tls */ + tls = silc_thread_tls_init(); + SILC_VERIFY(tls); + if (!tls) + return; + } + + SILC_LOG_DEBUG(("Setting global scheduler %p", schedule)); + + tls->schedule = schedule; +} + +/* Return global scheduler */ + +SilcSchedule silc_schedule_get_global(void) +{ + SilcTls tls = silc_thread_get_tls(); + + if (!tls) + return NULL; + + SILC_LOG_DEBUG(("Return global scheduler %p", tls->schedule)); + + return tls->schedule; +} + /* Add new task to the scheduler */ SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd, @@ -552,8 +681,19 @@ SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd, { SilcTask task = NULL; - if (silc_unlikely(!schedule->valid)) + if (!schedule) { + schedule = silc_schedule_get_global(); + SILC_VERIFY(schedule); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return NULL; + } + } + + if (silc_unlikely(!schedule->valid)) { + silc_set_errno(SILC_ERR_NOT_VALID); return NULL; + } SILC_SCHEDULE_LOCK(schedule); @@ -607,6 +747,11 @@ SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd, task = (SilcTask)ttask; + /* Call notify callback */ + if (schedule->notify) + schedule->notify(schedule, TRUE, task, FALSE, 0, 0, seconds, useconds, + schedule->notify_context); + } else if (silc_likely(type == SILC_TASK_FD)) { SilcTaskFd ftask; @@ -627,6 +772,7 @@ SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd, schedule->max_tasks)) { SILC_LOG_WARNING(("Scheduler task limit reached: cannot add new task")); task = NULL; + silc_set_errno(SILC_ERR_LIMIT); goto out; } @@ -660,6 +806,11 @@ SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd, task = (SilcTask)ftask; + /* Call notify callback */ + if (schedule->notify) + schedule->notify(schedule, TRUE, task, TRUE, ftask->fd, + SILC_TASK_READ, 0, 0, schedule->notify_context); + } else if (silc_unlikely(type == SILC_TASK_SIGNAL)) { SILC_SCHEDULE_UNLOCK(schedule); schedule_ops.signal_register(schedule, schedule->internal, fd, @@ -685,6 +836,17 @@ 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); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + } + if (silc_unlikely(task == SILC_ALL_TASKS)) { SilcHashTableList htl; @@ -694,25 +856,56 @@ SilcBool silc_schedule_task_del(SilcSchedule schedule, SilcTask task) /* Delete from fd queue */ silc_hash_table_list(schedule->fd_queue, &htl); - while (silc_hash_table_get(&htl, NULL, (void *)&task)) + while (silc_hash_table_get(&htl, NULL, (void *)&task)) { task->valid = FALSE; + + /* Call notify callback */ + if (schedule->notify) + schedule->notify(schedule, FALSE, task, TRUE, + ((SilcTaskFd)task)->fd, 0, 0, 0, + schedule->notify_context); + } silc_hash_table_list_reset(&htl); /* Delete from timeout queue */ silc_list_start(schedule->timeout_queue); - while ((task = (SilcTask)silc_list_get(schedule->timeout_queue)) - != SILC_LIST_END) + while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))) { task->valid = FALSE; + /* Call notify callback */ + if (schedule->notify) + schedule->notify(schedule, FALSE, task, FALSE, 0, 0, 0, 0, + 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 && 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; } @@ -725,6 +918,15 @@ SilcBool silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd) SILC_LOG_DEBUG(("Unregister task by fd %d", fd)); + if (!schedule) { + schedule = silc_schedule_get_global(); + SILC_VERIFY(schedule); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + } + SILC_SCHEDULE_LOCK(schedule); /* fd is unique, so there is only one task with this fd in the table */ @@ -733,6 +935,11 @@ SilcBool silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd) (void *)&task))) { SILC_LOG_DEBUG(("Deleting task %p", task)); task->valid = FALSE; + + /* Call notify callback */ + if (schedule->notify) + schedule->notify(schedule, FALSE, task, TRUE, fd, 0, 0, 0, + schedule->notify_context); ret = TRUE; } @@ -744,6 +951,9 @@ SilcBool silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd) ret = TRUE; } + if (ret == FALSE) + silc_set_errno(SILC_ERR_NOT_FOUND); + return ret; } @@ -759,6 +969,15 @@ SilcBool silc_schedule_task_del_by_callback(SilcSchedule schedule, SILC_LOG_DEBUG(("Unregister task by callback")); + if (!schedule) { + schedule = silc_schedule_get_global(); + SILC_VERIFY(schedule); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + } + SILC_SCHEDULE_LOCK(schedule); /* Delete from fd queue */ @@ -766,6 +985,12 @@ SilcBool silc_schedule_task_del_by_callback(SilcSchedule schedule, while (silc_hash_table_get(&htl, NULL, (void *)&task)) { if (task->callback == callback) { task->valid = FALSE; + + /* Call notify callback */ + if (schedule->notify) + schedule->notify(schedule, FALSE, task, TRUE, + ((SilcTaskFd)task)->fd, 0, 0, 0, + schedule->notify_context); ret = TRUE; } } @@ -777,12 +1002,20 @@ SilcBool silc_schedule_task_del_by_callback(SilcSchedule schedule, while ((task = (SilcTask)silc_list_get(list))) { if (task->callback == callback) { task->valid = FALSE; + + /* Call notify callback */ + if (schedule->notify) + schedule->notify(schedule, FALSE, task, FALSE, 0, 0, 0, 0, + schedule->notify_context); ret = TRUE; } } SILC_SCHEDULE_UNLOCK(schedule); + if (ret == FALSE) + silc_set_errno(SILC_ERR_NOT_FOUND); + return ret; } @@ -798,6 +1031,15 @@ SilcBool silc_schedule_task_del_by_context(SilcSchedule schedule, SILC_LOG_DEBUG(("Unregister task by context")); + if (!schedule) { + schedule = silc_schedule_get_global(); + SILC_VERIFY(schedule); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + } + SILC_SCHEDULE_LOCK(schedule); /* Delete from fd queue */ @@ -805,6 +1047,12 @@ SilcBool silc_schedule_task_del_by_context(SilcSchedule schedule, while (silc_hash_table_get(&htl, NULL, (void *)&task)) { if (task->context == context) { task->valid = FALSE; + + /* Call notify callback */ + if (schedule->notify) + schedule->notify(schedule, FALSE, task, TRUE, + ((SilcTaskFd)task)->fd, 0, 0, 0, + schedule->notify_context); ret = TRUE; } } @@ -815,13 +1063,21 @@ SilcBool silc_schedule_task_del_by_context(SilcSchedule schedule, silc_list_start(list); while ((task = (SilcTask)silc_list_get(list))) { if (task->context == context) { - ret = TRUE; task->valid = FALSE; + + /* Call notify callback */ + if (schedule->notify) + schedule->notify(schedule, FALSE, task, FALSE, 0, 0, 0, 0, + schedule->notify_context); + ret = TRUE; } } SILC_SCHEDULE_UNLOCK(schedule); + if (ret == FALSE) + silc_set_errno(SILC_ERR_NOT_FOUND); + return ret; } @@ -841,6 +1097,15 @@ SilcBool silc_schedule_task_del_by_all(SilcSchedule schedule, int fd, if (fd) return silc_schedule_task_del_by_fd(schedule, fd); + if (!schedule) { + schedule = silc_schedule_get_global(); + SILC_VERIFY(schedule); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + } + SILC_SCHEDULE_LOCK(schedule); /* Delete from timeout queue */ @@ -849,12 +1114,20 @@ SilcBool silc_schedule_task_del_by_all(SilcSchedule schedule, int fd, while ((task = (SilcTask)silc_list_get(list))) { if (task->callback == callback && task->context == context) { task->valid = FALSE; + + /* Call notify callback */ + if (schedule->notify) + schedule->notify(schedule, FALSE, task, FALSE, 0, 0, 0, 0, + schedule->notify_context); ret = TRUE; } } SILC_SCHEDULE_UNLOCK(schedule); + if (ret == FALSE) + silc_set_errno(SILC_ERR_NOT_FOUND); + return TRUE; } @@ -867,8 +1140,19 @@ SilcBool silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd, { SilcTaskFd task; - if (silc_unlikely(!schedule->valid)) + if (!schedule) { + schedule = silc_schedule_get_global(); + SILC_VERIFY(schedule); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return FALSE; + } + } + + if (silc_unlikely(!schedule->valid)) { + silc_set_errno(SILC_ERR_NOT_VALID); return FALSE; + } SILC_SCHEDULE_LOCK(schedule); @@ -883,6 +1167,12 @@ SilcBool silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd, task->revents = mask; silc_schedule_dispatch_fd(schedule); } + + /* Call notify callback */ + if (schedule->notify) + schedule->notify(schedule, TRUE, (SilcTask)task, + TRUE, task->fd, mask, 0, 0, + schedule->notify_context); } SILC_SCHEDULE_UNLOCK(schedule); @@ -898,8 +1188,19 @@ SilcTaskEvent silc_schedule_get_fd_events(SilcSchedule schedule, SilcTaskFd task; SilcTaskEvent event = 0; - if (silc_unlikely(!schedule->valid)) + if (!schedule) { + schedule = silc_schedule_get_global(); + SILC_VERIFY(schedule); + if (!schedule) { + silc_set_errno(SILC_ERR_INVALID_ARGUMENT); + return 0; + } + } + + if (silc_unlikely(!schedule->valid)) { + silc_set_errno(SILC_ERR_NOT_VALID); return 0; + } SILC_SCHEDULE_LOCK(schedule); if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd), @@ -916,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); +}