Backport of upstream salt (3008.x/master) RequestServer.request_handler:
use poll + recv instead of asyncio.wait_for(recv(), 0.3).

Python >= 3.12 implements asyncio.wait_for with asyncio.timeout(), which
cancels the task via _must_cancel even when the awaited pyzmq future has
already completed. The request is consumed from the REP socket and lost;
the REP FSM stays in "expecting send" state and the MWorker never receives
again. Once all workers hit the race the MWorkerQueue blocks on the DEALER,
the ROUTER queues grow without bound and the master stops answering on 4506.

--- a/salt/transport/zeromq.py
+++ b/salt/transport/zeromq.py
@@ -565,7 +565,17 @@ class RequestServer(salt.transport.base.DaemonizedRequestServer):
     async def request_handler(self):
         while not self._event.is_set():
             try:
-                request = await asyncio.wait_for(self._socket.recv(), 0.3)
+                # Use poll + recv instead of asyncio.wait_for(recv(), timeout).
+                # Python 3.12 rewrote wait_for to use asyncio.timeout() which
+                # cancels the task via _must_cancel after data has already been
+                # consumed from the ZMQ socket. On a REP socket this leaves the
+                # FSM in a 'received request / expecting send' state
+                # permanently, deadlocking the worker. poll() never consumes
+                # data, so a timeout here is always safe.
+                events = await self._socket.poll(timeout=300)
+                if not events:
+                    continue
+                request = await self._socket.recv()
                 reply = await self.handle_message(None, request)
                 await self._socket.send(self.encode_payload(reply))
             except zmq.error.Again:
