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

Portable way of waiting for sockets when all you have is a socket descriptor, not a socket object #400

Closed
Fuyukai opened this issue Jan 13, 2018 · 6 comments

Comments

@Fuyukai
Copy link
Member

Fuyukai commented Jan 13, 2018

I'm writing an async wrapper over psycopg2 for my ORM to work with trio, and I've hit a snag with it - as far as I can see in the docs, there's no universal way of waiting until a file descriptor is readable.

There's trio.hazmat.wait_readable, but that only works on *nix, and wait_socket_readable (as shown in the docs) only works on socket.socket objects.

I took a quick search of the issues and couldn't find anything related to this either.

@njsmith
Copy link
Member

njsmith commented Jan 13, 2018 via email

@Fuyukai
Copy link
Member Author

Fuyukai commented Jan 13, 2018

The asynchronous support for psycopg2 requires selecting on the file descriptors, which requires the ability to select on the connection objects returned.

@njsmith
Copy link
Member

njsmith commented Jan 14, 2018

Oh, I see – so it's only sockets that you want to wait on, but psycopg2 is giving handing you the raw socket descriptors, while trio.hazmat.wait_socket_* expect Python socket objects.

You can convert psycopg2's socket descriptors into Python socket objects by using socket.fromfd. So something like:

class TrioPgConn:
    def __init__(self, ...):
        self._conn = psycopg2.connect(..., async=True)
        self._sock = socket.fromfd(self._conn.fileno())

    async def _wait_ready(self):
        # Cribbing from http://initd.org/psycopg/docs/advanced.html#asynchronous-support
        while True:
            state = self._conn.poll()
            if state == psycopg2.extensions.POLL_OK:
                return
            elif state == psycopg2.extensions.POLL_WRITE:
                await trio.hazmat.wait_socket_writable(self._sock)
            elif state == psycopg2.extensions.POLL_READ:
                await trio.hazmat.wait_socket_readable(self._sock)
            else:
                raise ...

@njsmith njsmith changed the title Universal way of waiting for file descriptors Portable way of waiting for sockets when all you have is a socket descriptor, not a socket object Jan 14, 2018
@njsmith
Copy link
Member

njsmith commented Jan 14, 2018

Longer term, I wonder if we should relax the type-checking in wait_socket_* to make them accept file descriptors (or technically SOCKET handles on windows), or any object with a fileno method. It looks like there are a few reasons why we currently restrict to just socket objects:

  • wait_socket_* accepted random file descriptors on Unix, then it would be possible to write code that accidentally passes non-socket file descriptors into wait_socket_*, and it would seem to work, but then when you tried running your program on Windows it would break. Forcing the use of socket objects makes it harder to make this mistake. This seems like a relatively minor benefit though – the correct way to write such code is to use the plain wait_readable/wait_writable and that... also gives you a program that works on Unix but not Windows.

  • Using socket objects in the Windows backend gives us a bit of robustness against some weird edge cases involving sockets getting closed while we're in the middle of selecting on them. But these are pretty rare cases, and Graceful handling of sockets (or whatever) getting closed while in use #36 is a much more general solution that we need to implement anyway, so this doesn't seem like a big deal either.

  • Maybe select.select on Windows only handles socket objects, not socket descriptors? Not that fixing this would be a big deal, it'd just mean making our own select wrapper which we should possibly do at some point anyway (Windows: wait_{read,writ}able limited to 512 sockets #3). ...OK, no, but I just checked and it's not a problem anyway, select.select is happy to take raw socket descriptors:

In [8]: select.select([s2], [s2], [s2])
Out[8]:
([],
 [<socket.socket fd=684, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 49819), raddr=('127.0.0.1', 49814)>],
 [])

In [9]: select.select([s2.fileno()], [s2.fileno()], [s2.fileno()])
Out[9]: ([], [684], [])

So I guess we should probably start allowing socket descriptors in wait_socket_* (maybe after first fixing #36). Then you could just do await wait_socket_readable(pgconn) directly. Thanks for bringing this up!

@tacaswell
Copy link

I am also interested in this, but for waiting on input events (ex '/dev/event1) on linux.

@njsmith
Copy link
Member

njsmith commented May 27, 2018

@tacaswell If you're interested in Unix specifically, and not sockets, then I think trio.hazmat.wait_readable/trio.hazmat.wait_writable should give you what you need – the awkwardness that this issue is about is specifically a side-effect of dealing with sockets across Windows/Unix. (Also now I'm curious what you're working on but probably there's a better place to talk about that then this issue :-).)

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