diff --git a/docs/source/reference-io.rst b/docs/source/reference-io.rst
index d6e5ff7b..723ecd78 100644
--- a/docs/source/reference-io.rst
+++ b/docs/source/reference-io.rst
@@ -403,6 +403,10 @@ Socket objects
          left in an unknown state – possibly open, and possibly
          closed. The only reasonable thing to do is to close it.
 
+   .. method:: is_readable
+
+      Check whether the socket is readable or not.
+
    .. method:: sendfile
 
       `Not implemented yet! <https://github.com/python-trio/trio/issues/45>`__
diff --git a/newsfragments/760.feature.rst b/newsfragments/760.feature.rst
new file mode 100644
index 00000000..dd7ab9d3
--- /dev/null
+++ b/newsfragments/760.feature.rst
@@ -0,0 +1,2 @@
+Trio sockets have a new method `~trio.socket.SocketType.is_readable` that allows
+you to check whether a socket is readable. This is useful for HTTP/1.1 clients.
diff --git a/trio/_socket.py b/trio/_socket.py
index e866213f..0e257849 100644
--- a/trio/_socket.py
+++ b/trio/_socket.py
@@ -1,5 +1,6 @@
 import os as _os
 import sys as _sys
+import select
 import socket as _stdlib_socket
 from functools import wraps as _wraps
 
@@ -289,7 +290,7 @@ def socket(
 
 def _sniff_sockopts_for_fileno(family, type, proto, fileno):
     """Correct SOCKOPTS for given fileno, falling back to provided values.
-    
+
     """
     # Wrap the raw fileno into a Python socket object
     # This object might have the wrong metadata, but it lets us easily call getsockopt
@@ -478,6 +479,15 @@ class _SocketType(SocketType):
         if flag in [_stdlib_socket.SHUT_WR, _stdlib_socket.SHUT_RDWR]:
             self._did_shutdown_SHUT_WR = True
 
+    def is_readable(self):
+        # use select.select on Windows, and select.poll everywhere else
+        if _sys.platform == "win32":
+            rready, _, _ = select.select([self._sock], [], [], 0)
+            return bool(rready)
+        p = select.poll()
+        p.register(self._sock, select.POLLIN)
+        return bool(p.poll(0))
+
     async def wait_writable(self):
         await _core.wait_writable(self._sock)
 

Add a method to trio socket objects that checks whether the OS thinks the socket is readable
Say you're writing an HTTP/1.1 client library, that supports connection pooling. When you take an idle connection out of your pool, it might be fine to use... OR, the server might have gotten tired of waiting for that connection to do something while it was sitting idle in the pool, and have already closed it. Because of infelicities in HTTP/1.1's design, there is no 100% reliable way to detect this, because there's a race condition: you might send a new request down the connection just as the server is closing it, and then you have no idea what happened and whether the connection dropped before or after the server started processing your request.

So, we would like to minimize how often this happens. But, because there's actually no way to detect it at the protocol level, there really is no good way to do this using regular high-level APIs. The best approach is: when you take an idle connection out of the pool, before you start using it again, do a quick check whether the underlying socket is readable. You don't want to actually read from it; there's nothing to learn by doing so. If an HTTP/1.1 server sent anything on an idle connection, it's either a 408 Request Timeout or just a preemptory close. You especially don't want to block waiting to receive data, since that would defeat the whole purpose – we just want to check whether you *could* receive data. And you want to do this directly on the raw socket, ignoring things like any TLS encryption layer, because TLS does not really provide any way to do a pure check for whether *unencrypted* bytes are available – to figure that out you first have to read the encrypted bytes and then check if they make a complete frame, and we don't want to read any bytes.

So... for this specific, weird, and incredibly important use case, you don't want to use Trio's high-level `Stream` layer, you want to peek through all the abstraction layers, ignore Trio's socket I/O API entirely, and call `select` or `poll` directly on the underlying socket just to ask the OS whether it is readable.

So far, so good; Trio lets you do all that using supported, public APIs.

But! Suppose that for testing, you want to use a fake virtual networking layer. That's great: we have first-class support for that in Trio's socket I/O API (#170). Except... asks or urllib3 or whatever, for this one operation, currently has to do an end-run around Trio's socket I/O API – it has to directly call the OS's `select` or `poll` operation, which means that it needs a real OS socket, not a fake virtual socket. And that means that currently, our network faking API cannot handle HTTP clients, which is kind of an important use case.

Solution: we should add a method on Trio socket objects that does this readability check. It's pretty straightforward, [though there are a few complications](https://github.com/urllib3/urllib3/blob/master/src/urllib3/util/wait.py). (Mostly, you need to use `select` on windows and `poll` everywhere else.) And the whole point of this is that libraries like asks or urllib3 will call this method instead of doing their own call to `select`/`poll`. And it will do exactly the same thing they would have done by hand. Except..... since it's a method on the socket object, our fake virtual sockets will be able to override it to do a fake virtual `select`/`poll`. And that will solve the problem.
diff --git a/trio/tests/test_socket.py b/trio/tests/test_socket.py
index beab12a9..c03c8bb8 100644
--- a/trio/tests/test_socket.py
+++ b/trio/tests/test_socket.py
@@ -404,6 +404,17 @@ async def test_SocketType_simple_server(address, socket_type):
             assert await client.recv(1) == b"x"
 
 
+async def test_SocketType_is_readable():
+    a, b = tsocket.socketpair()
+    with a, b:
+        assert not a.is_readable()
+        await b.send(b"x")
+        await _core.wait_readable(a)
+        assert a.is_readable()
+        assert await a.recv(1) == b"x"
+        assert not a.is_readable()
+
+
 # On some macOS systems, getaddrinfo likes to return V4-mapped addresses even
 # when we *don't* pass AI_V4MAPPED.
 # https://github.com/python-trio/trio/issues/580

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
3.7
test-requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
asn1crypto==0.24.0
astor==0.8.0
astroid==2.2.5
async-generator==1.10
atomicwrites==1.3.0
attrs==19.1.0
backcall==0.1.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.12.3
coverage==4.5.3
cryptography==2.7
decorator==4.4.0
entrypoints==0.3
exceptiongroup==1.2.2
execnet==2.0.2
flake8==3.7.7
idna==2.8
immutables==0.9
importlib-metadata==0.18
iniconfig==2.0.0
ipython==7.6.0
ipython-genutils==0.2.0
isort==4.3.21
jedi==0.13.3
lazy-object-proxy==1.4.1
mccabe==0.6.1
more-itertools==7.1.0
outcome==1.0.0
packaging==19.0
parso==0.4.0
pexpect==4.7.0
pickleshare==0.7.5
pluggy==0.12.0
prompt-toolkit==2.0.9
ptyprocess==0.6.0
py==1.8.0
pycodestyle==2.5.0
pycparser==2.19
pyflakes==2.1.1
Pygments==2.4.2
pylint==2.3.1
pyOpenSSL==19.0.0
pyparsing==2.4.0
pytest==7.4.4
pytest-asyncio==0.21.2
pytest-cov==2.7.1
pytest-mock==3.11.1
pytest-xdist==3.5.0
six==1.12.0
sniffio==1.1.0
sortedcontainers==2.1.0
tomli==2.0.1
traitlets==4.3.2
-e git+https://github.com/python-trio/trio.git@0b0feb2c7e6e2669d6affaae6da4e1014133834b#egg=trio
trustme==0.5.2
typed-ast==1.3.1
typing_extensions==4.7.1
wcwidth==0.1.7
wrapt==1.11.2
yapf==0.27.0
zipp==0.5.1

name: trio
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - asn1crypto==0.24.0
      - astor==0.8.0
      - astroid==2.2.5
      - async-generator==1.10
      - atomicwrites==1.3.0
      - attrs==19.1.0
      - backcall==0.1.0
      - cffi==1.12.3
      - coverage==4.5.3
      - cryptography==2.7
      - decorator==4.4.0
      - entrypoints==0.3
      - exceptiongroup==1.2.2
      - execnet==2.0.2
      - flake8==3.7.7
      - idna==2.8
      - immutables==0.9
      - importlib-metadata==0.18
      - iniconfig==2.0.0
      - ipython==7.6.0
      - ipython-genutils==0.2.0
      - isort==4.3.21
      - jedi==0.13.3
      - lazy-object-proxy==1.4.1
      - mccabe==0.6.1
      - more-itertools==7.1.0
      - outcome==1.0.0
      - packaging==19.0
      - parso==0.4.0
      - pexpect==4.7.0
      - pickleshare==0.7.5
      - pluggy==0.12.0
      - prompt-toolkit==2.0.9
      - ptyprocess==0.6.0
      - py==1.8.0
      - pycodestyle==2.5.0
      - pycparser==2.19
      - pyflakes==2.1.1
      - pygments==2.4.2
      - pylint==2.3.1
      - pyopenssl==19.0.0
      - pyparsing==2.4.0
      - pytest==7.4.4
      - pytest-asyncio==0.21.2
      - pytest-cov==2.7.1
      - pytest-mock==3.11.1
      - pytest-xdist==3.5.0
      - six==1.12.0
      - sniffio==1.1.0
      - sortedcontainers==2.1.0
      - tomli==2.0.1
      - traitlets==4.3.2
      - trio==0.11.0+dev
      - trustme==0.5.2
      - typed-ast==1.3.1
      - typing-extensions==4.7.1
      - wcwidth==0.1.7
      - wrapt==1.11.2
      - yapf==0.27.0
      - zipp==0.5.1
prefix: /opt/conda/envs/trio

trio/tests/test_socket.py::test_SocketType_is_readable
trio/tests/test_socket.py::test_SocketType_resolve[AddressFamily.AF_INET-addrs0] trio/tests/test_socket.py::test_SocketType_resolve[AddressFamily.AF_INET6-addrs1] trio/tests/test_socket.py::test_getprotobyname
trio/tests/test_socket.py::test__try_sync trio/tests/test_socket.py::test_socket_has_some_reexports trio/tests/test_socket.py::test_getaddrinfo trio/tests/test_socket.py::test_getnameinfo trio/tests/test_socket.py::test_from_stdlib_socket trio/tests/test_socket.py::test_from_fd trio/tests/test_socket.py::test_socketpair_simple trio/tests/test_socket.py::test_socket trio/tests/test_socket.py::test_socket_v6 trio/tests/test_socket.py::test_sniff_sockopts trio/tests/test_socket.py::test_SocketType_basics trio/tests/test_socket.py::test_SocketType_dup trio/tests/test_socket.py::test_SocketType_shutdown trio/tests/test_socket.py::test_SocketType_simple_server[127.0.0.1-AddressFamily.AF_INET] trio/tests/test_socket.py::test_SocketType_simple_server[::1-AddressFamily.AF_INET6] trio/tests/test_socket.py::test_SocketType_unresolved_names trio/tests/test_socket.py::test_SocketType_non_blocking_paths trio/tests/test_socket.py::test_SocketType_connect_paths trio/tests/test_socket.py::test_resolve_remote_address_exception_closes_socket trio/tests/test_socket.py::test_send_recv_variants trio/tests/test_socket.py::test_idna trio/tests/test_socket.py::test_custom_hostname_resolver trio/tests/test_socket.py::test_custom_socket_factory trio/tests/test_socket.py::test_SocketType_is_abstract trio/tests/test_socket.py::test_unix_domain_socket trio/tests/test_socket.py::test_interrupted_by_close
newsfragments/760.feature.rst trio/_socket.py docs/source/reference-io.rst
newsfragments/760.feature.rst trio/_socket.py docs/source/reference-io.rst