Skip to content
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

all workers are being blocked through the use of Unix sockets #2878

Open
1 task done
carson0321 opened this issue Dec 14, 2023 · 5 comments
Open
1 task done

all workers are being blocked through the use of Unix sockets #2878

carson0321 opened this issue Dec 14, 2023 · 5 comments

Comments

@carson0321
Copy link

carson0321 commented Dec 14, 2023

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

@carson0321 carson0321 added the bug label Dec 14, 2023
@ahopkins
Copy link
Member

The issue has nothing to do with the use of your own socket object.

The blocking nature is related to two things

  1. your handlers are not async, therefore they will block the app completely and you can only handle one request at a time.

  2. 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.

@ahopkins ahopkins added question and removed bug labels Dec 14, 2023
@carson0321
Copy link
Author

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)

@sjsadowski
Copy link
Contributor

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?

@carson0321
Copy link
Author

carson0321 commented Dec 15, 2023

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

@sjsadowski
Copy link
Contributor

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.

@sjsadowski sjsadowski self-assigned this Dec 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants