mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-16 00:00:07 +01:00
340 lines
8 KiB
C
340 lines
8 KiB
C
/*
|
|
* libwebsockets - lib/core-net/sequencer.c
|
|
*
|
|
* Copyright (C) 2019 Andy Green <andy@warmcat.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation:
|
|
* version 2.1 of the License.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "core/private.h"
|
|
|
|
/*
|
|
* per pending event
|
|
*/
|
|
typedef struct lws_seq_event {
|
|
struct lws_dll2 seq_event_list;
|
|
|
|
void *data;
|
|
lws_seq_events_t e;
|
|
} lws_seq_event_t;
|
|
|
|
/*
|
|
* per sequencer
|
|
*/
|
|
typedef struct lws_sequencer {
|
|
struct lws_dll2 seq_list;
|
|
struct lws_dll2 seq_pend_list;
|
|
struct lws_dll2 seq_to_list;
|
|
|
|
struct lws_dll2_owner seq_event_owner;
|
|
struct lws_context_per_thread *pt;
|
|
lws_seq_event_cb cb;
|
|
const char *name;
|
|
|
|
time_t time_created;
|
|
time_t timeout; /* 0 or time we timeout */
|
|
|
|
char going_down;
|
|
} lws_sequencer_t;
|
|
|
|
#define QUEUE_SANITY_LIMIT 10
|
|
|
|
lws_sequencer_t *
|
|
lws_sequencer_create(struct lws_context *context, int tsi, size_t user_size,
|
|
void **puser, lws_seq_event_cb cb, const char *name)
|
|
{
|
|
struct lws_context_per_thread *pt = &context->pt[tsi];
|
|
lws_sequencer_t *seq = lws_zalloc(sizeof(*seq) + user_size, __func__);
|
|
|
|
if (!seq)
|
|
return NULL;
|
|
|
|
seq->cb = cb;
|
|
seq->pt = pt;
|
|
seq->name = name;
|
|
|
|
*puser = (void *)&seq[1];
|
|
|
|
/* add the sequencer to the pt */
|
|
|
|
lws_pt_lock(pt, __func__); /* ---------------------------------- pt { */
|
|
|
|
lws_dll2_add_tail(&seq->seq_list, &pt->seq_owner);
|
|
|
|
lws_pt_unlock(pt); /* } pt ------------------------------------------ */
|
|
|
|
time(&seq->time_created);
|
|
|
|
/* try to queue the creation cb */
|
|
|
|
if (lws_sequencer_event(seq, LWSSEQ_CREATED, NULL)) {
|
|
lws_dll2_remove(&seq->seq_list);
|
|
lws_free(seq);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return seq;
|
|
}
|
|
|
|
static int
|
|
seq_ev_destroy(struct lws_dll2 *d, void *user)
|
|
{
|
|
lws_seq_event_t *seqe = lws_container_of(d, lws_seq_event_t,
|
|
seq_event_list);
|
|
|
|
lws_dll2_remove(&seqe->seq_event_list);
|
|
lws_free(seqe);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
lws_sequencer_destroy(lws_sequencer_t **pseq)
|
|
{
|
|
lws_sequencer_t *seq = *pseq;
|
|
|
|
/* defeat another thread racing to add events while we are destroying */
|
|
seq->going_down = 1;
|
|
|
|
seq->cb(seq, (void *)&seq[1], LWSSEQ_DESTROYED, NULL);
|
|
|
|
lws_pt_lock(seq->pt, __func__); /* -------------------------- pt { */
|
|
|
|
lws_dll2_remove(&seq->seq_list);
|
|
lws_dll2_remove(&seq->seq_to_list);
|
|
lws_dll2_remove(&seq->seq_pend_list);
|
|
/* remove and destroy any pending events */
|
|
lws_dll2_foreach_safe(&seq->seq_event_owner, NULL, seq_ev_destroy);
|
|
|
|
lws_pt_unlock(seq->pt); /* } pt ---------------------------------- */
|
|
|
|
|
|
lws_free_set_NULL(seq);
|
|
}
|
|
|
|
int
|
|
lws_sequencer_event(lws_sequencer_t *seq, lws_seq_events_t e, void *data)
|
|
{
|
|
lws_seq_event_t *seqe;
|
|
|
|
if (!seq || seq->going_down)
|
|
return 1;
|
|
|
|
seqe = lws_zalloc(sizeof(*seqe), __func__);
|
|
if (!seqe)
|
|
return 1;
|
|
|
|
seqe->e = e;
|
|
seqe->data = data;
|
|
|
|
// lwsl_notice("%s: seq %s: event %d\n", __func__, seq->name, e);
|
|
|
|
lws_pt_lock(seq->pt, __func__); /* ----------------------------- pt { */
|
|
|
|
if (seq->seq_event_owner.count > QUEUE_SANITY_LIMIT) {
|
|
lwsl_err("%s: more than %d events queued\n", __func__,
|
|
QUEUE_SANITY_LIMIT);
|
|
}
|
|
|
|
lws_dll2_add_tail(&seqe->seq_event_list, &seq->seq_event_owner);
|
|
|
|
/* if not already on the pending list, add us */
|
|
if (lws_dll2_is_detached(&seq->seq_pend_list))
|
|
lws_dll2_add_tail(&seq->seq_pend_list, &seq->pt->seq_pend_owner);
|
|
|
|
lws_pt_unlock(seq->pt); /* } pt ------------------------------------- */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* seq should have at least one pending event (he was on the pt's list of
|
|
* sequencers with pending events). Send the top event in the queue.
|
|
*/
|
|
|
|
static int
|
|
lws_sequencer_next_event(struct lws_dll2 *d, void *user)
|
|
{
|
|
lws_sequencer_t *seq = lws_container_of(d, lws_sequencer_t,
|
|
seq_pend_list);
|
|
lws_seq_event_t *seqe;
|
|
struct lws_dll2 *dh;
|
|
int n;
|
|
|
|
/* we should be on the pending list, right? */
|
|
assert(seq->seq_event_owner.count);
|
|
|
|
/* events are only added at tail, so no race possible yet... */
|
|
|
|
dh = lws_dll2_get_head(&seq->seq_event_owner);
|
|
seqe = lws_container_of(dh, lws_seq_event_t, seq_event_list);
|
|
|
|
n = seq->cb(seq, (void *)&seq[1], seqe->e, seqe->data);
|
|
|
|
/* ... have to lock here though, because we will change the list */
|
|
|
|
lws_pt_lock(seq->pt, __func__); /* ----------------------------- pt { */
|
|
|
|
/* detach event from sequencer event list and free it */
|
|
lws_dll2_remove(&seqe->seq_event_list);
|
|
lws_free(seqe);
|
|
|
|
/*
|
|
* if seq has no more pending, remove from pt's list of sequencers
|
|
* with pending events
|
|
*/
|
|
if (!seq->seq_event_owner.count)
|
|
lws_dll2_remove(&seq->seq_pend_list);
|
|
|
|
lws_pt_unlock(seq->pt); /* } pt ------------------------------------- */
|
|
|
|
if (n) {
|
|
lwsl_info("%s: destroying seq '%s' by request\n", __func__,
|
|
seq->name);
|
|
lws_sequencer_destroy(&seq);
|
|
|
|
return LWSSEQ_RET_DESTROY;
|
|
}
|
|
|
|
return LWSSEQ_RET_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* nonpublic helper for the pt to call one event per pending sequencer, if any
|
|
* are pending
|
|
*/
|
|
|
|
int
|
|
lws_pt_do_pending_sequencer_events(struct lws_context_per_thread *pt)
|
|
{
|
|
if (!pt->seq_pend_owner.count)
|
|
return 0;
|
|
|
|
return lws_dll2_foreach_safe(&pt->seq_pend_owner, NULL,
|
|
lws_sequencer_next_event);
|
|
}
|
|
|
|
/* set secs to zero to remove timeout */
|
|
|
|
int
|
|
lws_sequencer_timeout(lws_sequencer_t *seq, int secs)
|
|
{
|
|
lws_dll2_remove(&seq->seq_to_list);
|
|
|
|
if (!secs) {
|
|
/* we are clearing the timeout */
|
|
seq->timeout = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
time(&seq->timeout);
|
|
seq->timeout += secs;
|
|
|
|
/*
|
|
* we sort the pt's list of sequencers with pending timeouts, so it's
|
|
* cheap to check it every second
|
|
*/
|
|
|
|
lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
|
|
seq->pt->seq_to_owner.head) {
|
|
lws_sequencer_t *s = lws_container_of(p, lws_sequencer_t,
|
|
seq_to_list);
|
|
|
|
assert(s->timeout); /* shouldn't be on the list otherwise */
|
|
if (s->timeout >= seq->timeout) {
|
|
/* drop us in before this guy */
|
|
lws_dll2_add_before(&seq->seq_to_list,
|
|
&s->seq_to_list);
|
|
|
|
return 0;
|
|
}
|
|
} lws_end_foreach_dll_safe(p, tp);
|
|
|
|
/*
|
|
* Either nobody on the list yet to compare him to, or he's the
|
|
* longest timeout... stick him at the tail end
|
|
*/
|
|
|
|
lws_dll2_add_tail(&seq->seq_to_list, &seq->pt->seq_to_owner);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* nonpublic helper to check for and handle sequencer timeouts for a whole pt
|
|
*/
|
|
|
|
int
|
|
lws_sequencer_timeout_check(struct lws_context_per_thread *pt, time_t now)
|
|
{
|
|
lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
|
|
pt->seq_to_owner.head) {
|
|
lws_sequencer_t *s = lws_container_of(p, lws_sequencer_t,
|
|
seq_to_list);
|
|
|
|
assert(s->timeout); /* shouldn't be on the list otherwise */
|
|
if (s->timeout <= now) {
|
|
/* seq has timed out... remove him from timeout list */
|
|
lws_sequencer_timeout(s, 0);
|
|
/* queue the message to inform the sequencer */
|
|
lws_sequencer_event(s, LWSSEQ_TIMED_OUT, NULL);
|
|
} else
|
|
/*
|
|
* No need to look further if we met one later than now:
|
|
* the list is sorted in ascending time order
|
|
*/
|
|
return 0;
|
|
|
|
} lws_end_foreach_dll_safe(p, tp);
|
|
|
|
/* send every sequencer a heartbeat message... it can ignore it */
|
|
|
|
lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
|
|
pt->seq_owner.head) {
|
|
lws_sequencer_t *s = lws_container_of(p, lws_sequencer_t,
|
|
seq_list);
|
|
|
|
/* queue the message to inform the sequencer */
|
|
lws_sequencer_event(s, LWSSEQ_HEARTBEAT, NULL);
|
|
|
|
} lws_end_foreach_dll_safe(p, tp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
lws_sequencer_t *
|
|
lws_sequencer_from_user(void *u)
|
|
{
|
|
return &((lws_sequencer_t *)u)[-1];
|
|
}
|
|
|
|
const char *
|
|
lws_sequencer_name(lws_sequencer_t *seq)
|
|
{
|
|
return seq->name;
|
|
}
|
|
|
|
int
|
|
lws_sequencer_secs_since_creation(lws_sequencer_t *seq)
|
|
{
|
|
time_t now;
|
|
|
|
time(&now);
|
|
|
|
return now - seq->time_created;
|
|
}
|