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

Adapt bind_socket to make it usable with multiple processes #1009

Merged
merged 14 commits into from Jun 21, 2021
44 changes: 44 additions & 0 deletions tests/test_config.py
Expand Up @@ -355,3 +355,47 @@ def test_ws_max_size() -> None:
config = Config(app=asgi_app, ws_max_size=1000)
config.load()
assert config.ws_max_size == 1000


@pytest.mark.parametrize(
"reload, workers",
[
(True, 1),
(False, 2),
],
ids=["--reload=True --workers=1", "--reload=False --workers=2"],
)
@pytest.mark.skipif(sys.platform == "win32", reason="require unix-like system")
def test_bind_unix_socket_works_with_reload_or_workers(tmp_path, reload, workers):
uds_file = tmp_path / "uvicorn.sock"
config = Config(
app=asgi_app, uds=uds_file.as_posix(), reload=reload, workers=workers
Kludex marked this conversation as resolved.
Show resolved Hide resolved
)
config.load()
sock = config.bind_socket()
assert isinstance(sock, socket.socket)
assert sock.family == socket.AF_UNIX
assert sock.getsockname() == uds_file.as_posix()
sock.close()


@pytest.mark.parametrize(
"reload, workers",
[
(True, 1),
(False, 2),
],
ids=["--reload=True --workers=1", "--reload=False --workers=2"],
)
@pytest.mark.skipif(sys.platform == "win32", reason="require unix-like system")
def test_bind_fd_works_with_reload_or_workers(reload, workers):
fdsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
fd = fdsock.fileno()
config = Config(app=asgi_app, fd=fd, reload=reload, workers=workers)
config.load()
sock = config.bind_socket()
assert isinstance(sock, socket.socket)
assert sock.family == socket.AF_UNIX
assert sock.getsockname() == ""
sock.close()
fdsock.close()
82 changes: 54 additions & 28 deletions uvicorn/config.py
Expand Up @@ -395,37 +395,63 @@ def setup_event_loop(self) -> None:
loop_setup()

def bind_socket(self) -> socket.socket:
family = socket.AF_INET
addr_format = "%s://%s:%d"
logger_args: List[Union[str, int]]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just needed to add this post-merge with master @Kludex
the rest stayed the same

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I removed the hold button and feel free to merge if still ok with this !

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it so weird that the approval is not removed after a commit is pushed. I don't understand the reason behind it.

Anyway, LGTM! :)

if self.uds:
path = self.uds
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.bind(path)
uds_perms = 0o666
os.chmod(self.uds, uds_perms)
except OSError as exc:
logger.error(exc)
sys.exit(1)

if self.host and ":" in self.host:
# It's an IPv6 address.
family = socket.AF_INET6
addr_format = "%s://[%s]:%d"
message = "Uvicorn running on unix socket %s (Press CTRL+C to quit)"
sock_name_format = "%s"
color_message = (
"Uvicorn running on "
+ click.style(sock_name_format, bold=True)
+ " (Press CTRL+C to quit)"
)
logger_args = [self.uds]
elif self.fd:
sock = socket.fromfd(self.fd, socket.AF_UNIX, socket.SOCK_STREAM)
message = "Uvicorn running on socket %s (Press CTRL+C to quit)"
fd_name_format = "%s"
color_message = (
"Uvicorn running on "
+ click.style(fd_name_format, bold=True)
+ " (Press CTRL+C to quit)"
)
logger_args = [sock.getsockname()]
else:
family = socket.AF_INET
addr_format = "%s://%s:%d"

if self.host and ":" in self.host:
# It's an IPv6 address.
family = socket.AF_INET6
addr_format = "%s://[%s]:%d"

sock = socket.socket(family=family)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind((self.host, self.port))
except OSError as exc:
logger.error(exc)
sys.exit(1)

sock = socket.socket(family=family)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind((self.host, self.port))
except OSError as exc:
logger.error(exc)
sys.exit(1)
message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)"
color_message = (
"Uvicorn running on "
+ click.style(addr_format, bold=True)
+ " (Press CTRL+C to quit)"
)
protocol_name = "https" if self.is_ssl else "http"
logger_args = [protocol_name, self.host, self.port]
logger.info(message, *logger_args, extra={"color_message": color_message})
sock.set_inheritable(True)
Kludex marked this conversation as resolved.
Show resolved Hide resolved

message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)"
color_message = (
"Uvicorn running on "
+ click.style(addr_format, bold=True)
+ " (Press CTRL+C to quit)"
)
protocol_name = "https" if self.is_ssl else "http"
logger.info(
message,
protocol_name,
self.host,
self.port,
extra={"color_message": color_message},
)
return sock

@property
Expand Down
3 changes: 3 additions & 0 deletions uvicorn/main.py
@@ -1,4 +1,5 @@
import logging
import os
import platform
import ssl
import sys
Expand Down Expand Up @@ -426,6 +427,8 @@ def run(app: typing.Union[ASGIApplication, str], **kwargs: typing.Any) -> None:
Multiprocess(config, target=server.run, sockets=[sock]).run()
else:
server.run()
if config.uds:
os.remove(config.uds)


if __name__ == "__main__":
Expand Down