LCOV - code coverage report
Current view: top level - src/plugins/zeromqsend - publish.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 0 112 0.0 %
Date: 2019-09-12 12:28:41 Functions: 0 6 0.0 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief
       5             :  *
       6             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       7             :  */
       8             : 
       9             : #include "zeromqsend.h"
      10             : 
      11             : #include <kdbhelper.h>
      12             : #include <kdblogger.h>
      13             : 
      14             : #include <time.h>   // clock_gettime()
      15             : #include <unistd.h> // usleep()
      16             : 
      17             : /** wait inside loop while waiting for messages (10ms)  */
      18             : #define ELEKTRA_ZEROMQSEND_LOOPDELAY_NS (10 * 1000 * 1000)
      19             : 
      20             : /** first byte of a subscription message */
      21             : #define ELEKTRA_ZEROMQSEND_SUBSCRIPTION_MESSAGE '\x01'
      22             : 
      23             : #define ELEKTRA_ZEROMQSEND_MONITOR_ENDPOINT "inproc://zmqpublish-monitor"
      24             : 
      25             : /**
      26             :  * Receive and return events from a ZeroMQ monitor socket.
      27             :  *
      28             :  * @param  monitor monitor socket
      29             :  * @return         ZMQ_EVENT_ number
      30             :  * @retval  0   if receiving interrupted or no message available
      31             :  * @retval -1   on invalid message
      32             :  */
      33           0 : static int getMonitorEvent (void * monitor)
      34             : {
      35             :         // First frame in message contains event number and value
      36             :         zmq_msg_t msg;
      37           0 :         zmq_msg_init (&msg);
      38           0 :         if (zmq_msg_recv (&msg, monitor, ZMQ_DONTWAIT) == -1)
      39             :         {
      40             :                 // presumably interrupted or no message available
      41             :                 return 0;
      42             :         }
      43           0 :         if (!zmq_msg_more (&msg))
      44             :         {
      45             :                 ELEKTRA_LOG_WARNING ("Invalid monitor message received!");
      46             :                 return -1;
      47             :         }
      48             : 
      49           0 :         uint8_t * data = (uint8_t *) zmq_msg_data (&msg);
      50           0 :         uint16_t event = *(uint16_t *) (data);
      51             : 
      52             :         // Second frame in message contains event address
      53             :         // We receive it to clear the buffer, since we are only
      54             :         // interested in the event number
      55           0 :         zmq_msg_init (&msg);
      56           0 :         if (zmq_msg_recv (&msg, monitor, ZMQ_DONTWAIT) == -1)
      57             :         {
      58             :                 // presumably interrupted
      59             :                 return 0;
      60             :         }
      61           0 :         if (zmq_msg_more (&msg))
      62             :         {
      63             :                 ELEKTRA_LOG_WARNING ("Invalid monitor message received!");
      64             :                 return -1;
      65             :         }
      66             : 
      67           0 :         return event;
      68             : }
      69             : 
      70             : static struct timespec ts_diff (struct timespec now, struct timespec start)
      71             : {
      72             :         struct timespec diff;
      73           0 :         if ((now.tv_nsec - start.tv_nsec) < 0)
      74             :         {
      75           0 :                 diff.tv_sec = now.tv_sec - start.tv_sec - 1;
      76           0 :                 diff.tv_nsec = 1000000000 + now.tv_nsec - start.tv_nsec;
      77             :         }
      78             :         else
      79             :         {
      80           0 :                 diff.tv_sec = now.tv_sec - start.tv_sec;
      81           0 :                 diff.tv_nsec = now.tv_nsec - start.tv_nsec;
      82             :         }
      83             :         return diff;
      84             : }
      85             : 
      86             : /**
      87             :  * Wait for connection message from ZeroMQ monitor socket.
      88             :  *
      89             :  * @param  monitorSocket socket
      90             :  * @retval  1 when connected
      91             :  * @retval -1 on timeout
      92             :  * @retval  0 on other errors
      93             :  */
      94           0 : static int waitForConnection (void * monitorSocket, long connectTimeout)
      95             : {
      96             :         struct timespec wait;
      97             :         struct timespec start;
      98             :         struct timespec now;
      99             :         struct timespec diff;
     100           0 :         time_t startFallback = -1;
     101           0 :         long timeoutSec = (connectTimeout / (1000));
     102           0 :         long timeoutNsec = (connectTimeout % (1000)) * (1000 * 1000);
     103           0 :         if (clock_gettime (CLOCK_MONOTONIC, &start) == -1)
     104             :         {
     105             :                 ELEKTRA_LOG_WARNING ("Using slower fallback for timeout detection");
     106           0 :                 startFallback = time (NULL);
     107             :                 // minimum timeout is 1 second when using the fallback
     108           0 :                 if (timeoutSec == 0)
     109             :                 {
     110           0 :                         timeoutSec = 1;
     111             :                 }
     112             :         }
     113             : 
     114             :         // wait for connection established event
     115           0 :         int connected = 0;
     116           0 :         while (!connected)
     117             :         {
     118           0 :                 wait.tv_sec = 0;
     119           0 :                 wait.tv_nsec = ELEKTRA_ZEROMQSEND_LOOPDELAY_NS;
     120           0 :                 while (!nanosleep (&wait, &wait) && errno == EINTR)
     121             :                         ;
     122             : 
     123           0 :                 int event = getMonitorEvent (monitorSocket);
     124             : 
     125           0 :                 int timeout = 0;
     126           0 :                 if (startFallback == -1)
     127             :                 {
     128           0 :                         clock_gettime (CLOCK_MONOTONIC, &now);
     129           0 :                         diff = ts_diff (now, start);
     130           0 :                         timeout = diff.tv_sec >= timeoutSec && diff.tv_nsec >= timeoutNsec;
     131             :                 }
     132             :                 else
     133             :                 {
     134           0 :                         timeout = time (NULL) - startFallback >= timeoutSec;
     135             :                 }
     136           0 :                 if (timeout)
     137             :                 {
     138             :                         ELEKTRA_LOG_WARNING ("connection timed out. could not publish notification");
     139           0 :                         zmq_close (monitorSocket);
     140           0 :                         return -1;
     141             :                 }
     142             : 
     143           0 :                 switch (event)
     144             :                 {
     145             :                 case ZMQ_EVENT_CONNECTED:
     146             :                         // we do not need the publisher monitor anymore
     147           0 :                         zmq_close (monitorSocket);
     148           0 :                         connected = 1;
     149           0 :                         break;
     150             :                 case -1:
     151             :                         // abort, inconsistencies detected
     152             :                         ELEKTRA_LOG_WARNING ("Cannot monitor connection events");
     153             :                         return 0;
     154             :                         break;
     155             :                 case 0:
     156             :                         // no message available or interrupted, try again
     157             :                         break;
     158             :                 default:
     159             :                         // other ZMQ event, ignore
     160             :                         break;
     161             :                 }
     162             :         }
     163             :         return 1;
     164             : }
     165             : 
     166             : /**
     167             :  * Wait for first subscription message on ZeroMQ socket.
     168             :  *
     169             :  * @param  socket socket
     170             :  * @retval  1 on success
     171             :  * @retval -2 on timeout
     172             :  * @retval  0 on other errors
     173             :  */
     174           0 : static int waitForSubscription (void * socket, long subscribeTimeout)
     175             : {
     176             :         struct timespec start;
     177             :         struct timespec now;
     178             :         struct timespec wait;
     179             :         struct timespec diff;
     180           0 :         time_t startFallback = -1;
     181           0 :         long timeoutSec = (subscribeTimeout / (1000));
     182           0 :         long timeoutNsec = (subscribeTimeout % (1000)) * (1000 * 1000);
     183           0 :         if (clock_gettime (CLOCK_MONOTONIC, &start) == -1)
     184             :         {
     185             :                 ELEKTRA_LOG_WARNING ("Using slower fallback for timeout detection");
     186           0 :                 startFallback = time (NULL);
     187             :                 // minimum timeout is 1 second when using the fallback
     188           0 :                 if (timeoutSec == 0)
     189             :                 {
     190           0 :                         timeoutSec = 1;
     191             :                 }
     192             :         }
     193             : 
     194             :         // wait until we receive the first subscription message
     195             :         zmq_msg_t message;
     196           0 :         zmq_msg_init (&message);
     197           0 :         int hasSubscriber = 0;
     198           0 :         int lastErrno = 0;
     199             :         do
     200             :         {
     201           0 :                 wait.tv_sec = 0;
     202           0 :                 wait.tv_nsec = ELEKTRA_ZEROMQSEND_LOOPDELAY_NS;
     203           0 :                 while (!nanosleep (&wait, &wait) && errno == EINTR)
     204             :                         ;
     205             : 
     206           0 :                 lastErrno = 0;
     207           0 :                 int result = zmq_msg_recv (&message, socket, ZMQ_DONTWAIT);
     208           0 :                 if (result == -1)
     209             :                 {
     210           0 :                         lastErrno = zmq_errno ();
     211             :                 }
     212             : 
     213           0 :                 int timeout = 0;
     214           0 :                 if (startFallback == -1)
     215             :                 {
     216           0 :                         clock_gettime (CLOCK_MONOTONIC, &now);
     217           0 :                         diff = ts_diff (now, start);
     218           0 :                         timeout = diff.tv_sec >= timeoutSec && diff.tv_nsec >= timeoutNsec;
     219             :                 }
     220             :                 else
     221             :                 {
     222           0 :                         timeout = time (NULL) - startFallback >= timeoutSec;
     223             :                 }
     224           0 :                 if (timeout)
     225             :                 {
     226             :                         ELEKTRA_LOG_WARNING ("subscribing timed out. could not publish notification");
     227           0 :                         zmq_msg_close (&message);
     228           0 :                         return -2;
     229             :                 }
     230             : 
     231           0 :                 if (result == -1)
     232             :                 {
     233           0 :                         if (lastErrno != EAGAIN)
     234             :                         {
     235             :                                 ELEKTRA_LOG_WARNING ("receiving failed %s", zmq_strerror (lastErrno));
     236           0 :                                 zmq_msg_close (&message);
     237           0 :                                 return 0;
     238             :                         }
     239             :                 }
     240             :                 else
     241             :                 {
     242             :                         // we have received a message subscription or unsubscription message
     243           0 :                         char * messageData = zmq_msg_data (&message);
     244           0 :                         if (messageData[0] == ELEKTRA_ZEROMQSEND_SUBSCRIPTION_MESSAGE)
     245             :                         {
     246           0 :                                 hasSubscriber = 1;
     247             :                         }
     248             :                 }
     249           0 :         } while (lastErrno == EAGAIN && !hasSubscriber);
     250             : 
     251           0 :         zmq_msg_close (&message);
     252             : 
     253           0 :         return 1;
     254             : }
     255             : 
     256             : /**
     257             :  * @internal
     258             :  * Connect to ZeroMq SUB or XSUB socket bound at endpoint.
     259             :  *
     260             :  * @param  data plugin data
     261             :  * @retval 1 on success
     262             :  * @retval 0 on error
     263             :  */
     264           0 : int elektraZeroMqSendConnect (ElektraZeroMqSendPluginData * data)
     265             : {
     266             :         // create zmq context
     267           0 :         if (!data->zmqContext)
     268             :         {
     269           0 :                 data->zmqContext = zmq_ctx_new ();
     270           0 :                 if (data->zmqContext == NULL)
     271             :                 {
     272             :                         ELEKTRA_LOG_WARNING ("zmq_ctx_new failed %s", zmq_strerror (zmq_errno ()));
     273             :                         return 0;
     274             :                 }
     275             :         }
     276             : 
     277           0 :         if (!data->zmqPublisher)
     278             :         {
     279             :                 // create publish socket
     280           0 :                 data->zmqPublisher = zmq_socket (data->zmqContext, ZMQ_XPUB);
     281           0 :                 if (data->zmqPublisher == NULL)
     282             :                 {
     283             :                         ELEKTRA_LOG_WARNING ("zmq_socket failed %s", zmq_strerror (zmq_errno ()));
     284           0 :                         zmq_close (data->zmqPublisher);
     285           0 :                         return 0;
     286             :                 }
     287             : 
     288             :                 // setup socket monitor
     289           0 :                 if (zmq_socket_monitor (data->zmqPublisher, ELEKTRA_ZEROMQSEND_MONITOR_ENDPOINT, ZMQ_EVENT_CONNECTED) == -1)
     290             :                 {
     291             :                         ELEKTRA_LOG_WARNING ("creating socket monitor failed: %s", zmq_strerror (zmq_errno ()));
     292             :                         return 0;
     293             :                 }
     294           0 :                 data->zmqPublisherMonitor = zmq_socket (data->zmqContext, ZMQ_PAIR);
     295           0 :                 if (zmq_connect (data->zmqPublisherMonitor, ELEKTRA_ZEROMQSEND_MONITOR_ENDPOINT) != 0)
     296             :                 {
     297             :                         ELEKTRA_LOG_WARNING ("connecting to socket monitor failed: %s", zmq_strerror (zmq_errno ()));
     298             :                         return 0;
     299             :                 }
     300             : 
     301             :                 // connect to endpoint
     302           0 :                 int result = zmq_connect (data->zmqPublisher, data->endpoint);
     303           0 :                 if (result != 0)
     304             :                 {
     305             :                         ELEKTRA_LOG_WARNING ("zmq_connect error: %s", zmq_strerror (zmq_errno ()));
     306           0 :                         zmq_close (data->zmqPublisher);
     307           0 :                         data->zmqPublisher = NULL;
     308           0 :                         return 0;
     309             :                 }
     310             :         }
     311             : 
     312             :         return 1;
     313             : }
     314             : 
     315             : /**
     316             :  * Publish notification on ZeroMq connection.
     317             :  *
     318             :  * @param changeType type of change
     319             :  * @param keyName    name of changed key
     320             :  * @param data       plugin data
     321             :  * @retval 1 on success
     322             :  * @retval -1 on connection timeout
     323             :  * @retval -2 on subscription timeout
     324             :  * @retval 0 on other errors
     325             :  */
     326           0 : int elektraZeroMqSendPublish (const char * changeType, const char * keyName, ElektraZeroMqSendPluginData * data)
     327             : {
     328           0 :         if (!elektraZeroMqSendConnect (data))
     329             :         {
     330             :                 ELEKTRA_LOG_WARNING ("could not connect to endpoint");
     331             :                 return 0;
     332             :         }
     333             : 
     334             :         // wait for subscription message
     335           0 :         if (!data->hasSubscriber)
     336             :         {
     337             :                 // NOTE zmq_connect() returns before a connection is established since
     338             :                 // ZeroMq asynchronously does that in the background.
     339             :                 // All notifications sent before the connection is established and and the
     340             :                 // socket has a subscriber are lost since ZMQ_(X)PUB sockets handle message
     341             :                 // filtering: Without subscribers all messages are discarded.
     342             :                 // Therefore we monitor the socket for until the connection is established
     343             :                 // and then wait for the first subscription message.
     344             :                 // A ZMQ_XPUB socket instead of a ZMQ_PUB socket allows us to receive
     345             :                 // subscription messages
     346           0 :                 int result = waitForConnection (data->zmqPublisherMonitor, data->connectTimeout);
     347           0 :                 if (result != 1)
     348             :                 {
     349             :                         return result;
     350             :                 }
     351           0 :                 result = waitForSubscription (data->zmqPublisher, data->subscribeTimeout);
     352           0 :                 if (result == 1)
     353             :                 {
     354           0 :                         data->hasSubscriber = 1;
     355             :                 }
     356             :                 else
     357             :                 {
     358             :                         return result;
     359             :                 }
     360             :         }
     361             : 
     362             :         // send notification
     363           0 :         if (!elektraZeroMqSendNotification (data->zmqPublisher, changeType, keyName))
     364             :         {
     365             :                 ELEKTRA_LOG_WARNING ("could not send notification");
     366             :                 return 0;
     367             :         }
     368             : 
     369           0 :         return 1;
     370             : }
     371             : 
     372             : /**
     373             :  * @internal
     374             :  * Send notification over ZeroMq socket.
     375             :  *
     376             :  * zmq_send() asynchronous.
     377             :  * Processing already handled in a thread created by ZeroMq.
     378             :  *
     379             :  * @param  socket     ZeroMq socket
     380             :  * @param  changeType type of change
     381             :  * @param  keyName    name of changed key
     382             :  * @retval 1 on success
     383             :  * @retval 0 on error
     384             :  */
     385           0 : int elektraZeroMqSendNotification (void * socket, const char * changeType, const char * keyName)
     386             : {
     387             :         unsigned int size;
     388             : 
     389             :         // Send change type
     390           0 :         size = zmq_send (socket, changeType, elektraStrLen (changeType), ZMQ_SNDMORE);
     391           0 :         if (size != elektraStrLen (changeType))
     392             :         {
     393             :                 return 0;
     394             :         }
     395             : 
     396           0 :         size = zmq_send (socket, keyName, elektraStrLen (keyName), 0);
     397           0 :         if (size != elektraStrLen (keyName))
     398             :         {
     399             :                 return 0;
     400             :         }
     401             : 
     402           0 :         return 1;
     403             : }

Generated by: LCOV version 1.13