bridge: implement MSGQ to ZMQ bridge with subscriber-based publishing (#32862)
	
		
	
				
					
				
			implement MSGQ to ZMQ bridge with subscriber-based publishing
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
old-commit-hash: 2faa08c2d6
			
			
				vw-mqb-aeb
			
			
		
							parent
							
								
									d21917ffb3
								
							
						
					
					
						commit
						51fb5009f1
					
				
				 4 changed files with 212 additions and 62 deletions
			
			
		@ -0,0 +1,134 @@ | 
				
			|||||||
 | 
					#include "cereal/messaging/msgq_to_zmq.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cassert> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "common/util.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern ExitHandler do_exit; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static std::string recv_zmq_msg(void *sock) { | 
				
			||||||
 | 
					  zmq_msg_t msg; | 
				
			||||||
 | 
					  zmq_msg_init(&msg); | 
				
			||||||
 | 
					  std::string ret; | 
				
			||||||
 | 
					  if (zmq_msg_recv(&msg, sock, 0) > 0) { | 
				
			||||||
 | 
					    ret.assign((char *)zmq_msg_data(&msg), zmq_msg_size(&msg)); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  zmq_msg_close(&msg); | 
				
			||||||
 | 
					  return ret; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MsgqToZmq::run(const std::vector<std::string> &endpoints, const std::string &ip) { | 
				
			||||||
 | 
					  zmq_context = std::make_unique<ZMQContext>(); | 
				
			||||||
 | 
					  msgq_context = std::make_unique<MSGQContext>(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Create ZMQPubSockets for each endpoint
 | 
				
			||||||
 | 
					  for (auto endpoint : endpoints) { | 
				
			||||||
 | 
					    auto &socket_pair = socket_pairs.emplace_back(); | 
				
			||||||
 | 
					    socket_pair.endpoint = endpoint; | 
				
			||||||
 | 
					    socket_pair.pub_sock = std::make_unique<ZMQPubSocket>(); | 
				
			||||||
 | 
					    int ret = socket_pair.pub_sock->connect(zmq_context.get(), endpoint); | 
				
			||||||
 | 
					    if (ret != 0) { | 
				
			||||||
 | 
					      printf("Failed to create ZMQ publisher for [%s]: %s\n", endpoint.c_str(), zmq_strerror(zmq_errno())); | 
				
			||||||
 | 
					      return; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Start ZMQ monitoring thread to monitor socket events
 | 
				
			||||||
 | 
					  std::thread thread(&MsgqToZmq::zmqMonitorThread, this); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  while (!do_exit) { | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					      std::unique_lock lk(mutex); | 
				
			||||||
 | 
					      cv.wait(lk, [this]() { return do_exit || !sub2pub.empty(); }); | 
				
			||||||
 | 
					      if (do_exit) break; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for (auto sub_sock : msgq_poller->poll(100)) { | 
				
			||||||
 | 
					        std::unique_ptr<Message> msg(sub_sock->receive(true)); | 
				
			||||||
 | 
					        while (msg && sub2pub[sub_sock]->sendMessage(msg.get()) == -1) { | 
				
			||||||
 | 
					          if (errno != EINTR) break; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    util::sleep_for(1);  // Give zmqMonitorThread a chance to acquire the mutex
 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  thread.join(); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MsgqToZmq::zmqMonitorThread() { | 
				
			||||||
 | 
					  std::vector<zmq_pollitem_t> pollitems; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Set up ZMQ monitor for each pub socket
 | 
				
			||||||
 | 
					  for (int i = 0; i < socket_pairs.size(); ++i) { | 
				
			||||||
 | 
					    std::string addr = "inproc://op-bridge-monitor-" + std::to_string(i); | 
				
			||||||
 | 
					    zmq_socket_monitor(socket_pairs[i].pub_sock->sock, addr.c_str(), ZMQ_EVENT_ACCEPTED | ZMQ_EVENT_DISCONNECTED); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void *monitor_socket = zmq_socket(zmq_context->getRawContext(), ZMQ_PAIR); | 
				
			||||||
 | 
					    zmq_connect(monitor_socket, addr.c_str()); | 
				
			||||||
 | 
					    pollitems.emplace_back(zmq_pollitem_t{.socket = monitor_socket, .events = ZMQ_POLLIN}); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  while (!do_exit) { | 
				
			||||||
 | 
					    int ret = zmq_poll(pollitems.data(), pollitems.size(), 1000); | 
				
			||||||
 | 
					    if (ret < 0) { | 
				
			||||||
 | 
					      if (errno == EINTR) { | 
				
			||||||
 | 
					        // Due to frequent EINTR signals from msgq, introduce a brief delay (200 ms)
 | 
				
			||||||
 | 
					        // to reduce CPU usage during retry attempts.
 | 
				
			||||||
 | 
					        util::sleep_for(200); | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      continue; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (int i = 0; i < pollitems.size(); ++i) { | 
				
			||||||
 | 
					      if (pollitems[i].revents & ZMQ_POLLIN) { | 
				
			||||||
 | 
					        // First frame in message contains event number and value
 | 
				
			||||||
 | 
					        std::string frame = recv_zmq_msg(pollitems[i].socket); | 
				
			||||||
 | 
					        if (frame.empty()) continue; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        uint16_t event_type = *(uint16_t *)(frame.data()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Second frame in message contains event address
 | 
				
			||||||
 | 
					        frame = recv_zmq_msg(pollitems[i].socket); | 
				
			||||||
 | 
					        if (frame.empty()) continue; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::unique_lock lk(mutex); | 
				
			||||||
 | 
					        auto &pair = socket_pairs[i]; | 
				
			||||||
 | 
					        if (event_type & ZMQ_EVENT_ACCEPTED) { | 
				
			||||||
 | 
					          printf("socket [%s] connected\n", pair.endpoint.c_str()); | 
				
			||||||
 | 
					          if (++pair.connected_clients == 1) { | 
				
			||||||
 | 
					            // Create new MSGQ subscriber socket and map to ZMQ publisher
 | 
				
			||||||
 | 
					            pair.sub_sock = std::make_unique<MSGQSubSocket>(); | 
				
			||||||
 | 
					            pair.sub_sock->connect(msgq_context.get(), pair.endpoint, "127.0.0.1"); | 
				
			||||||
 | 
					            sub2pub[pair.sub_sock.get()] = pair.pub_sock.get(); | 
				
			||||||
 | 
					            registerSockets(); | 
				
			||||||
 | 
					          } | 
				
			||||||
 | 
					        } else if (event_type & ZMQ_EVENT_DISCONNECTED) { | 
				
			||||||
 | 
					          printf("socket [%s] disconnected\n", pair.endpoint.c_str()); | 
				
			||||||
 | 
					          if (pair.connected_clients == 0 || --pair.connected_clients == 0) { | 
				
			||||||
 | 
					            // Remove MSGQ subscriber socket from mapping and reset it
 | 
				
			||||||
 | 
					            sub2pub.erase(pair.sub_sock.get()); | 
				
			||||||
 | 
					            pair.sub_sock.reset(nullptr); | 
				
			||||||
 | 
					            registerSockets(); | 
				
			||||||
 | 
					          } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        cv.notify_one(); | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Clean up monitor sockets
 | 
				
			||||||
 | 
					  for (int i = 0; i < pollitems.size(); ++i) { | 
				
			||||||
 | 
					    zmq_socket_monitor(socket_pairs[i].pub_sock->sock, nullptr, 0); | 
				
			||||||
 | 
					    zmq_close(pollitems[i].socket); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  cv.notify_one(); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MsgqToZmq::registerSockets() { | 
				
			||||||
 | 
					  msgq_poller = std::make_unique<MSGQPoller>(); | 
				
			||||||
 | 
					  for (const auto &socket_pair : socket_pairs) { | 
				
			||||||
 | 
					    if (socket_pair.sub_sock) { | 
				
			||||||
 | 
					      msgq_poller->registerSocket(socket_pair.sub_sock.get()); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,37 @@ | 
				
			|||||||
 | 
					#pragma once | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <condition_variable> | 
				
			||||||
 | 
					#include <map> | 
				
			||||||
 | 
					#include <memory> | 
				
			||||||
 | 
					#include <mutex> | 
				
			||||||
 | 
					#include <string> | 
				
			||||||
 | 
					#include <vector> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define private public | 
				
			||||||
 | 
					#include "msgq/impl_msgq.h" | 
				
			||||||
 | 
					#include "msgq/impl_zmq.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MsgqToZmq { | 
				
			||||||
 | 
					public: | 
				
			||||||
 | 
					  MsgqToZmq() {} | 
				
			||||||
 | 
					  void run(const std::vector<std::string> &endpoints, const std::string &ip); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected: | 
				
			||||||
 | 
					  void registerSockets(); | 
				
			||||||
 | 
					  void zmqMonitorThread(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  struct SocketPair { | 
				
			||||||
 | 
					    std::string endpoint; | 
				
			||||||
 | 
					    std::unique_ptr<ZMQPubSocket> pub_sock; | 
				
			||||||
 | 
					    std::unique_ptr<MSGQSubSocket> sub_sock; | 
				
			||||||
 | 
					    int connected_clients = 0; | 
				
			||||||
 | 
					  }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  std::unique_ptr<MSGQContext> msgq_context; | 
				
			||||||
 | 
					  std::unique_ptr<ZMQContext> zmq_context; | 
				
			||||||
 | 
					  std::mutex mutex; | 
				
			||||||
 | 
					  std::condition_variable cv; | 
				
			||||||
 | 
					  std::unique_ptr<MSGQPoller> msgq_poller; | 
				
			||||||
 | 
					  std::map<SubSocket *, ZMQPubSocket *> sub2pub; | 
				
			||||||
 | 
					  std::vector<SocketPair> socket_pairs; | 
				
			||||||
 | 
					}; | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue