1 27
import errno
2 27
import sys
3 27
from math import inf
4

5 27
import trio
6 27
from . import socket as tsocket
7

8

9
# Default backlog size:
10
#
11
# Having the backlog too low can cause practical problems (a perfectly healthy
12
# service that starts failing to accept connections if they arrive in a
13
# burst).
14
#
15
# Having it too high doesn't really cause any problems. Like any buffer, you
16
# want backlog queue to be zero usually, and it won't save you if you're
17
# getting connection attempts faster than you can call accept() on an ongoing
18
# basis. But unlike other buffers, this one doesn't really provide any
19
# backpressure. If a connection gets stuck waiting in the backlog queue, then
20
# from the peer's point of view the connection succeeded but then their
21
# send/recv will stall until we get to it, possibly for a long time. OTOH if
22
# there isn't room in the backlog queue... then their connect stalls, possibly
23
# for a long time, which is pretty much the same thing.
24
#
25
# A large backlog can also use a bit more kernel memory, but this seems fairly
26
# negligible these days.
27
#
28
# So this suggests we should make the backlog as large as possible. This also
29
# matches what Golang does. However, they do it in a weird way, where they
30
# have a bunch of code to sniff out the configured upper limit for backlog on
31
# different operating systems. But on every system, passing in a too-large
32
# backlog just causes it to be silently truncated to the configured maximum,
33
# so this is unnecessary -- we can just pass in "infinity" and get the maximum
34
# that way. (Verified on Windows, Linux, macOS using
35
# notes-to-self/measure-listen-backlog.py)
36 27
def _compute_backlog(backlog):
37 27
    if backlog is None:
38 27
        backlog = inf
39
    # Many systems (Linux, BSDs, ...) store the backlog in a uint16 and are
40
    # missing overflow protection, so we apply our own overflow protection.
41
    # https://github.com/golang/go/issues/5030
42 27
    return min(backlog, 0xFFFF)
43

44

45 27
async def open_tcp_listeners(port, *, host=None, backlog=None):
46
    """Create :class:`SocketListener` objects to listen for TCP connections.
47

48
    Args:
49

50
      port (int): The port to listen on.
51

52
          If you use 0 as your port, then the kernel will automatically pick
53
          an arbitrary open port. But be careful: if you use this feature when
54
          binding to multiple IP addresses, then each IP address will get its
55
          own random port, and the returned listeners will probably be
56
          listening on different ports. In particular, this will happen if you
57
          use ``host=None`` – which is the default – because in this case
58
          :func:`open_tcp_listeners` will bind to both the IPv4 wildcard
59
          address (``0.0.0.0``) and also the IPv6 wildcard address (``::``).
60

61
      host (str, bytes-like, or None): The local interface to bind to. This is
62
          passed to :func:`~socket.getaddrinfo` with the ``AI_PASSIVE`` flag
63
          set.
64

65
          If you want to bind to the wildcard address on both IPv4 and IPv6,
66
          in order to accept connections on all available interfaces, then
67
          pass ``None``. This is the default.
68

69
          If you have a specific interface you want to bind to, pass its IP
70
          address or hostname here. If a hostname resolves to multiple IP
71
          addresses, this function will open one listener on each of them.
72

73
          If you want to use only IPv4, or only IPv6, but want to accept on
74
          all interfaces, pass the family-specific wildcard address:
75
          ``"0.0.0.0"`` for IPv4-only and ``"::"`` for IPv6-only.
76

77
      backlog (int or None): The listen backlog to use. If you leave this as
78
          ``None`` then Trio will pick a good default. (Currently: whatever
79
          your system has configured as the maximum backlog.)
80

81
    Returns:
82
      list of :class:`SocketListener`
83

84
    """
85
    # getaddrinfo sometimes allows port=None, sometimes not (depending on
86
    # whether host=None). And on some systems it treats "" as 0, others it
87
    # doesn't:
88
    #   http://klickverbot.at/blog/2012/01/getaddrinfo-edge-case-behavior-on-windows-linux-and-osx/
89 27
    if not isinstance(port, int):
90 27
        raise TypeError("port must be an int not {!r}".format(port))
91

92 27
    backlog = _compute_backlog(backlog)
93

94 27
    addresses = await tsocket.getaddrinfo(
95
        host, port, type=tsocket.SOCK_STREAM, flags=tsocket.AI_PASSIVE
96
    )
97

98 27
    listeners = []
99 27
    unsupported_address_families = []
100 27
    try:
101 27
        for family, type, proto, _, sockaddr in addresses:
102 27
            try:
103 27
                sock = tsocket.socket(family, type, proto)
104 27
            except OSError as ex:
105 27
                if ex.errno == errno.EAFNOSUPPORT:
106
                    # If a system only supports IPv4, or only IPv6, it
107
                    # is still likely that getaddrinfo will return
108
                    # both an IPv4 and an IPv6 address. As long as at
109
                    # least one of the returned addresses can be
110
                    # turned into a socket, we won't complain about a
111
                    # failure to create the other.
112 27
                    unsupported_address_families.append(ex)
113 27
                    continue
114
                else:
115 27
                    raise
116 27
            try:
117
                # See https://github.com/python-trio/trio/issues/39
118 27
                if sys.platform != "win32":
119 16
                    sock.setsockopt(tsocket.SOL_SOCKET, tsocket.SO_REUSEADDR, 1)
120

121 27
                if family == tsocket.AF_INET6:
122 27
                    sock.setsockopt(tsocket.IPPROTO_IPV6, tsocket.IPV6_V6ONLY, 1)
123

124 27
                await sock.bind(sockaddr)
125 27
                sock.listen(backlog)
126

127 27
                listeners.append(trio.SocketListener(sock))
128 27
            except:
129 27
                sock.close()
130 27
                raise
131 27
    except:
132 27
        for listener in listeners:
133 27
            listener.socket.close()
134 27
        raise
135

136 27
    if unsupported_address_families and not listeners:
137 27
        raise OSError(
138
            errno.EAFNOSUPPORT,
139
            "This system doesn't support any of the kinds of "
140
            "socket that that address could use",
141
        ) from trio.MultiError(unsupported_address_families)
142

143 27
    return listeners
144

145

146 27
async def serve_tcp(
147
    handler,
148
    port,
149
    *,
150
    host=None,
151
    backlog=None,
152
    handler_nursery=None,
153
    task_status=trio.TASK_STATUS_IGNORED,
154
):
155
    """Listen for incoming TCP connections, and for each one start a task
156
    running ``handler(stream)``.
157

158
    This is a thin convenience wrapper around :func:`open_tcp_listeners` and
159
    :func:`serve_listeners` – see them for full details.
160

161
    .. warning::
162

163
       If ``handler`` raises an exception, then this function doesn't do
164
       anything special to catch it – so by default the exception will
165
       propagate out and crash your server. If you don't want this, then catch
166
       exceptions inside your ``handler``, or use a ``handler_nursery`` object
167
       that responds to exceptions in some other way.
168

169
    When used with ``nursery.start`` you get back the newly opened listeners.
170
    So, for example, if you want to start a server in your test suite and then
171
    connect to it to check that it's working properly, you can use something
172
    like::
173

174
        from trio.testing import open_stream_to_socket_listener
175

176
        async with trio.open_nursery() as nursery:
177
            listeners = await nursery.start(serve_tcp, handler, 0)
178
            client_stream = await open_stream_to_socket_listener(listeners[0])
179

180
            # Then send and receive data on 'client_stream', for example:
181
            await client_stream.send_all(b"GET / HTTP/1.0\\r\\n\\r\\n")
182

183
    This avoids several common pitfalls:
184

185
    1. It lets the kernel pick a random open port, so your test suite doesn't
186
       depend on any particular port being open.
187

188
    2. It waits for the server to be accepting connections on that port before
189
       ``start`` returns, so there's no race condition where the incoming
190
       connection arrives before the server is ready.
191

192
    3. It uses the Listener object to find out which port was picked, so it
193
       can connect to the right place.
194

195
    Args:
196
      handler: The handler to start for each incoming connection. Passed to
197
          :func:`serve_listeners`.
198

199
      port: The port to listen on. Use 0 to let the kernel pick an open port.
200
          Passed to :func:`open_tcp_listeners`.
201

202
      host (str, bytes, or None): The host interface to listen on; use
203
          ``None`` to bind to the wildcard address. Passed to
204
          :func:`open_tcp_listeners`.
205

206
      backlog: The listen backlog, or None to have a good default picked.
207
          Passed to :func:`open_tcp_listeners`.
208

209
      handler_nursery: The nursery to start handlers in, or None to use an
210
          internal nursery. Passed to :func:`serve_listeners`.
211

212
      task_status: This function can be used with ``nursery.start``.
213

214
    Returns:
215
      This function only returns when cancelled.
216

217
    """
218 27
    listeners = await trio.open_tcp_listeners(port, host=host, backlog=backlog)
219 27
    await trio.serve_listeners(
220
        handler, listeners, handler_nursery=handler_nursery, task_status=task_status
221
    )

Read our documentation on viewing source code .

Loading