uNetworking / uWebSockets

Simple, secure & standards compliant web server for the most demanding of applications
Apache License 2.0
17.42k stars 1.76k forks source link

.drain not always called #1047

Closed tomrosling closed 4 years ago

tomrosling commented 4 years ago

Hi,

It seems that the .drain handler isn't always called when a socket becomes writable again. What I think is happening is that send() is being called, and sending all of the remaining backpressure, after the socket has actually become writable but before the us_new_socket_context_on_writable callback has been called - so when that callback is actually called, the backpressure is already zero and my drainHandler never gets called. There are places where send() is called internally (particularly, during uncork() in the us_new_socket_context_on_data callback), which are causing this for me.

Since I'm checking ws->getBufferedAmount() before actually sending anything, and waiting for the .drain handler if it returned non-zero so I can try again, my app can get into a state where there are packets to send but it doesn't know the socket is ready to take them.

I'm working around it for now by checking if I can send my queued packets after receiving data, but it doesn't seem ideal.

ghost commented 4 years ago

I will have a look, but please pull latest version. Those function names are old

ghost commented 4 years ago

Your explanation makes perfect sense, you're almost certainly correct. Will see how this can be solved in easiest way

tomrosling commented 4 years ago

Great, thanks!

ghost commented 4 years ago

https://github.com/uNetworking/uWebSockets/blob/c1a969ceddba3afd952ca8b6d3351b452481be6f/src/WebSocketContext.h#L314

If this already is 0, the following logic fails. It should always call drainHandler if backpressure already was 0:

https://github.com/uNetworking/uWebSockets/blob/c1a969ceddba3afd952ca8b6d3351b452481be6f/src/WebSocketContext.h#L332

https://github.com/uNetworking/uWebSockets/blob/c1a969ceddba3afd952ca8b6d3351b452481be6f/src/WebSocketContext.h#L320

ghost commented 4 years ago

Fixed on master, please have a test and reopen if issue persist.

Thanks

tomrosling commented 4 years ago

Appears to be fixed, thanks again for the quick turnaround.

poor1017 commented 1 year ago

Hi @tomrosling

Can you give a tutorial for .drain handler or a example?

I cannot find anything useful for .drain in uWebSockets/examples.

Thank you!

tomrosling commented 1 year ago

Hey,

There's some documentation here: https://github.com/uNetworking/uWebSockets/blob/master/misc/READMORE.md#backpressure-in-websockets

I've changed jobs since raising this issue so no longer have access to the source code I was working with, but from what I recall/can see now, you shouldn't try to send more data than the socket can have buffered. The drain handler is called when space becomes available in the socket's send buffer. So, if you have more packets that need to be sent, you should buffer them in your application layer and send them when .drain is called.

poor1017 commented 1 year ago

@tomrosling

Thank you for your quick reply.

So you mean: If I have a chunk of streaming data, I should buffer the data, send the data in .drain handler. But where to buffer my streaming data? When is .drain called or in which situation .drain will be called?

If I misunderstand your idea, please let me know.

Thank you!

tomrosling commented 1 year ago

You will have to manage buffering it yourself, somewhere in your application.

.drain is called when the socket is able to buffer (internally) more of your data, and you can try sending it again. As far as I recall, it will only be called if you previously tried to send something and failed due to backpressure.

poor1017 commented 1 year ago

Hi @tomrosling

I got your idea almostly.

If my data which need to be sent is small, .drain will not be called in most time, right?

If .drain is called due to backpressure, the operation under .drain is only sending the buffer again, right?

Thank you!

tomrosling commented 1 year ago

Right, if you don't send much data then there will rarely be backpressure, depending on your network connection.

Yes, if you want to send more data then you should wait until drain has been called to do so. I recommend that you try it and see.