-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
asyncio.start_unix_server maybe shouldn’t default to cleanup_socket=True when sock parameter is passed #133354
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
Comments
Hey, I can take this issue. Before creating a PR though, I'd want to confirm whether this was intended or not, because it seems like something that was intentional. |
Doing cleanup by default when |
Agreed that getting an opinion, ideally from whoever made the change initially, would be good. |
Haha yea I was searching the github PRs tab, grepping, listing via gh-cli, and I genuinely couldn't find it. I was able to narrow it down to before python 3.13 and after python 3.12. Is there a standardized way to find who initially made a change? |
The discussion there mentions the use of the |
@CendioOssman perhaps you have some thoughts on this, as the author of the original change? |
(Side note, if you want to get the inode number for the listening socket, why are you doing |
The idea was that since this creates a server object, it implies a great deal of ownership. E.g. accepting and creating new sockets. Secondly, removing all traces of the socket when it is closed is how most sockets behave, except for Unix sockets. Hence, having the cleanup be enabled by default seemed like the most useful and least surprising option. Admittedly, systemd socket activation was not considered. |
I seem to recall that the socket's inode number was from the magical socket file system, and not from the file on disk. So this is a race, yes, but no alternative was found. |
Ah, good point. I didn’t actually test it; it’s possible that |
Philosophically, I think the issue regarding ownership is that any socket (UNIX-domain or otherwise) ought to be cleaned up once the socket no longer exists. The problem is, with any socket (UNIX-domain or otherwise), the socket no longer exists once the last file descriptor referring to it is closed, not once this particular file descriptor referring to it is closed. With non-UNIX-in-filesystem sockets (i.e. non-UNIX sockets or UNIX sockets outside the filesystem), that’s trivial: the kernel cleans up the socket once the last descriptor is closed. With UNIX-in-filesystem sockets, if you get the |
Indeed, the library code cannot know for sure and has to be told by the application. So the question was really what the most useful default should be. I made the assumption that the vast majority of cases (but not all) would have the asyncio server as the sole owner of the socket. Hence, that cleanup should be on by default. That would reduce the risk of developers overlooking the setting. And it keeps application code cleaner, as they don't need to explicitly specify this. I think it would be a big mistake if the default was changed when Given that the usage of |
What would be the issue with making cleanup_socket a required argument as opposed to having a default? Requiring the argument would arguably make the function more clear IMO. |
If this were new, I’d agree with you more strongly. I’m mostly bothered because it’s a compatibility break from Python 3.12. And not even one that’s nice to work around, given that I have to write this ugliness: if sys.hexversion >= 0x030D00F0:
async def start_unix_server_from_socket(sock: socket.socket) -> asyncio.Server:
return await asyncio.start_unix_server(handle_connection, sock=sock, cleanup_socket=False)
else:
async def start_unix_server_from_socket(sock: socket.socket) -> asyncio.Server:
return await asyncio.start_unix_server(handle_connection, sock=sock)
That would arguably be nice, but would break backwards compatibility with 3.12 even more. |
I understand where Mr. Ossman is coming from and I think his defaults were sane. I am a little uneasy about the compatibility break and the problem with socket activation, but a tradeoff needed to be made and IMHO it's a fair trade off, given you can always undo it with an additional argument, albiet with additional code. Python, at least to my knowledge, is very philosophically implicit, and a lot of the features of the language will abstract lower level logic or do things implicitly (the type system, pointers, large amounts of metadata that you sometimes can't control) that can sometimes make the language easier or more difficult to work in, but will usually provide a very fast and seamless experience developing in the language with the tradeoff of taking away control. That's why it's so widely used in prototyping and research. So I feel like the discussion earlier about giving the programmer less flexibility by default is largely irrelivent. The main issue at this point is the compatibility break, but I've already spoken on that. I definitely do think we need to open a separate issue to deal with the problem with the documentation. I can do that if you guys want. |
I was planning on making a PR for the docs myself (assuming you mean the missing parameter on |
Yea, I can take it. |
What is your opinion on factoring out cleanup_socket logic into a separate function? |
Bug report
Bug description:
When
asyncio.start_unix_server
is called and thesock
parameter is passed (rather thanpath
), a server is wrapped around an existing socket. In this case, the fact thatcleanup_socket
defaults toTrue
is very weird and potentially broken: it means that asyncio will try to delete a socket that someone else created (either other code in the same process, or, in the case of e.g. systemd socket passing, a socket created by a different program).This is problematic for a couple of reasons when using systemd socket passing:
os.stat
call increate_unix_server
blows up.So this is definitely a nontrivial backwards compatibility break from 3.12 (which didn’t have cleanup logic at all and didn’t have the parameter) to 3.13, despite not being mentioned in the release notes. It also seems like a bit of a footgun in general, not to mention the fact that in implementation
start_unix_server
takes arbitrary kwargs and forwards them tocreate_unix_server
, but the documentation instead mentions each parameter explicitly and doesn’t mentioncleanup_socket
at all.CPython versions tested on:
3.13
Operating systems tested on:
Linux
The text was updated successfully, but these errors were encountered: