{"record":{"id":"323353670f65a676","repo":"locustio/locust","slug":"zmq-network-broken","errorCode":null,"errorMessage":"ZMQ network broken","messagePattern":"ZMQ network broken","errorType":"exception","errorClass":"RPCError","httpStatus":null,"severity":"error","filePath":"locust/rpc/zmqrpc.py","lineNumber":45,"sourceCode":"            self.socket.send(msg.serialize(), zmq.NOBLOCK)\n        except zmqerr.ZMQError as e:\n            raise RPCSendError(\"ZMQ sent failure\") from e\n\n    @retry()\n    def send_to_client(self, msg):\n        try:\n            self.socket.send_multipart([msg.node_id.encode(), msg.serialize()])\n        except zmqerr.ZMQError as e:\n            raise RPCSendError(\"ZMQ sent failure\") from e\n\n    def recv(self):\n        try:\n            data = self.socket.recv()\n            msg = Message.unserialize(data)\n        except msgerr.ExtraData as e:\n            raise RPCReceiveError(\"ZMQ interrupted message\") from e\n        except zmqerr.ZMQError as e:\n            raise RPCError(\"ZMQ network broken\") from e\n        return msg\n\n    def recv_from_client(self):\n        try:\n            data = self.socket.recv_multipart()\n            addr = data[0].decode()\n        except UnicodeDecodeError as e:\n            raise RPCReceiveError(\"ZMQ interrupted or corrupted message\") from e\n        except zmqerr.ZMQError as e:\n            raise RPCError(\"ZMQ network broken\") from e\n        try:\n            msg = Message.unserialize(data[1])\n        except (UnicodeDecodeError, msgerr.ExtraData) as e:\n            raise RPCReceiveError(\"ZMQ interrupted or corrupted message\", addr=addr) from e\n        return addr, msg\n\n    def close(self, linger=None):\n        self.socket.close(linger=linger)","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/locustio/locust/blob/f391a716e12c2c712e80b5835e877b7933397453/locust/rpc/zmqrpc.py#L27-L63","documentation":"RPCError raised when the ZMQ socket itself fails during recv() (as opposed to message corruption). It wraps zmq.error.ZMQError — most commonly ZMQ's 'context was terminated' or 'socket closed' — indicating the transport is no longer usable, so the library surfaces it as a fatal network error rather than a receive error.","triggerScenarios":"Calling recv() (directly or via wait_for_reply/worker loops) after the ZMQ context was terminated or the socket was closed — e.g. during Environment quit/teardown while a listener greenlet is still blocked in recv.","commonSituations":"Test teardown racing with listener greenlets; process shutdown while recv is blocked; destroying the zmq context while another greenlet still uses the socket.","solutions":["Ensure listener/recv greenlets are stopped before closing the ZMQ context or quitting the runner","Catch RPCError around recv loops during shutdown and exit the loop cleanly","Avoid sharing ZMQ sockets across greenlets that outlive the Environment","If it happens mid-run, restart the runner process — the context is unrecoverable"],"exampleFix":"// before\ndef worker(client, environment):\n    while True:\n        msg = client.recv()  # raises RPCError at shutdown\n// after\ndef worker(client, environment):\n    while True:\n        try:\n            msg = client.recv()\n        except RPCError:\n            break  # context/socket closed, stop listener","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    msg = client.recv()\nexcept RPCError:\n    break  # context/socket closed; exit the receive loop cleanly","preventionTips":["Stop all recv greenlets before quitting the Environment","Never close/destroy ZMQ sockets or context while listeners run","Wrap long-lived recv loops in try/except RPCError for clean shutdown"],"tags":["zmq","distributed","network","shutdown"],"backgroundTag":"zmq-context-terminated","analyzedSha":"f391a716e12c2c712e80b5835e877b7933397453","analyzedAt":"2026-08-29T00:36:13.872Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}