[Libwebsockets] client protocol selection criteria discussion

Andy Green andy at warmcat.com
Sun Feb 16 07:02:21 CET 2020



On 2/16/20 3:32 AM, Olivier Langlois wrote:
> On Mon, 2020-02-10 at 00:44 +0000, Andy Green wrote:
>>
>> On February 9, 2020 11:05:23 PM GMT, Olivier Langlois <
>> olivier at olivierlanglois.net
>>> wrote:
>>> I feel like I am currently in uncharted waters in my lws usage and
>>> I am
>>> discovering the limits of what is possible as I figure out how to
>>> accomplish my design goal.
>>
>> Lws is predicated around a single threaded event loop.  Its only way
>> to interoperate with other threads is lws_cancel_service().
>>
>> That's a very simple proposition that goes a long way.  Lws doesn't
>> claim anything to mislead you into thinking it's threadsafe and you
>> can just call its apis from different threads - you can't.
> 
> Fair enough. you can have 1 thread per loop and this is how I intended
> to use 2 threads with lws.

I really suggest you don't... it's FOSS, you can do what you like, but 
from my perspective lws is singlethreaded.

This kind of hack doesn't scale

  - the internal abstraction is a pt / per-thread struct which contains 
the event loop, fd maps for the whole process-worth of fds and other 
things... instead of "per load-balancing server on same machine" that 
becomes "per client connection".

  - each connection is in its own event loop and thread and can't 
interoperate with the other ones without locking

  - if you use a muxed protocol like h2, in lws it does not support 
streams coming from different threads, async thread management depending 
or shared connection or stream state, locking, races etc

If you find missing locking that's generally useful then patches are 
welcome... otherwise this mode isn't supported.

>>> Here is some background to what I'm trying to accomplish:
>>>
>>> 1. Have a client connect to 2 different services
>>
>> It's fine.
>>
>>> 2. Because I don't want 1 service handling impacting the
>>> performance
>>> and responsiveness of the other service handling, having 2 service
>>> threads and have each connection binded to its own thread
>>> exclusively
>>> would be ideal
>>
>> In the lws part, with only two or a dozen streams on a capable
>> microcontroller, it doesn't typically introduce much real latency
>> over what the network traffic itself caused.  If your user code is
>> going to block for cpu or anything else, it's possible to handle all
>> the io as lws wants, put it into shared objects in memory and signal
>> another thread something came / call lws_cancel_service() to alert
>> lws something to do from another thread.  If you don't really expect
>> your user code to block, try it all in the one service thread first.(
>>
>> That will all work fine if, eg, unknown to either of them your two
>> client connections are h2 streams to the same endpoint sharing one
>> network connection.
> 
> Again, I agree. 1 thread doing the LWS I/O for 2 connection won't
> introduce much real latency. My latency concern is from each protocol
> incoming message handling code than the I/O itself. The volume of
> incoming messages can be susbtantially high.
> 
> An alternate design would be:
> 
> 1 LWS thread dispatching messages to 2 worker threads. That would work
> fine

Yes this is what I proposed in the last email, and something I can support.

> but as long as I am able to keep a simpler 2 threads design in the
> real-time realm that is the road that I am favorising if allowed by LWS
> simply because not having to pass the messages between threads would
> keep the message handling latency lower.

...

>>
>>> 3. The 2 service are 2 different protocols.
>>
>> Doesn't make any difference.
>>
>>> I have found that if a connection was created from a service
>>> thread, an
>>> initial binding was created between the thread and the connection
>>> (under which conditions could that binding be broken?).
>>
>> No that's something different.  You almost always use exactly one
>> service thread, that's why a max of 1 is the default.  In the case
>> you're the server and want to serve huge numbers of connections,
>> load-balancing across n event loops on n threads / cores lets you
>> scale beyond what a single thread can do.  It's still a single-
>> threaded event loop, you just have n of them and new connections bind
>> to the most idle one.  It's not what you want.
> 
> I agree that multiple service thread is usually reserved to server
> design. My application message processing is that heavy. I'm confident
> that a single connection can use 100% of its cpu. Therefore the most
> obvious and easiest way to balance my processing load is to assign 1
> connection to its dedicated thread.

There are indeed cases where that's the right way... lws offers 
lws_cancel_service() to mediate between the threads.  On server side, 
lws also offers threadpool that manages flexible binding and lifecycle 
detach between wsi and worker threads.

https://libwebsockets.org/git/libwebsockets/tree/lib/misc/threadpool

... if you want worker threads for client, taking a look to see if 
extending it (or using it) will do what you want would be a supported 
way.  It leaves lws on its event loop as recommended, and adds the 
mechanics to synchronize IO between the lws-world and the 
worker-thread-world per-connection.

>>> However, the only criteria lws use to determine the protocol to use
>>> is
>>> the Protocol header value found in the server reply. Therefore, I
>>> will
>>
>> Yeah that is how ws works.
>>
>>> be forced to use a single callback for my 2 protocols and do the
>>> dispatching myself from the callback.
>>
>> Nope...
>>
>> https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-client.h#n119
> 
> That is awesome! thx for the pointer. I need to check how the server
> will react by seeing the protocol name that I have assign to its API
> service but if it just silently discard it an HS succeed as usual,
> using local_protocol_name could do what I need. I will give it a try.

Generally, for ws the protocol name part of the struct lws_protocols is 
matched against the selected protocol name during ws negotiation anyway. 
  It's because ws allows you to offer p1,p2,p3 as the protocols and the 
server will pick one and tell you which one you will be using... so the 
logical protocol binding can only happen at the end of ws handshake.

>>> I am throwing this idea on the list for discussion. Would it be a
>>> good
>>> feature to make it possible to select the protocol based on the
>>> connection destination?
>>>
>>> ie: if you connect to protocolA server, use protocol A. if you
>>> connect
>>> to protocolB server, use protocol B.
>>>
>>> Something that I need to mention. It is that I am developing a WS
>>> client to use existing WS services. I suspect that I'm a rare breed
>>> as
>>
>> Lots of people use lws with somebody else's apis.
>>
> That is good to know. I'm looking forward seeing what others are doing
> with lws.
> 
> The only other projects that I'm aware of that are using lws are
> mosquitto and csound

It's FOSS... even as the author, I only find out by accident about 
deployments of lws on literally tens of millions of devices, or when 
they have a problem, or an IP lawyer emails me, or they show company 
affiliation on github, or they send me patches (eg, samsung, spotify, 
microsoft etc).

-Andy


More information about the Libwebsockets mailing list