Gunicorn fail to bind abstract unix domain socket
Abstract Unix domain sockets are not related to the file system and their path starts with \0. When trying to bind gunicorn to such socket we get the following exception:
~/gunicorn$ cat gunicorn.conf.py
bind = "unix:\0/foo/bar"
~/gunicorn$ /usr/bin/python3 /home/ubuntu/.local/bin/gunicorn -w 2 _--config gunicorn.conf.py_ echoapp:app
[2021-12-09 21:19:29 +0000] [1310108] [INFO] Starting gunicorn 20.1.0
Traceback (most recent call last):
File "/home/ubuntu/.local/bin/gunicorn", line 8, in <module>
sys.exit(run())
File "/home/ubuntu/.local/lib/python3.8/site-packages/gunicorn/app/wsgiapp.py", line 67, in run
WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run()
File "/home/ubuntu/.local/lib/python3.8/site-packages/gunicorn/app/base.py", line 231, in run
super().run()
File "/home/ubuntu/.local/lib/python3.8/site-packages/gunicorn/app/base.py", line 72, in run
Arbiter(self).run()
File "/home/ubuntu/.local/lib/python3.8/site-packages/gunicorn/arbiter.py", line 198, in run
self.start()
File "/home/ubuntu/.local/lib/python3.8/site-packages/gunicorn/arbiter.py", line 155, in start
self.LISTENERS = sock.create_sockets(self.cfg, self.log, fds)
File "/home/ubuntu/.local/lib/python3.8/site-packages/gunicorn/sock.py", line 184, in create_sockets
sock = sock_type(addr, conf, log)
File "/home/ubuntu/.local/lib/python3.8/site-packages/gunicorn/sock.py", line 108, in __init__
st = os.stat(addr)
ValueError: embedded null byte
2 questions:
- Is there any open issue for this? tried to look for such but couldn't find it.
- Would you accept PR with a fix for this bug?
I'll just add that abstract Unix domain sockets "is a nonportable Linux extension".
@horowity an abstract socket address is not (yet) supported by Gunicorn: https://github.com/benoitc/gunicorn/blob/master/gunicorn/sock.py#L101-L139
Support of it seems easy. You will need to check the pattern in init (https://github.com/benoitc/gunicorn/blob/master/gunicorn/sock.py#L105) and when Fd is none, ignore the filesystem check that use os.stat when this is an abstract socket.
Similarly in bind, ignore the chown and umask functions when it is an abstract socket : https://github.com/benoitc/gunicorn/blob/master/gunicorn/sock.py#L122-L126
Please add a test for it creating such socket and testing if launch is OK. Let me know if you have any other questions.
@benoitc - could you please review the proposed PR - Abstract unix #2700 for adding such support?
@benoitc - kind reminder, we are waiting for your approval for the PR.
I worked around this like so. In real code you'd likely want to do something a bit more robust. I haven't tested this much yet but it works so far.
class CustomServer(Server):
def create_unix_server(self, sock, config):
# Custom method to handle abstract socket
if config.uds and config.uds.startswith("\0"): # Check for abstract socket
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.bind(config.uds)
sock.listen(config.backlog)
else:
# Fall back to the default behavior for filesystem-based sockets
sock = super().create_unix_server(sock, config)
return sock
async def startup(self, sockets):
config = self.config
uds = config.uds
if uds and uds.startswith("\0"):
sock = self.create_unix_server(None, config)
await super().startup([sock])
else:
await super().startup(sockets)