all workers are being blocked through the use of Unix sockets
carson0321 opened this issue · comments
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
In the following code, two APIs using Unix sockets are implemented. API B returns results easily, while API A requires a 10-second wait. When only API B is called, everything appears normal. However, an abnormal behavior is observed when API B is called after API A. Despite having four workers, API A blocks API B.
Demo: https://imgur.com/OepbE4u
Code snippet
import os
import time
from sanic import Sanic, HTTPResponse, Request, response
app = Sanic(__name__)
@app.get("/a")
def base(request: Request) -> HTTPResponse:
print(f"PID: {os.getpid()}")
time.sleep(10)
return response.text("Done")
@app.get("/b")
def fast(request: Request) -> HTTPResponse:
print(f"PID: {os.getpid()}")
return response.text("Fast Done")
if __name__ == '__main__':
import socket
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.bind('/tmp/api.sock')
app.run(host="127.0.0.1", port=4444, workers=4, unix="/tmp/api.sock", debug=True)
Expected Behavior
Everything appears normal when calling API B after API A.
How do you run Sanic?
Sanic CLI
Operating System
Linux
Sanic Version
23.6.0
Additional context
No response
The issue has nothing to do with the use of your own socket object.
The blocking nature is related to two things
-
your handlers are not async, therefore they will block the app completely and you can only handle one request at a time.
-
inside your handlers you are using , time.sleep and not asyncio.sleep. The former is blocking, the latter not.
Sanic works on the premise of asyncio and therefore you may want to do a little research on how async Python works.
Hope this helps. Good luck.
Hi @ahopkins, thanks for your response. I understand that Python has async/await to handle asynchronous operations. However, it appears normal when modified as follows. In my opinion, this behavior is not normal, and it may have nothing to do with asynchronous operation.
import os
import time
from sanic import Sanic, HTTPResponse, Request, response
app = Sanic(__name__)
@app.get("/a")
def base(request: Request) -> HTTPResponse:
print(f"PID: {os.getpid()}")
time.sleep(10)
return response.text("Done")
@app.get("/b")
def fast(request: Request) -> HTTPResponse:
print(f"PID: {os.getpid()}")
return response.text("Fast Done")
if __name__ == '__main__':
app.run(host="127.0.0.1", port=4444, workers=4, debug=True)
Seems to work as expected, blocks for 10 seconds on /a, blocks until completed on b. Still blocks if async keyword is added because time.sleep() is a blocking call. same behavior over unix socket or tcp. If all workers are exhausted blocking on the /a route call, /b is in a pending state until a worker frees up to handle the route.
Can you clarify what you think is broken?
Demo1: https://imgur.com/OepbE4u
Demo2: https://imgur.com/qIy77rE
Hi @sjsadowski, It's my fault that the first original code snippet had the wrong section. I've edited it and noticed another issue that describes a situation reminiscent of this one: #2467 (comment).
I provide two demos using time.sleep(). However, Demo2 differs from Demo1 in that Sanic starts up without a unix socket object. During normal usage, when API B is called after API A, all workers function correctly, and you can observe the printed PID. In Demo1, only a single worker is utilized instead of multiple workers, despite having four workers available.
In summary, by replacing the 'unix' parameter with the 'sock' parameter, all workers function correctly. Do you believe this is the expected behavior?
app.run(host="127.0.0.1", port=4444, workers=4, unix="/tmp/api.sock", debug=True) -> blocked
app.run(host="127.0.0.1", port=4444, workers=4, sock=sock, debug=True) -> ok
app.run(host="127.0.0.1", port=4444, workers=4, debug=True) -> ok
I'll have to dig into the connection handler to identify whether that behavior should be expected. If it is, we should document it. I will update after I've had a chance to investigate.