• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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