Merge branch 'bugfix/fix_osi_event_bug' into 'master'

Bugfix/fix osi event bug

See merge request espressif/esp-idf!51916
This commit is contained in:
Wang Meng Yang
2026-09-02 17:47:06 +08:00
6 changed files with 859 additions and 212 deletions
+17 -2
View File
@@ -19,6 +19,7 @@
struct osi_thread;
struct osi_event;
struct osi_dynamic_event;
typedef struct osi_thread osi_thread_t;
@@ -81,10 +82,12 @@ const char *osi_thread_name(osi_thread_t *thread);
int osi_thread_queue_wait_size(osi_thread_t *thread, int wq_idx);
/*
* brief: Create an osi_event struct and register the handler function and its argument
* brief: Create a session-stable osi_event and register its handler and argument.
* An osi_event is a kind of work that can be posted to the workqueue of osi_thread to process,
* but the work can have at most one instance the thread workqueue before it is processed. This
* allows the "single post, multiple data processing" jobs.
* Delete is logical: storage remains valid until osi_thread_event_deinit(), allowing stale
* posts during session teardown to be rejected without a global alive-list lock.
* param func: the handler to process the job
* param context: the argument to be passed to the handler function when the job is being processed
* return: NULL if no memory, otherwise a valid struct pointer
@@ -103,7 +106,7 @@ struct osi_event *osi_event_create(osi_thread_func_t func, void *context);
bool osi_event_bind(struct osi_event* event, osi_thread_t *thread, int queue_idx);
/*
* brief: Destroy the osi_event struct created by osi_event_create and free the allocated memory
* brief: Logically delete an osi_event. Its memory is reclaimed by osi_thread_event_deinit().
* param event: the pointer to osi_event
*/
void osi_event_delete(struct osi_event* event);
@@ -118,6 +121,18 @@ void osi_event_delete(struct osi_event* event);
*/
bool osi_thread_post_event(struct osi_event *event, uint32_t timeout);
/*
* Dynamic events may be created and destroyed repeatedly during one
* osi_thread event-subsystem session. Unlike session-stable osi_event objects,
* their storage can be released by delete, so all operations use a separate
* API whose post/bind entry points validate the opaque pointer without first
* dereferencing it.
*/
struct osi_dynamic_event *osi_dynamic_event_create(osi_thread_func_t func, void *context);
bool osi_dynamic_event_bind(struct osi_dynamic_event *event, osi_thread_t *thread, int queue_idx);
bool osi_dynamic_event_post(struct osi_dynamic_event *event, uint32_t timeout);
void osi_dynamic_event_delete(struct osi_dynamic_event *event);
int osi_thread_event_init(void);
void osi_thread_event_deinit(void);
+296 -200
View File
@@ -56,7 +56,7 @@ struct osi_thread_start_arg {
int error;
};
struct osi_event {
struct osi_event_core {
struct work_item item;
osi_mutex_t lock;
osi_thread_t *thread;
@@ -65,17 +65,25 @@ struct osi_event {
uint8_t queue_idx;
};
struct osi_event {
struct osi_event_core core;
};
struct osi_dynamic_event {
struct osi_event_core core;
};
#define OSI_EVENT_FLAG_QUEUED (1U << 0)
#define OSI_EVENT_FLAG_POSTING (1U << 1)
#define OSI_EVENT_FLAG_DELETING (1U << 2)
#define OSI_EVENT_FLAG_RUNNING (1U << 3)
#define OSI_EVENT_FLAG_DELETING (1U << 1)
#define OSI_EVENT_FLAG_RUNNING (1U << 2)
#define OSI_EVENT_HAS_FLAG(event, flag) (((event)->flags & (flag)) != 0)
#define OSI_EVENT_SET_FLAG(event, flag) ((event)->flags |= (uint8_t)(flag))
#define OSI_EVENT_CLEAR_FLAG(event, flag) ((event)->flags &= (uint8_t)(~(flag)))
static const size_t DEFAULT_WORK_QUEUE_CAPACITY = 100;
static list_t *s_osi_event_list;
static list_t *s_osi_session_event_list;
static list_t *s_osi_dynamic_event_list;
static osi_mutex_t s_osi_event_lock;
#if OSI_THREAD_DEBUG
@@ -494,35 +502,47 @@ int osi_thread_queue_wait_size(osi_thread_t *thread, int wq_idx)
}
static bool osi_event_add_alive_locked(struct osi_event *event)
static struct osi_event_core *osi_event_core_new(size_t size, osi_thread_func_t func, void *context)
{
assert(s_osi_event_list != NULL);
return list_append(s_osi_event_list, event);
struct osi_event_core *event = osi_calloc(size);
if (event == NULL) {
return NULL;
}
if (osi_mutex_new(&event->lock) != 0) {
osi_free(event);
return NULL;
}
event->item.func = func;
event->item.context = context;
/* Session events hold a registry pin; dynamic events hold an owner ref. */
event->ref_count = 1;
return event;
}
static void osi_event_free(struct osi_event *event)
static void osi_event_core_free(struct osi_event_core *event)
{
if (event != NULL) {
osi_mutex_free(&event->lock);
memset(event, 0, sizeof(struct osi_event));
memset(event, 0, sizeof(*event));
osi_free(event);
}
}
static bool osi_event_is_idle(const struct osi_event *event)
static bool osi_event_is_idle(const struct osi_event_core *event)
{
return !OSI_EVENT_HAS_FLAG(event, OSI_EVENT_FLAG_QUEUED) &&
!OSI_EVENT_HAS_FLAG(event, OSI_EVENT_FLAG_POSTING) &&
!OSI_EVENT_HAS_FLAG(event, OSI_EVENT_FLAG_RUNNING);
}
static bool osi_event_should_free(const struct osi_event *event)
static bool osi_event_should_free(const struct osi_event_core *event)
{
return OSI_EVENT_HAS_FLAG(event, OSI_EVENT_FLAG_DELETING) &&
osi_event_is_idle(event);
}
static bool osi_event_can_bind_locked(const struct osi_event *event, osi_thread_t *thread, int queue_idx)
static bool osi_event_can_bind_locked(const struct osi_event_core *event, osi_thread_t *thread, int queue_idx)
{
return !OSI_EVENT_HAS_FLAG(event, OSI_EVENT_FLAG_DELETING) &&
event->thread == NULL &&
@@ -531,7 +551,7 @@ static bool osi_event_can_bind_locked(const struct osi_event *event, osi_thread_
queue_idx < thread->work_queue_num;
}
static bool osi_event_can_post_locked(const struct osi_event *event)
static bool osi_event_can_post_locked(const struct osi_event_core *event)
{
if (event->thread == NULL || event->queue_idx >= event->thread->work_queue_num) {
OSI_TRACE_EVENT("%s deny ev=%p flags=0x%x qidx=%u",
@@ -549,173 +569,188 @@ static bool osi_event_can_post_locked(const struct osi_event *event)
return false;
}
/* Do NOT gate on OSI_EVENT_FLAG_POSTING here. POSTING marks the window in
* osi_thread_post_event() between osi_thread_post() (enqueue) and the
* poster clearing the flag. During that window the generic event handler
* may already have run and cleared QUEUED. A concurrent post that arrives
* after QUEUED is cleared is a legitimate re-post (new work arrived while
* the handler was draining) and must be accepted; rejecting it causes a
* lost wakeup. QUEUED alone prevents genuine double-queueing. POSTING is
* retained only for osi_event_is_idle()/osi_event_should_free(). */
/* QUEUED alone prevents double-queueing. Do not gate on RUNNING: the
* generic handler clears QUEUED before invoking the user callback, and a
* concurrent post after that is a legitimate re-post of work that arrived
* while the handler was draining. */
return true;
}
static bool osi_event_is_alive_locked(const struct osi_event *event)
/* Caller holds event->lock. Drops one reference. Returns true if the caller
* must free the event AFTER unlocking; never destroy the mutex while held. */
static bool osi_event_release_locked(struct osi_event_core *event)
{
/* Do not dereference event here: callers may pass a stale pointer racing
* with osi_event_delete(). The alive list is the ownership boundary. */
return s_osi_event_list != NULL && list_contains(s_osi_event_list, event);
}
static bool osi_event_remove_alive_locked(struct osi_event *event)
{
bool removed = false;
if (s_osi_event_list == NULL) {
assert(event->ref_count > 0);
event->ref_count--;
if (event->ref_count != 0) {
return false;
}
return osi_event_should_free(event);
}
removed = list_delete(s_osi_event_list, event);
return removed;
static void osi_event_unlock_and_maybe_free(struct osi_event_core *event, bool should_free)
{
osi_mutex_unlock(&event->lock);
if (should_free) {
osi_event_core_free(event);
}
}
static void osi_event_mark_deleting_locked(struct osi_event_core *event)
{
OSI_EVENT_SET_FLAG(event, OSI_EVENT_FLAG_DELETING);
event->item.func = NULL;
event->item.context = NULL;
}
struct osi_event *osi_event_create(osi_thread_func_t func, void *context)
{
struct osi_event *event = (struct osi_event *)osi_event_core_new(sizeof(*event), func, context);
bool added = false;
struct osi_event *event = osi_calloc(sizeof(struct osi_event));
if (event == NULL) {
return NULL;
}
if (osi_mutex_new(&event->lock) != 0) {
osi_free(event);
osi_event_lock();
if (s_osi_session_event_list != NULL) {
added = list_append(s_osi_session_event_list, event);
}
osi_event_unlock();
if (!added) {
osi_event_core_free(&event->core);
return NULL;
}
event->item.func = func;
event->item.context = context;
event->ref_count = 1;
osi_event_lock();
added = osi_event_add_alive_locked(event);
osi_event_unlock();
if (added) {
return event;
}
osi_mutex_free(&event->lock);
osi_free(event);
return NULL;
}
/* ref_count is protected by the per-event lock (event->lock), NOT the global
* s_osi_event_lock. This keeps the reference-release path independent of the
* global event subsystem: it must stay valid even after
* osi_thread_event_deinit() has freed s_osi_event_lock (e.g. when a thread is
* freed on a shutdown path that tears the event subsystem down first). The
* global lock is only used to gate the alive-list membership that decides
* whether a new reference may be acquired. */
static bool osi_event_acquire(struct osi_event *event)
{
bool acquired = false;
if (event == NULL) {
return false;
}
/* Hold the global lock so the event cannot be removed from the alive list
* (and thus cannot be freed) while we take a fresh reference. Nesting is
* always global-lock-outer, event->lock-inner; no path takes them in the
* reverse order, so this cannot deadlock. */
osi_event_lock();
if (osi_event_is_alive_locked(event)) {
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
assert(event->ref_count > 0);
event->ref_count++;
osi_mutex_unlock(&event->lock);
acquired = true;
}
osi_event_unlock();
return acquired;
}
static void osi_event_retain(struct osi_event *event)
{
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
assert(event->ref_count > 0);
event->ref_count++;
osi_mutex_unlock(&event->lock);
}
static void osi_event_release(struct osi_event *event)
{
bool should_free = false;
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
assert(event->ref_count > 0);
event->ref_count--;
if (event->ref_count == 0) {
should_free = osi_event_should_free(event);
}
osi_mutex_unlock(&event->lock);
if (should_free) {
osi_event_free(event);
}
return event;
}
void osi_event_delete(struct osi_event *event)
{
bool removed = false;
if (event == NULL) {
return;
}
osi_event_lock();
removed = osi_event_remove_alive_locked(event);
osi_event_unlock();
if (!removed) {
return;
}
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
OSI_EVENT_SET_FLAG(event, OSI_EVENT_FLAG_DELETING);
event->item.func = NULL;
event->item.context = NULL;
osi_mutex_unlock(&event->lock);
osi_event_release(event);
/* The registry pin keeps session-event storage valid until subsystem
* deinit, so delete is only a logical, idempotent operation. */
osi_mutex_lock(&event->core.lock, OSI_MUTEX_MAX_TIMEOUT);
osi_event_mark_deleting_locked(&event->core);
osi_mutex_unlock(&event->core.lock);
}
bool osi_event_bind(struct osi_event *event, osi_thread_t *thread, int queue_idx)
{
bool ret = false;
if (!osi_event_acquire(event)) {
if (event == NULL) {
return false;
}
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
if (osi_event_can_bind_locked(event, thread, queue_idx)) {
event->thread = thread;
event->queue_idx = queue_idx;
osi_mutex_lock(&event->core.lock, OSI_MUTEX_MAX_TIMEOUT);
if (osi_event_can_bind_locked(&event->core, thread, queue_idx)) {
event->core.thread = thread;
event->core.queue_idx = (uint8_t)queue_idx;
ret = true;
}
osi_mutex_unlock(&event->lock);
osi_event_release(event);
osi_mutex_unlock(&event->core.lock);
return ret;
}
struct osi_dynamic_event *osi_dynamic_event_create(osi_thread_func_t func, void *context)
{
struct osi_dynamic_event *event =
(struct osi_dynamic_event *)osi_event_core_new(sizeof(*event), func, context);
bool added = false;
if (event == NULL) {
return NULL;
}
osi_event_lock();
if (s_osi_dynamic_event_list != NULL) {
added = list_append(s_osi_dynamic_event_list, event);
}
osi_event_unlock();
if (!added) {
osi_event_core_free(&event->core);
return NULL;
}
return event;
}
/* The global alive list is the ownership boundary for dynamic events. Never
* dereference event until list membership is confirmed under the global lock.
* Lock nesting is always global-lock-outer, event-lock-inner.
* On success: drops the global lock and returns holding event->core.lock with
* an extra reference; the caller must unlock (and maybe free) via
* osi_event_unlock_and_maybe_free after osi_event_release_locked. */
static bool osi_dynamic_event_acquire_locked(struct osi_dynamic_event *event)
{
if (event == NULL) {
return false;
}
osi_event_lock();
if (s_osi_dynamic_event_list == NULL ||
!list_contains(s_osi_dynamic_event_list, event)) {
osi_event_unlock();
return false;
}
osi_mutex_lock(&event->core.lock, OSI_MUTEX_MAX_TIMEOUT);
assert(event->core.ref_count > 0);
event->core.ref_count++;
osi_event_unlock();
return true;
}
bool osi_dynamic_event_bind(struct osi_dynamic_event *event, osi_thread_t *thread, int queue_idx)
{
bool ret = false;
bool should_free;
if (!osi_dynamic_event_acquire_locked(event)) {
return false;
}
if (osi_event_can_bind_locked(&event->core, thread, queue_idx)) {
event->core.thread = thread;
event->core.queue_idx = (uint8_t)queue_idx;
ret = true;
}
should_free = osi_event_release_locked(&event->core);
osi_event_unlock_and_maybe_free(&event->core, should_free);
return ret;
}
void osi_dynamic_event_delete(struct osi_dynamic_event *event)
{
bool removed = false;
bool should_free;
if (event == NULL) {
return;
}
osi_event_lock();
if (s_osi_dynamic_event_list != NULL) {
removed = list_delete(s_osi_dynamic_event_list, event);
}
osi_event_unlock();
if (!removed) {
return;
}
osi_mutex_lock(&event->core.lock, OSI_MUTEX_MAX_TIMEOUT);
osi_event_mark_deleting_locked(&event->core);
should_free = osi_event_release_locked(&event->core);
osi_event_unlock_and_maybe_free(&event->core, should_free);
}
static void osi_thread_generic_event_handler(void *context)
{
struct osi_event *event = (struct osi_event *)context;
struct osi_event_core *event = (struct osi_event_core *)context;
osi_thread_func_t func = NULL;
void *func_context = NULL;
bool should_free = false;
if (event == NULL) {
return;
@@ -724,8 +759,8 @@ static void osi_thread_generic_event_handler(void *context)
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
OSI_EVENT_CLEAR_FLAG(event, OSI_EVENT_FLAG_QUEUED);
if (OSI_EVENT_HAS_FLAG(event, OSI_EVENT_FLAG_DELETING)) {
osi_mutex_unlock(&event->lock);
osi_event_release(event);
should_free = osi_event_release_locked(event);
osi_event_unlock_and_maybe_free(event, should_free);
return;
}
OSI_EVENT_SET_FLAG(event, OSI_EVENT_FLAG_RUNNING);
@@ -741,20 +776,17 @@ static void osi_thread_generic_event_handler(void *context)
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
OSI_EVENT_CLEAR_FLAG(event, OSI_EVENT_FLAG_RUNNING);
OSI_TRACE_DEBUG("%s exit ev=%p flags=0x%x", __func__, event, event->flags);
osi_mutex_unlock(&event->lock);
osi_event_release(event);
should_free = osi_event_release_locked(event);
osi_event_unlock_and_maybe_free(event, should_free);
}
/* Reclaim a queued event work item during thread teardown WITHOUT invoking the
* user callback. It only clears the QUEUED flag and drops the reference the
* queued item owns (osi_thread_post_event() retained it); this frees the event
* if it was already deleted, and leaves a still-live event untouched. Unlike
* osi_thread_generic_event_handler(), it never dispatches into stack state that
* may already have been freed on the shutdown path. */
* user callback. The release path only uses the per-event lock and remains
* valid after the global event subsystem has been deinitialized. */
static void osi_thread_generic_event_drain(void *context)
{
struct osi_event *event = (struct osi_event *)context;
struct osi_event_core *event = (struct osi_event_core *)context;
bool should_free = false;
if (event == NULL) {
return;
@@ -762,86 +794,150 @@ static void osi_thread_generic_event_drain(void *context)
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
OSI_EVENT_CLEAR_FLAG(event, OSI_EVENT_FLAG_QUEUED);
osi_mutex_unlock(&event->lock);
osi_event_release(event);
should_free = osi_event_release_locked(event);
osi_event_unlock_and_maybe_free(event, should_free);
}
bool osi_thread_post_event(struct osi_event *event, uint32_t timeout)
{
bool ret = false;
osi_thread_t *thread = NULL;
uint8_t queue_idx = 0;
struct osi_event_core *core;
osi_thread_t *thread;
uint8_t queue_idx;
bool ret;
bool should_free = false;
if (!osi_event_acquire(event)) {
OSI_TRACE_EVENT("%s acquire fail ev=%p", __func__, event);
if (event == NULL) {
return false;
}
core = &event->core;
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
if (!osi_event_can_post_locked(event)) {
osi_mutex_unlock(&event->lock);
osi_event_release(event);
/* Session-event storage is pinned until subsystem deinit, so the hot path
* needs only the per-event lock and a queue reference. */
osi_mutex_lock(&core->lock, OSI_MUTEX_MAX_TIMEOUT);
if (!osi_event_can_post_locked(core)) {
OSI_TRACE_EVENT("%s post fail ev=%p qidx=%u wq_len=%d", __func__, event, core->queue_idx,
core->thread ? osi_thread_queue_wait_size(core->thread, core->queue_idx) : -1);
osi_mutex_unlock(&core->lock);
return false;
}
OSI_EVENT_SET_FLAG(event, OSI_EVENT_FLAG_QUEUED);
OSI_EVENT_SET_FLAG(event, OSI_EVENT_FLAG_POSTING);
thread = event->thread;
queue_idx = event->queue_idx;
osi_mutex_unlock(&event->lock);
OSI_EVENT_SET_FLAG(core, OSI_EVENT_FLAG_QUEUED);
core->ref_count++;
thread = core->thread;
queue_idx = core->queue_idx;
osi_mutex_unlock(&core->lock);
/* The queued work item owns a reference until the generic handler drains it. */
osi_event_retain(event);
ret = osi_thread_post(thread, osi_thread_generic_event_handler, event, queue_idx, timeout);
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
OSI_EVENT_CLEAR_FLAG(event, OSI_EVENT_FLAG_POSTING);
if (!ret) {
// clear "is_queued" when post failure, to allow for following event posts
OSI_EVENT_CLEAR_FLAG(event, OSI_EVENT_FLAG_QUEUED);
}
osi_mutex_unlock(&event->lock);
ret = osi_thread_post(thread, osi_thread_generic_event_handler, core, queue_idx, timeout);
osi_mutex_lock(&core->lock, OSI_MUTEX_MAX_TIMEOUT);
if (!ret) {
OSI_TRACE_EVENT("%s enqueue fail ev=%p qidx=%u wq_len=%d",
__func__, event, queue_idx,
OSI_TRACE_EVENT("%s enqueue fail ev=%p qidx=%u wq_len=%d", __func__, event, queue_idx,
osi_thread_queue_wait_size(thread, queue_idx));
osi_event_release(event);
/* Clear QUEUED on post failure so a later post may enqueue. */
OSI_EVENT_CLEAR_FLAG(core, OSI_EVENT_FLAG_QUEUED);
should_free = osi_event_release_locked(core);
}
osi_event_release(event);
osi_event_unlock_and_maybe_free(core, should_free);
return ret;
}
bool osi_dynamic_event_post(struct osi_dynamic_event *event, uint32_t timeout)
{
struct osi_event_core *core;
osi_thread_t *thread;
uint8_t queue_idx;
bool ret;
bool should_free = false;
if (!osi_dynamic_event_acquire_locked(event)) {
return false;
}
core = &event->core;
if (!osi_event_can_post_locked(core)) {
OSI_TRACE_EVENT("%s post fail ev=%p qidx=%u wq_len=%d", __func__, event, core->queue_idx,
core->thread ? osi_thread_queue_wait_size(core->thread, core->queue_idx) : -1);
should_free = osi_event_release_locked(core);
osi_event_unlock_and_maybe_free(core, should_free);
return false;
}
OSI_EVENT_SET_FLAG(core, OSI_EVENT_FLAG_QUEUED);
core->ref_count++;
thread = core->thread;
queue_idx = core->queue_idx;
osi_mutex_unlock(&core->lock);
ret = osi_thread_post(thread, osi_thread_generic_event_handler, core, queue_idx, timeout);
osi_mutex_lock(&core->lock, OSI_MUTEX_MAX_TIMEOUT);
if (!ret) {
OSI_TRACE_EVENT("%s enqueue fail ev=%p qidx=%u wq_len=%d", __func__, event, queue_idx,
osi_thread_queue_wait_size(thread, queue_idx));
/* Drop the queue ownership taken above; handler will never run. */
OSI_EVENT_CLEAR_FLAG(core, OSI_EVENT_FLAG_QUEUED);
should_free = osi_event_release_locked(core);
}
/* Always drop the acquire reference from osi_dynamic_event_acquire_locked. */
if (osi_event_release_locked(core)) {
should_free = true;
}
osi_event_unlock_and_maybe_free(core, should_free);
return ret;
}
static void osi_event_retire(struct osi_event_core *event)
{
bool should_free;
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
osi_event_mark_deleting_locked(event);
should_free = osi_event_release_locked(event);
osi_event_unlock_and_maybe_free(event, should_free);
}
int osi_thread_event_init(void)
{
int ret = -1;
do {
if (osi_mutex_new(&s_osi_event_lock) != 0) {
break;
}
s_osi_event_list = list_new(NULL);
if (s_osi_event_list == NULL) {
break;
}
ret = 0;
} while (0);
if (ret != 0) {
osi_thread_event_deinit();
if (osi_mutex_new(&s_osi_event_lock) != 0) {
return -1;
}
return ret;
s_osi_session_event_list = list_new(NULL);
s_osi_dynamic_event_list = list_new(NULL);
if (s_osi_session_event_list == NULL || s_osi_dynamic_event_list == NULL) {
list_free(s_osi_session_event_list);
list_free(s_osi_dynamic_event_list);
s_osi_session_event_list = NULL;
s_osi_dynamic_event_list = NULL;
osi_mutex_free(&s_osi_event_lock);
return -1;
}
return 0;
}
void osi_thread_event_deinit(void)
{
if (s_osi_event_list != NULL) {
list_free(s_osi_event_list);
s_osi_event_list = NULL;
if (s_osi_event_lock == NULL) {
return;
}
osi_event_lock();
while (s_osi_session_event_list != NULL && !list_is_empty(s_osi_session_event_list)) {
struct osi_event *event = (struct osi_event *)list_front(s_osi_session_event_list);
list_delete(s_osi_session_event_list, event);
osi_event_retire(&event->core);
}
while (s_osi_dynamic_event_list != NULL && !list_is_empty(s_osi_dynamic_event_list)) {
struct osi_dynamic_event *event =
(struct osi_dynamic_event *)list_front(s_osi_dynamic_event_list);
list_delete(s_osi_dynamic_event_list, event);
osi_event_retire(&event->core);
}
list_free(s_osi_session_event_list);
list_free(s_osi_dynamic_event_list);
s_osi_session_event_list = NULL;
s_osi_dynamic_event_list = NULL;
osi_event_unlock();
osi_mutex_free(&s_osi_event_lock);
}