1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

lws_sequencer_t: allow wsi binding

This commit is contained in:
Andy Green 2019-07-22 14:02:00 -07:00
parent 557d51f1f4
commit b6b6915837
8 changed files with 134 additions and 2 deletions

View file

@ -4,7 +4,7 @@ Often a single network action like a client GET is just part of a
larger series of actions, perhaps involving different connections.
Since lws operates inside an event loop, if the outer sequencing
does not, it can be awkward to synchronize these steps with what's
doesn't, it can be awkward to synchronize these steps with what's
happening on the network with a particular connection on the event
loop thread.
@ -110,3 +110,29 @@ Under some conditions wsi may want to hang around a bit to see if there is a
subsequent client wsi transaction they can be reused on. They will clean
themselves up when they time out.
## Watching wsi lifecycle from a sequencer
When a sequencer is creating a wsi as part of its sequence, it will be very
interested in lifecycle events. At client wsi creation time, the sequencer
callback can set info->seq to itself in order to receive lifecycle messages
about its wsi.
|message|meaning|
|---|---|
|`LWSSEQ_WSI_CONNECTED`|The wsi has become connected|
|`LWSSEQ_WSI_CONN_FAIL`|The wsi has failed to connect|
|`LWSSEQ_WSI_CONN_CLOSE`|The wsi had been connected, but has now closed|
By receiving these, the sequencer can understand when it should attempt
reconnections or that it cannot progress the sequence.
When dealing with wsi that were created by the sequencer, they may close at
any time, eg, be closed by the remote peer or an intermediary. The
`LWSSEQ_WSI_CONN_CLOSE` message may have been queued but since they are
strictly handled in the order they arrived, before it was
handled an earlier message may want to cause some api to be called on
the now-free()-d wsi. To detect this situation safely, there is a
sequencer api `lws_sequencer_check_wsi()` which peeks the message
buffer and returns nonzero if it later contains an `LWSSEQ_WSI_CONN_CLOSE`
already.

View file

@ -73,6 +73,9 @@ typedef struct lws_abs {
const struct lws_abs_transport *at;
const lws_token_map_t *at_tokens;
lws_sequencer_t *seq;
void *opaque_user_data;
/*
* These are filled in by lws_abs_bind_and_create_instance() in the
* instance copy. They do not need to be set when creating the struct

View file

@ -49,6 +49,8 @@ enum lws_client_connect_ssl_connection_flags {
* */
};
typedef struct lws_sequencer lws_sequencer_t;
/** struct lws_client_connect_info - parameters to connect with when using
* lws_client_connect_via_info() */
@ -119,9 +121,16 @@ struct lws_client_connect_info {
* tokens
*/
lws_sequencer_t *seq;
/**< NULL, or an lws_sequencer_t that wants to be given messages about
* this wsi's lifecycle as it connects, errors or closes.
*/
void *opaque_user_data;
/**< This data has no meaning to lws but is applied to the client wsi
* and can be retrieved by user code with lws_get_opaque_user_data()
* and can be retrieved by user code with lws_get_opaque_user_data().
* It's also provided with sequencer messages if the wsi is bound to
* an lws_sequencer_t.
*/
/* Add new things just above here ---^

View file

@ -37,6 +37,10 @@ typedef enum {
LWSSEQ_TIMED_OUT, /* sequencer timeout */
LWSSEQ_HEARTBEAT, /* 1Hz callback */
LWSSEQ_WSI_CONNECTED, /* wsi we bound to us has connected */
LWSSEQ_WSI_CONN_FAIL, /* wsi we bound to us has failed to connect */
LWSSEQ_WSI_CONN_CLOSE, /* wsi we bound to us has closed */
LWSSEQ_USER_BASE = 100 /* define your events from here */
} lws_seq_events_t;
@ -61,6 +65,7 @@ typedef struct lws_sequencer lws_sequencer_t; /* opaque */
typedef lws_seq_cb_return_t (*lws_seq_event_cb)(struct lws_sequencer *seq,
void *user, int event, void *data);
/**
* lws_sequencer_create() - create and bind sequencer to a pt
*
@ -119,6 +124,25 @@ lws_sequencer_destroy(lws_sequencer_t **seq);
LWS_VISIBLE LWS_EXTERN int
lws_sequencer_event(lws_sequencer_t *seq, lws_seq_events_t e, void *data);
/**
* lws_sequencer_check_wsi() - check if wsi still extant
*
* \param seq: the sequencer interested in the wsi
* \param wsi: the wsi we want to confirm hasn't closed yet
*
* Check if wsi still extant, by peeking in the message queue for a
* LWSSEQ_WSI_CONN_CLOSE message about wsi. (Doesn't need to do the same for
* CONN_FAIL since that will never have produced any messages prior to that).
*
* Use this to avoid trying to perform operations on wsi that have already
* closed but we didn't get to that message yet.
*
* Returns 0 if not closed yet or 1 if it has closed but we didn't process the
* close message yet.
*/
LWS_VISIBLE LWS_EXTERN int
lws_sequencer_check_wsi(lws_sequencer_t *seq, struct lws *wsi);
/**
* lws_sequencer_timeout() - set a timeout by which the sequence must have
* completed by a different event or inform the

View file

@ -91,6 +91,13 @@ callback_abs_client_raw_skt(struct lws *wsi, enum lws_callback_reasons reason,
priv->established = 1;
if (priv->abs->ap->accept)
priv->abs->ap->accept(priv->abs->api);
if (wsi->seq)
/*
* we are bound to a sequencer who wants to know about
* our lifecycle events
*/
lws_sequencer_event(wsi->seq, LWSSEQ_WSI_CONNECTED, wsi);
break;
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
@ -98,10 +105,32 @@ callback_abs_client_raw_skt(struct lws *wsi, enum lws_callback_reasons reason,
if (in)
lwsl_user(" %s\n", (const char *)in);
if (wsi->seq)
/*
* we are bound to a sequencer who wants to know about
* our lifecycle events
*/
lws_sequencer_event(wsi->seq, LWSSEQ_WSI_CONN_FAIL,
wsi);
goto close_path;
/* fallthru */
case LWS_CALLBACK_RAW_CLOSE:
if (!user)
break;
if (wsi->seq)
/*
* we are bound to a sequencer who wants to know about
* our lifecycle events
*/
lws_sequencer_event(wsi->seq, LWSSEQ_WSI_CONN_CLOSE,
wsi);
close_path:
lwsl_debug("LWS_CALLBACK_RAW_CLOSE\n");
priv->established = 0;
priv->connecting = 0;
@ -237,6 +266,8 @@ lws_atcrs_client_conn(const lws_abs_t *abs)
i.origin = i.address;
i.context = abs->vh->context;
i.local_protocol_name = "lws-abs-cli-raw-skt";
i.seq = abs->seq;
i.opaque_user_data = abs->opaque_user_data;
priv->wsi = lws_client_connect_via_info(&i);
if (!priv->wsi)

View file

@ -70,6 +70,7 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i)
wsi->context = i->context;
wsi->desc.sockfd = LWS_SOCK_INVALID;
wsi->seq = i->seq;
wsi->vhost = NULL;
if (!i->vhost)

View file

@ -512,6 +512,8 @@ struct lws {
const struct lws_protocols *protocol;
struct lws_dll same_vh_protocol;
lws_sequencer_t *seq; /* associated sequencer if any */
struct lws_dll dll_timeout;
struct lws_dll dll_hrtimer;
struct lws_dll2 dll_buflist; /* guys with pending rxflow */

View file

@ -161,6 +161,42 @@ lws_sequencer_event(lws_sequencer_t *seq, lws_seq_events_t e, void *data)
return 0;
}
/*
* Check if wsi still extant, by peeking in the message queue for a
* LWSSEQ_WSI_CONN_CLOSE message about wsi. (Doesn't need to do the same for
* CONN_FAIL since that will never have produced any messages prior to that).
*
* Use this to avoid trying to perform operations on wsi that have already
* closed but we didn't get to that message yet.
*
* Returns 0 if not closed yet or 1 if it has closed but we didn't process the
* close message yet.
*/
int
lws_sequencer_check_wsi(lws_sequencer_t *seq, struct lws *wsi)
{
lws_seq_event_t *seqe;
struct lws_dll2 *dh;
lws_pt_lock(seq->pt, __func__); /* ----------------------------- pt { */
dh = lws_dll2_get_head(&seq->seq_event_owner);
while (dh) {
seqe = lws_container_of(dh, lws_seq_event_t, seq_event_list);
if (seqe->e == LWSSEQ_WSI_CONN_CLOSE && seqe->data == wsi)
break;
dh = dh->next;
}
lws_pt_unlock(seq->pt); /* } pt ------------------------------------- */
return !!dh;
}
/*
* 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.