zeromq Context destroy method
The pyzqm tutorial is very good for learning the basics of how to use zmq. I've learned a lot from reading it. Now I'm reading the PyZMQ API documentation because I feel that investing some time in learning the finer apsects of ZMQ is worth the effort. I'm discovering some handy tricks that I thought I'd share on my blog.
You know how you use a Context instance to create sockets? Well, the Context class has a very nice method for closing all sockets that were created by that context. Here's how you do it.
Here's a program that creates two servers and two clients that sends a message every second. After three seconds a timer kicks in and closes all
import zmq from time import sleep from multiprocessing import Process from threading import Timer, Thread context = zmq.Context() def server(name, port): server = context.socket(zmq.ROUTER) server.bind('tcp://*:{}'.format(port)) while True: print('{}: {}'.format(name, server.recv_multipart())) def client(name, port): client = context.socket(zmq.DEALER) client.connect('tcp://localhost:{}'.format(port)) while True: client.send_multipart(["", "From {} with love".format(name)]) sleep(1) def destroy(): context.destroy() if __name__ == "__main__": Process(target = server, args = ('server1', 5563)).start() Process(target = server, args = ('server2', 5564)).start() Process(target = client, args = ('client1', 5563)).start() Process(target = client, args = ('client2', 5564)).start() Timer(3, destroy).start()
However, this program won't work as intended. The clients will keep sending and the servers will keep printing what they receie. Why is this? Because we created the servers and clients as processes! When you create a process a copy is made of all the objects in scope. So the context object that the timer closed was never used to create any sockets! Aha, basic lesson in processes. Actually I made exactly that mistake :).
So let's create the servers and clients as threads instead:
if __name__ == "__main__": Thread(target = server, args = ('server1', 5563)).start() Thread(target = server, args = ('server2', 5564)).start() Thread(target = client, args = ('client1', 5563)).start() Thread(target = client, args = ('client2', 5564)).start() Timer(3, destroy).start()
This time the message flow stops, but not in a very nice way. The server threads throw an ContextTerminated exception, the clients throw ZMQError: Socket operation on non-socket exception, and then the program hangs. It's actually written in the docs:
destroy involves calling zmq_close(), which is NOT threadsafe. If there are active sockets in other threads, this must not be called.
So finally my conclusion is that context.destroy() is mainly useful when you have a single thread using many sockets. There are plenty of use cases for that. Here's a simple example where a single thread has two threads talking to each other (pretty useless, but just for the purpose of demonstation :)
import zmq from time import sleep from multiprocessing import Process from threading import Timer, Thread from itertools import count context = zmq.Context() def main(): server = context.socket(zmq.ROUTER) server.bind('tcp://*:5563{}') client = context.socket(zmq.DEALER) client.connect('tcp://localhost:5563') for i in count(): if i == 3: context.destroy() if not client.closed: client.send_multipart(["", "From client with love"]) sleep(1) print(server.recv_multipart()) else: break if __name__ == "__main__": main()