-
Notifications
You must be signed in to change notification settings - Fork 178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Busy WSGITask that does no I/O blocks main thread from flushing outbuf #349
Comments
I'm going to need to play with this a bit, because after a certain amount of content is buffered Waitress should block the yielding thread from continuing to yield so that it can flush output: https://github.com/Pylons/waitress/blob/master/src/waitress/channel.py#L342 That function won't return until we are back under the outbuf high water mark... basically it applies back pressured because in the past Waitress would allow an application to write infinite amount of bytes to disk in out buffers. |
@bertjwregeer thanks for your response! |
It shouldn't take 40 seconds to start flushing though... especially if you are yielding data really quickly as it should quickly reach the high watermark (or exhaust the iterator). The high-water mark is set to 16 MiB: 16777216 I will also admit that this is a very much worst case scenario, most applications have plenty of other requests/processes happening at the same time that do I/O. Adding a You could set the |
@idanzolantz if you want to give this change a shot: #364 you can see if this alleviates your issues. We now attempt to flush each time |
See this comment: #364 (comment) This fixes the issue, reproducer used:
(Thanks @mmerickel) |
Hey guys,
I'm using waitress==1.4.4 + flask==1.0.2 in python 3.6.10, on both MacOS 11.4 (Big Sur) and Linux Alpine 3.11.6 (Kernel 4.19.112).
Created this very simple app:
when running:
curl --raw --request POST 'http://localhost:8000/stream_example' --header 'Content-Type: application/json' --data-raw ''
(or using python'srequests
, same) I'm getting a strange behavior - it takes 30-40 seconds for the data to start streaming back to the client (while in chunked encoding I would expect the data stream to start immediately). memory gets inflated, as if the entire response is being buffered in-memory before sending the first byte.I did some research, and found out that as long as flask/WSGITask keeps generating output with no I/O between yields, main thread struggles to acquire the
outbuf
lock (_flush_some_if_lockable
atchannel.py
always fails to lock and return immediately, hence nothing gets flushed. when examining the output buffer at each iteration, it's clear the data keeps appending and nothing gets flushed. I'm not sure but I think I noticed the initialBytesIOBasedBuffer
object was overflown and rotated toTempfileBasedBuffer
at some point).Out of curiosity, I tried changing:
to:
and viola! response starts flowing immediately.
seams like something between python's GIL and the implementation of
Condition.acquire()
causesself.outbuf_lock.acquire(False)
to almost always peek at the lock state when the lock is held by the application. but if it actually requests the lock (by committing to wait any short timeout), it does get it.I know blocking main thread is a (very) bad idea. Alternatively, forcing the app/task thread to context-switch after every chunk also works (with worst performance for a single request, but probably better performance for production use-cases, where multiple requests are handled in parallel).
Add to
execute()
atWSGITask
class (task.py
) asleep(0)
right after writing each chunk:On unix systems,
time.sleep(0)
gets translated toselect
syscall withtimeout=0
, which is known to cause context-switch.after that, the main thread has no problem in gaining the lock.
In conclusion, I think this locking issue needs to be addressed.
You can either have the framework code that yields chunks from the application iterator to the socket force a context-switch (like I showed above),
or let users know (via your formal documentation / code examples) that applications that "yield chunks too fast" need to inject some sort of I/O or context-switching instructions between yield loops.
BTW, this issue reproduces on waitress==2.0.0 as well.
As said, checked on MacOS & Linux, did not check on Windows.
Thanks,
Idan.
The text was updated successfully, but these errors were encountered: