-
-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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
Raise errors from socket.close() #70872
Comments
While looking at a recent failure of test_fileno() in test_urllibnet, I discovered that socket.close() ignores the return value of the close() system call. It looks like it has always been this way: <https://docs.python.org/3/library/socket.html#socket.socket.fileno\>. On the other hand, both FileIO.close() and os.close() raise an exception if the underlying close() call fails. So I propose to make socket.close() also raise an exception for any failure indicated by the underlying close() call. The benefit is that a programming error causing EBADF would be more easily noticed. |
I like the idea :-) |
Victor suggested making these errors be reported by the finalize method (garbage collection). I started investigating this, but faced various test suite failures (test_io, test_curses), as well as many new unhandled exceptions printed out during the tests which do not actually trigger failures. Maybe this could become a worthwhile change, but it needs more investigation and wasn’t my original intention. Finalize-exception.patch is my patch so far. There are many comments over the place that make me uneasy about this change, so I think it should be investigated separately. E.g. Modules/_io/iobase.c: /* Silencing I/O errors is bad, but printing spurious tracebacks is # The try/except block is in case this is called at program |
Here is socket.close.v2.patch which hopefully fixes the test for Windows (still untested by me though) |
New changeset bd665613ed67 by Martin Panter in branch 'default': |
I tweaked the test again for Windows, anticipating that it will raise ENOTSOCK rather than EBADF. |
I think this patch should be reverted. It breaks backwards compatibility: import socket
sock0 = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
sock = socket.socket(
socket.AF_INET, socket.SOCK_STREAM, 0, sock0.fileno())
sock0.close()
sock.close() This code behaves differently on 3.5 vs 3.6. In uvloop (https://github.com/magicstack/uvloop) I create Python sockets for libuv socket handles. Sometimes, those handles are closed by libuv, and I have no control over that. So right now, a lot of uvloop tests fail because socket.close() now can raise an error. It also doesn't make any sense to raise it anyways. socket.close() *must* be safe to call even when the socket is already closed. |
IOW, what is happening in uvloop:
One way to solve this would be to use os.dup() in uvloop (i.e. pass a duplicated FD to libuv). But that would mean that programs that use uvloop will consume file descriptors (limited resource) faster than programs that use vanilla asyncio. Currently, there's a documented way of creating a socket out of an existing FD (fourth argument to the socket's constructor). This means that the FD might be controlled by someone. At least in this case, sock.close() must not raise if it fails. It's OK if the FD is already close, because the Python socket was only wrapping it in the first place. |
I think your code example is not very robust, because the “sock” object refers to a freed file descriptor, and could easily close an unrelated file: $ python3.5 -q
>>> import socket
>>> sock0 = socket.socket()
>>> sock = socket.socket(fileno=sock0.fileno())
>>> sock0.close()
>>> f = open("/dev/null") # Unrelated code/thread opens a file
>>> sock.close() # Closes unrelated file descriptor!
>>> f.close() # Error even in 3.5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 9] Bad file descriptor I am not familiar with your use case or library, but I would suggest that either:
Calling socket.close() should be safe if the Python socket object is registered as closed, but IMO calling close(), or any other method, when the OS socket (file descriptor) has been released behind Python’s back is a programming error. |
If libuv closes the FD (step 3), won’t you get the same sort of problem if the uvloop user tries to do something else with the Python socket object, e.g. call getpeername()? I see the fileno=... parameter for sockets as a parallel to the os.fdopen() function, which does raise exceptions from FileIO.close(). Maybe one option is to only trigger a DeprecationWarning, and raise a proper OSError in a future version. |
Another example: some asyncio (run with uvloop) code:
Suppose the above snippet of code is some real-world program. Now, in Python 3.5 everything works as expected. In Python 3.6, "conn.close()" will raise an exception. Why: uvloop passes the FD of "conn" to libuv, which does its thing and closes the connection when it should be closed. Now:
I can't modify "connect_accepted_socket" to call "detach" on "conn", as it would make "conn" unusable right after the call. This option can't be implemented, as it would break the backwards compatibility.
This is not just about uvloop/libuv. It's about any Python program out there that will break in 3.6. A lot of code extracts the FD out of socket objects and does something with it. We can't just make socket.close() to raise in 3.6 -- that's not how backwards compatibility promise works. Guido, what do you think about this issue? |
My proposal: ignore EBADF in socket.close(). It means that the socket is closed already. It doesn't matter why. |
As Martin explained, getting EBAD means that your code smells. Your |
Maybe we should fix asyncio. Maybe if a user passes an existing socket object into loop.create_connection, it should duplicate the socket. |
After thinking about this problem for a while, I arrived to the conclusion that we need to fix asyncio. Essentially, when a user passes a socket object to the event loop API like 'create_server', they give up the control of the socket to the loop. The loop should detach the socket object and have a full control over it. |
"After thinking about this problem for a while, I arrived to the conclusion that we need to fix asyncio." Ah, you became reasonable :-D "Essentially, when a user passes a socket object to the event loop API like 'create_server', they give up the control of the socket to the loop. The loop should detach the socket object and have a full control over it." I don't know how asyncio should be modified exactly, but I agree that someone should change in asyncio. The question is more about how to reduce headache because of the backward compatibility and don't kill performances. Would you mind to open an issue specific for asyncio? |
Come on... :)
I'll open an issue on GH today to discuss with you/Guido/asyncio devs. |
Guido, Victor, please take a look: python/asyncio#449 |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: