diff --git a/READMEs/README.lws_sequencer.md b/READMEs/README.lws_sequencer.md index d8ae8604b..155631455 100644 --- a/READMEs/README.lws_sequencer.md +++ b/READMEs/README.lws_sequencer.md @@ -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. + diff --git a/include/libwebsockets/abstract/abstract.h b/include/libwebsockets/abstract/abstract.h index d6c56a1e4..b9d856a14 100644 --- a/include/libwebsockets/abstract/abstract.h +++ b/include/libwebsockets/abstract/abstract.h @@ -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 diff --git a/include/libwebsockets/lws-client.h b/include/libwebsockets/lws-client.h index 86a50c157..d2eab9dec 100644 --- a/include/libwebsockets/lws-client.h +++ b/include/libwebsockets/lws-client.h @@ -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 ---^ diff --git a/include/libwebsockets/lws-sequencer.h b/include/libwebsockets/lws-sequencer.h index fea857f3b..c31ce7509 100644 --- a/include/libwebsockets/lws-sequencer.h +++ b/include/libwebsockets/lws-sequencer.h @@ -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 diff --git a/lib/abstract/transports/raw-skt.c b/lib/abstract/transports/raw-skt.c index 9be6df098..f51d73fa1 100644 --- a/lib/abstract/transports/raw-skt.c +++ b/lib/abstract/transports/raw-skt.c @@ -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) diff --git a/lib/core-net/connect.c b/lib/core-net/connect.c index dcca4ec80..04c68e4af 100644 --- a/lib/core-net/connect.c +++ b/lib/core-net/connect.c @@ -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) diff --git a/lib/core-net/private.h b/lib/core-net/private.h index 11dbfb1cf..e51010d84 100644 --- a/lib/core-net/private.h +++ b/lib/core-net/private.h @@ -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 */ diff --git a/lib/core-net/sequencer.c b/lib/core-net/sequencer.c index 557df85ad..72c123081 100644 --- a/lib/core-net/sequencer.c +++ b/lib/core-net/sequencer.c @@ -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.