1# `struct lws_sequencer` introduction 2 3Often a single network action like a client GET is just part of a 4larger series of actions, perhaps involving different connections. 5 6Since lws operates inside an event loop, if the outer sequencing 7doesn't, it can be awkward to synchronize these steps with what's 8happening on the network with a particular connection on the event 9loop thread. 10 11![lws_sequencer](/doc-assets/lws_sequencer.svg) 12 13`struct lws_sequencer` provides a generic way to stage multi-step 14operations from inside the event loop. Because it participates 15in the event loop similar to a wsi, it always operates from the 16service thread context and can access structures that share the 17service thread without locking. It can also provide its own 18higher-level timeout handling. 19 20Naturally you can have many of them running in the same event 21loop operating independently. 22 23Sequencers themselves bind to a pt (per-thread) service thread, 24by default there's only one of these and it's the same as saying 25they bind to an `lws_context`. The sequencer callback may create 26wsi which in turn are bound to a vhost, but the sequencer itself 27is above all that. 28 29## Sequencer timeouts 30 31The sequencer additionally maintains its own second-resolution timeout 32checked by lws for the step being sequenced... this is independent of 33any lws wsi timeouts which tend to be set and reset for very short-term 34timeout protection inside one transaction. 35 36The sequencer timeout operates separately and above any wsi timeout, and 37is typically only reset by the sequencer callback when it receives an 38event indicating a step completed or failed, or it sets up the next sequence 39step. 40 41If the sequencer timeout expires, then the sequencer receives a queued 42`LWSSEQ_TIMED_OUT` message informing it, and it can take corrective action 43or schedule a retry of the step. This message is queued and sent normally 44under the service thread context and in order of receipt. 45 46Unlike lws timeouts which force the wsi to close, the sequencer timeout 47only sends the message. This allows the timeout to be used to, eg, wait 48out a retry cooloff period and then start the retry when the 49`LWSSEQ_TIMED_OUT` is received, according to the state of the sequencer. 50 51## Creating an `struct lws_sequencer` 52 53``` 54typedef struct lws_seq_info { 55 struct lws_context *context; /* lws_context for seq */ 56 int tsi; /* thread service idx */ 57 size_t user_size; /* size of user alloc */ 58 void **puser; /* place ptr to user */ 59 lws_seq_event_cb cb; /* seq callback */ 60 const char *name; /* seq name */ 61 const lws_retry_bo_t *retry; /* retry policy */ 62} lws_seq_info_t; 63``` 64 65``` 66struct lws_sequencer * 67lws_sequencer_create(lws_seq_info_t *info); 68``` 69 70When created, in lws the sequencer objects are bound to a 'per-thread', 71which is by default the same as to say bound to the `lws_context`. You 72can tag them with an opaque user data pointer, and they are also bound to 73a user-specified callback which handles sequencer events 74 75``` 76typedef int (*lws_seq_event_cb)(struct lws_sequencer *seq, void *user_data, 77 lws_seq_events_t event, void *data); 78``` 79 80`struct lws_sequencer` objects are private to lws and opaque to the user. A small 81set of apis lets you perform operations on the pointer returned by the 82create api. 83 84## Queueing events on a sequencer 85 86Each sequencer object can be passed "events", which are held on a per-sequencer 87queue and handled strictly in the order they arrived on subsequent event loops. 88`LWSSEQ_CREATED` and `LWSSEQ_DESTROYED` events are produced by lws reflecting 89the sequencer's lifecycle, but otherwise the event indexes have a user-defined 90meaning and are queued on the sequencer by user code for eventual consumption 91by user code in the sequencer callback. 92 93Pending events are removed from the sequencer queues and sent to the sequencer 94callback from inside the event loop at a rate of one per event loop wait. 95 96## Destroying sequencers 97 98`struct lws_sequencer` objects are cleaned up during context destruction if they are 99still around. 100 101Normally the sequencer callback receives a queued message that 102informs it that it's either failed at the current step, or succeeded and that 103was the last step, and requests that it should be destroyed by returning 104`LWSSEQ_RET_DESTROY` from the sequencer callback. 105 106## Lifecycle considerations 107 108Sequencers may spawn additional assets like client wsi as part of the sequenced 109actions... the lifecycle of the sequencer and the assets overlap but do not 110necessarily depend on each other... that is a wsi created by the sequencer may 111outlive the sequencer. 112 113It's important therefore to detach assets from the sequencer and the sequencer 114from the assets when each step is over and the asset is "out of scope" for the 115sequencer. It doesn't necessarily mean closing the assets, just making sure 116pointers are invalidated. For example, if a client wsi held a pointer to the 117sequencer as its `.user_data`, when the wsi is out of scope for the sequencer 118it can set it to NULL, eg, `lws_set_wsi_user(wsi, NULL);`. 119 120Under some conditions wsi may want to hang around a bit to see if there is a 121subsequent client wsi transaction they can be reused on. They will clean 122themselves up when they time out. 123 124## Watching wsi lifecycle from a sequencer 125 126When a sequencer is creating a wsi as part of its sequence, it will be very 127interested in lifecycle events. At client wsi creation time, the sequencer 128callback can set info->seq to itself in order to receive lifecycle messages 129about its wsi. 130 131|message|meaning| 132|---|---| 133|`LWSSEQ_WSI_CONNECTED`|The wsi has become connected| 134|`LWSSEQ_WSI_CONN_FAIL`|The wsi has failed to connect| 135|`LWSSEQ_WSI_CONN_CLOSE`|The wsi had been connected, but has now closed| 136 137By receiving these, the sequencer can understand when it should attempt 138reconnections or that it cannot progress the sequence. 139 140When dealing with wsi that were created by the sequencer, they may close at 141any time, eg, be closed by the remote peer or an intermediary. The 142`LWSSEQ_WSI_CONN_CLOSE` message may have been queued but since they are 143strictly handled in the order they arrived, before it was 144handled an earlier message may want to cause some api to be called on 145the now-free()-d wsi. To detect this situation safely, there is a 146sequencer api `lws_sequencer_check_wsi()` which peeks the message 147buffer and returns nonzero if it later contains an `LWSSEQ_WSI_CONN_CLOSE` 148already. 149 150