void zyre_peer_refresh (zyre_peer_t *self, uint64_t evasive_timeout, uint64_t expired_timeout) { assert (self); self->evasive_at = zclock_mono () + evasive_timeout; self->expired_at = zclock_mono () + expired_timeout; }
static int zyre_node_ping_peer (const char *key, void *item, void *argument) { zyre_peer_t *peer = (zyre_peer_t *) item; zyre_node_t *self = (zyre_node_t *) argument; if (zclock_mono () >= zyre_peer_expired_at (peer)) { if (self->verbose) zsys_info ("(%s) peer expired name=%s endpoint=%s", self->name, zyre_peer_name (peer), zyre_peer_endpoint (peer)); zyre_node_remove_peer (self, peer); } else if (zclock_mono () >= zyre_peer_evasive_at (peer)) { // If peer is being evasive, force a TCP ping. // TODO: do this only once for a peer in this state; // it would be nicer to use a proper state machine // for peer management. if (self->verbose) zsys_info ("(%s) peer seems dead/slow name=%s endpoint=%s", self->name, zyre_peer_name (peer), zyre_peer_endpoint (peer)); zre_msg_t *msg = zre_msg_new (ZRE_MSG_PING); zyre_peer_send (peer, &msg); // Inform the calling application this peer is being evasive zstr_sendm (self->outbox, "EVASIVE"); zstr_sendm (self->outbox, zyre_peer_identity (peer)); zstr_send (self->outbox, zyre_peer_name (peer)); } return 0; }
static long s_tickless (zloop_t *self) { // Calculate tickless timer, up to 1 hour int64_t tickless = zclock_mono () + 1000 * 3600; // Scan timers, which are not sorted // TODO: sort timers properly on insertion s_timer_t *timer = (s_timer_t *) zlistx_first (self->timers); while (timer) { // Find earliest timer if (tickless > timer->when) tickless = timer->when; timer = (s_timer_t *) zlistx_next (self->timers); } // Tickets are sorted, so check first ticket s_ticket_t *ticket = (s_ticket_t *) zlistx_first (self->tickets); if (ticket && tickless > ticket->when) tickless = ticket->when; long timeout = (long) (tickless - zclock_mono ()); if (timeout < 0) timeout = 0; if (self->verbose) zsys_debug ("zloop polling for %d msec", (int) timeout); return timeout * ZMQ_POLL_MSEC; }
// -------------------------------------------------------------------------- // Self test of this class void zclock_test (bool verbose) { printf (" * zclock: "); // @selftest int64_t start = zclock_time (); zclock_sleep (10); assert ((zclock_time () - start) >= 10); start = zclock_mono (); int64_t usecs = zclock_usecs (); zclock_sleep (10); assert ((zclock_mono () - start) >= 10); assert ((zclock_usecs () - usecs) >= 10000); char *timestr = zclock_timestr (); if (verbose) puts (timestr); freen (timestr); #if defined (__WINDOWS__) zsys_shutdown(); #endif // @end printf ("OK\n"); }
void zloop_ticket_reset (zloop_t *self, void *handle) { s_ticket_t *ticket = (s_ticket_t *) handle; assert (ticket->tag == TICKET_TAG); ticket->when = zclock_mono () + ticket->delay; zlistx_move_end (self->tickets, ticket->list_handle); }
static s_ticket_t * s_ticket_new (size_t delay, zloop_timer_fn handler, void *arg) { s_ticket_t *self = (s_ticket_t *) zmalloc (sizeof (s_ticket_t)); assert (self); self->tag = TICKET_TAG; self->delay = delay; self->when = zclock_mono () + delay; self->handler = handler; self->arg = arg; return self; }
void zbeacon (zsock_t *pipe, void *args) { self_t *self = s_self_new (pipe); assert (self); // Signal successful initialization zsock_signal (pipe, 0); while (!self->terminated) { // Poll on API pipe and on UDP socket zmq_pollitem_t pollitems [] = { { zsock_resolve (self->pipe), 0, ZMQ_POLLIN, 0 }, { NULL, self->udpsock, ZMQ_POLLIN, 0 } }; long timeout = -1; if (self->transmit) { timeout = (long) (self->ping_at - zclock_mono ()); if (timeout < 0) timeout = 0; } int pollset_size = self->udpsock? 2: 1; if (zmq_poll (pollitems, pollset_size, timeout * ZMQ_POLL_MSEC) == -1) break; // Interrupted if (pollitems [0].revents & ZMQ_POLLIN) s_self_handle_pipe (self); if (pollitems [1].revents & ZMQ_POLLIN) s_self_handle_udp (self); if (self->transmit && zclock_mono () >= self->ping_at) { // Send beacon to any listening peers if (zsys_udp_send (self->udpsock, self->transmit, &self->broadcast, sizeof (inaddr_t))) // Try to recreate UDP socket on interface s_self_prepare_udp (self); self->ping_at = zclock_mono () + self->interval; } } s_self_destroy (&self); }
static int s_self_handle_pipe (self_t *self) { // Get just the command off the pipe char *command = zstr_recv (self->pipe); if (!command) return -1; // Interrupted if (self->verbose) zsys_info ("zbeacon: API command=%s", command); if (streq (command, "VERBOSE")) self->verbose = true; else if (streq (command, "CONFIGURE")) { int port; int rc = zsock_recv (self->pipe, "i", &port); assert (rc == 0); s_self_configure (self, port); } else if (streq (command, "PUBLISH")) { zframe_destroy (&self->transmit); zsock_recv (self->pipe, "fi", &self->transmit, &self->interval); assert (zframe_size (self->transmit) <= UDP_FRAME_MAX); if (self->interval == 0) self->interval = INTERVAL_DFLT; // Start broadcasting immediately self->ping_at = zclock_mono (); } else if (streq (command, "SILENCE")) zframe_destroy (&self->transmit); else if (streq (command, "SUBSCRIBE")) { zframe_destroy (&self->filter); self->filter = zframe_recv (self->pipe); assert (zframe_size (self->filter) <= UDP_FRAME_MAX); } else if (streq (command, "UNSUBSCRIBE")) zframe_destroy (&self->filter); else if (streq (command, "$TERM")) self->terminated = true; else { zsys_error ("zbeacon: - invalid command: %s", command); assert (false); } zstr_free (&command); return 0; }
static s_timer_t * s_timer_new (int timer_id, size_t delay, size_t times, zloop_timer_fn handler, void *arg) { s_timer_t *self = (s_timer_t *) zmalloc (sizeof (s_timer_t)); assert (self); self->timer_id = timer_id; self->delay = delay; self->times = times; self->when = zclock_mono () + delay; self->handler = handler; self->arg = arg; return self; }
static void node_actor (zsock_t *pipe, void *args) { zyre_t *node = zyre_new (NULL); if (!node) return; // Could not create new node zyre_set_verbose (node); zyre_set_endpoint (node, "inproc://%s", (char *) args); free (args); // Connect to test hub zyre_gossip_connect (node, "inproc://zyre-hub"); zyre_start (node); zsock_signal (pipe, 0); // Signal "ready" to caller int counter = 0; char *to_peer = NULL; // Either of these set, char *to_group = NULL; // and we set a message char *cookie = NULL; zpoller_t *poller = zpoller_new (pipe, zyre_socket (node), NULL); int64_t trigger = zclock_mono () + 1000; while (true) { void *which = zpoller_wait (poller, randof (1000)); if (!which) break; // Interrupted // $TERM from parent means exit; anything else is breach of // contract so we should assert if (which == pipe) { char *command = zstr_recv (pipe); assert (streq (command, "$TERM")); zstr_free (&command); break; // Finished } // Process an event from node if (which == zyre_socket (node)) { zmsg_t *incoming = zyre_recv (node); if (!incoming) break; // Interrupted char *event = zmsg_popstr (incoming); char *peer = zmsg_popstr (incoming); char *name = zmsg_popstr (incoming); if (streq (event, "ENTER")) // Always say hello to new peer to_peer = strdup (peer); else if (streq (event, "EXIT")) // Always try talk to departed peer to_peer = strdup (peer); else if (streq (event, "WHISPER")) { // Send back response 1/2 the time if (randof (2) == 0) { to_peer = strdup (peer); cookie = zmsg_popstr (incoming); } } else if (streq (event, "SHOUT")) { to_peer = strdup (peer); to_group = zmsg_popstr (incoming); cookie = zmsg_popstr (incoming); // Send peer response 1/3rd the time if (randof (3) > 0) { free (to_peer); to_peer = NULL; } // Send group response 1/3rd the time if (randof (3) > 0) { free (to_group); to_group = NULL; } } else if (streq (event, "JOIN")) { char *group = zmsg_popstr (incoming); printf ("I: %s joined %s\n", name, group); free (group); } else if (streq (event, "LEAVE")) { char *group = zmsg_popstr (incoming); printf ("I: %s left %s\n", name, group); free (group); } free (event); free (peer); free (name); zmsg_destroy (&incoming); // Send outgoing messages if needed if (to_peer) { zyre_whispers (node, to_peer, "%d", counter++); free (to_peer); to_peer = NULL; } if (to_group) { zyre_shouts (node, to_group, "%d", counter++); free (to_group); to_group = NULL; } if (cookie) { free (cookie); cookie = NULL; } } if (zclock_mono () >= trigger) { trigger = zclock_mono () + 1000; char group [10]; sprintf (group, "GROUP%03d", randof (MAX_GROUP)); if (randof (4) == 0) zyre_join (node, group); else if (randof (3) == 0) zyre_leave (node, group); } } zpoller_destroy (&poller); zyre_destroy (&node); }
void zbeacon_test (bool verbose) { printf (" * zbeacon: "); if (verbose) printf ("\n"); // @selftest // Test 1 - two beacons, one speaking, one listening // Create speaker beacon to broadcast our service zactor_t *speaker = zactor_new (zbeacon, NULL); assert (speaker); if (verbose) zstr_sendx (speaker, "VERBOSE", NULL); zsock_send (speaker, "si", "CONFIGURE", 9999); char *hostname = zstr_recv (speaker); if (!*hostname) { printf ("OK (skipping test, no UDP broadcasting)\n"); zactor_destroy (&speaker); free (hostname); return; } free (hostname); // Create listener beacon on port 9999 to lookup service zactor_t *listener = zactor_new (zbeacon, NULL); assert (listener); if (verbose) zstr_sendx (listener, "VERBOSE", NULL); zsock_send (listener, "si", "CONFIGURE", 9999); hostname = zstr_recv (listener); assert (*hostname); free (hostname); // We will broadcast the magic value 0xCAFE byte announcement [2] = { 0xCA, 0xFE }; zsock_send (speaker, "sbi", "PUBLISH", announcement, 2, 100); // We will listen to anything (empty subscription) zsock_send (listener, "sb", "SUBSCRIBE", "", 0); // Wait for at most 1/2 second if there's no broadcasting zsock_set_rcvtimeo (listener, 500); char *ipaddress = zstr_recv (listener); if (ipaddress) { zframe_t *content = zframe_recv (listener); assert (zframe_size (content) == 2); assert (zframe_data (content) [0] == 0xCA); assert (zframe_data (content) [1] == 0xFE); zframe_destroy (&content); zstr_free (&ipaddress); zstr_sendx (speaker, "SILENCE", NULL); } zactor_destroy (&listener); zactor_destroy (&speaker); // Test subscription filter using a 3-node setup zactor_t *node1 = zactor_new (zbeacon, NULL); assert (node1); zsock_send (node1, "si", "CONFIGURE", 5670); hostname = zstr_recv (node1); assert (*hostname); free (hostname); zactor_t *node2 = zactor_new (zbeacon, NULL); assert (node2); zsock_send (node2, "si", "CONFIGURE", 5670); hostname = zstr_recv (node2); assert (*hostname); free (hostname); zactor_t *node3 = zactor_new (zbeacon, NULL); assert (node3); zsock_send (node3, "si", "CONFIGURE", 5670); hostname = zstr_recv (node3); assert (*hostname); free (hostname); zsock_send (node1, "sbi", "PUBLISH", "NODE/1", 6, 250); zsock_send (node2, "sbi", "PUBLISH", "NODE/2", 6, 250); zsock_send (node3, "sbi", "PUBLISH", "RANDOM", 6, 250); zsock_send (node1, "sb", "SUBSCRIBE", "NODE", 4); // Poll on three API sockets at once zpoller_t *poller = zpoller_new (node1, node2, node3, NULL); assert (poller); int64_t stop_at = zclock_mono () + 1000; while (zclock_mono () < stop_at) { long timeout = (long) (stop_at - zclock_mono ()); if (timeout < 0) timeout = 0; void *which = zpoller_wait (poller, timeout * ZMQ_POLL_MSEC); if (which) { assert (which == node1); char *ipaddress, *received; zstr_recvx (node1, &ipaddress, &received, NULL); assert (streq (received, "NODE/2")); zstr_free (&ipaddress); zstr_free (&received); } } zpoller_destroy (&poller); // Stop listening zstr_sendx (node1, "UNSUBSCRIBE", NULL); // Stop all node broadcasts zstr_sendx (node1, "SILENCE", NULL); zstr_sendx (node2, "SILENCE", NULL); zstr_sendx (node3, "SILENCE", NULL); // Destroy the test nodes zactor_destroy (&node1); zactor_destroy (&node2); zactor_destroy (&node3); // @end printf ("OK\n"); }
int zloop_start (zloop_t *self) { assert (self); int rc = 0; // Main reactor loop while (!zsys_interrupted) { if (self->need_rebuild) { // If s_rebuild_pollset() fails, break out of the loop and // return its error rc = s_rebuild_pollset (self); if (rc) break; } rc = zmq_poll (self->pollset, (int) self->poll_size, s_tickless (self)); if (rc == -1 || zsys_interrupted) { if (self->verbose) zsys_debug ("zloop: interrupted"); rc = 0; break; // Context has been shut down } // Handle any timers that have now expired int64_t time_now = zclock_mono (); s_timer_t *timer = (s_timer_t *) zlistx_first (self->timers); while (timer) { if (time_now >= timer->when) { if (self->verbose) zsys_debug ("zloop: call timer handler id=%d", timer->timer_id); rc = timer->handler (self, timer->timer_id, timer->arg); if (rc == -1) break; // Timer handler signaled break if (timer->times && --timer->times == 0) zlistx_delete (self->timers, timer->list_handle); else timer->when += timer->delay; } timer = (s_timer_t *) zlistx_next (self->timers); } // Handle any tickets that have now expired s_ticket_t *ticket = (s_ticket_t *) zlistx_first (self->tickets); while (ticket && time_now >= ticket->when) { if (self->verbose) zsys_debug ("zloop: call ticket handler"); rc = ticket->handler (self, 0, ticket->arg); if (rc == -1) break; // Timer handler signaled break zlistx_delete (self->tickets, ticket->list_handle); ticket = (s_ticket_t *) zlistx_next (self->tickets); } // Handle any readers and pollers that are ready size_t item_nbr; for (item_nbr = 0; item_nbr < self->poll_size && rc >= 0; item_nbr++) { s_reader_t *reader = &self->readact [item_nbr]; if (reader->handler) { if ((self->pollset [item_nbr].revents & ZMQ_POLLERR) && !reader->tolerant) { if (self->verbose) zsys_warning ("zloop: can't read %s socket: %s", zsock_type_str (reader->sock), zmq_strerror (zmq_errno ())); // Give handler one chance to handle error, then kill // reader because it'll disrupt the reactor otherwise. if (reader->errors++) { zloop_reader_end (self, reader->sock); self->pollset [item_nbr].revents = 0; } } else reader->errors = 0; // A non-error happened if (self->pollset [item_nbr].revents) { if (self->verbose) zsys_debug ("zloop: call %s socket handler", zsock_type_str (reader->sock)); rc = reader->handler (self, reader->sock, reader->arg); if (rc == -1 || self->need_rebuild) break; } } else { s_poller_t *poller = &self->pollact [item_nbr]; assert (self->pollset [item_nbr].socket == poller->item.socket); if ((self->pollset [item_nbr].revents & ZMQ_POLLERR) && !poller->tolerant) { if (self->verbose) zsys_warning ("zloop: can't poll %s socket (%p, %d): %s", poller->item.socket ? zsys_sockname (zsock_type (poller->item.socket)) : "FD", poller->item.socket, poller->item.fd, zmq_strerror (zmq_errno ())); // Give handler one chance to handle error, then kill // poller because it'll disrupt the reactor otherwise. if (poller->errors++) { zloop_poller_end (self, &poller->item); self->pollset [item_nbr].revents = 0; } } else poller->errors = 0; // A non-error happened if (self->pollset [item_nbr].revents) { if (self->verbose) zsys_debug ("zloop: call %s socket handler (%p, %d)", poller->item.socket ? zsys_sockname (zsock_type (poller->item.socket)) : "FD", poller->item.socket, poller->item.fd); rc = poller->handler (self, &self->pollset [item_nbr], poller->arg); if (rc == -1 || self->need_rebuild) break; } } } // Now handle any timer zombies // This is going to be slow if we have many timers; we might use // a faster lookup on the timer list. while (zlistx_first (self->zombies)) { // Get timer_id back from pointer int timer_id = (byte *) zlistx_detach (self->zombies, NULL) - (byte *) NULL; s_timer_remove (self, timer_id); } if (rc == -1) break; } self->terminated = true; return rc; }
void zyre_node_actor (zsock_t *pipe, void *args) { // Create node instance to pass around zyre_node_t *self = zyre_node_new (pipe, args); if (!self) // Interrupted return; // Signal actor successfully initialized zsock_signal (self->pipe, 0); // Loop until the agent is terminated one way or another int64_t reap_at = zclock_mono () + REAP_INTERVAL; while (!self->terminated) { // Start beacon as soon as we can if (self->beacon && self->port <= 0) { // Our hostname is provided by zbeacon zsock_send(self->beacon, "si", "CONFIGURE", self->beacon_port); char *hostname = zstr_recv(self->beacon); // Is UDP broadcast interface available? if (!streq(hostname, "")) { if (zsys_ipv6()) self->port = zsock_bind(self->inbox, "tcp://%s%%%s:*", zsys_ipv6_address(), zsys_interface()); else self->port = zsock_bind(self->inbox, "tcp://%s:*", hostname); if (self->port > 0) { assert(!self->endpoint); // If caller set this, we'd be using gossip if (streq(zsys_interface(), "*")) { char *hostname = zsys_hostname(); self->endpoint = zsys_sprintf("tcp://%s:%d", hostname, self->port); zstr_free(&hostname); } else { self->endpoint = strdup(zsock_endpoint(self->inbox)); } // Set broadcast/listen beacon beacon_t beacon; beacon.protocol[0] = 'Z'; beacon.protocol[1] = 'R'; beacon.protocol[2] = 'E'; beacon.version = BEACON_VERSION; beacon.port = htons(self->port); zuuid_export(self->uuid, beacon.uuid); zsock_send(self->beacon, "sbi", "PUBLISH", (byte *)&beacon, sizeof(beacon_t), self->interval); zsock_send(self->beacon, "sb", "SUBSCRIBE", (byte *) "ZRE", 3); zpoller_add(self->poller, self->beacon); // Start polling on inbox zpoller_add(self->poller, self->inbox); } } zstr_free(&hostname); } int timeout = (int) (reap_at - zclock_mono ()); if (timeout > REAP_INTERVAL) timeout = REAP_INTERVAL; else if (timeout < 0) timeout = 0; zsock_t *which = (zsock_t *) zpoller_wait (self->poller, timeout); if (which == self->pipe) zyre_node_recv_api (self); else if (which == self->inbox) zyre_node_recv_peer (self); else if (self->beacon && (void *) which == self->beacon) zyre_node_recv_beacon (self); else if (self->gossip && (zactor_t *) which == self->gossip) zyre_node_recv_gossip (self); else if (zpoller_terminated (self->poller)) break; // Interrupted, check before expired else if (zpoller_expired (self->poller)) { if (zclock_mono () >= reap_at) { void *item; reap_at = zclock_mono () + REAP_INTERVAL; // Ping all peers and reap any expired ones for (item = zhash_first (self->peers); item != NULL; item = zhash_next (self->peers)) zyre_node_ping_peer (zhash_cursor (self->peers), item, self); } } } zyre_node_destroy (&self); }
int main (int argn, char *argv []) { // Raise theoretical limit on how many ZeroMQ sockets we can create, // though real limit will be set by the process file handle limit. zsys_set_max_sockets (65535); // Test case 1: two servers, bunch of clients. printf ("Starting small test case: "); fflush (stdout); zactor_t *server1 = zactor_new (zgossip, "server1"); assert (server1); zstr_sendx (server1, "SET", "server/animate", "0", NULL); zstr_sendx (server1, "BIND", "inproc://server1", NULL); zactor_t *server2 = zactor_new (zgossip, "server2"); assert (server2); zstr_sendx (server2, "SET", "server/animate", "0", NULL); zstr_sendx (server2, "BIND", "inproc://server2", NULL); zstr_sendx (server2, "CONNECT", "inproc://server1", NULL); zactor_t *client1 = zactor_new (zgossip, "client1"); assert (client1); zstr_sendx (client1, "BIND", "inproc://client1", NULL); zstr_sendx (client1, "PUBLISH", "client1-00", "0000", NULL); zstr_sendx (client1, "PUBLISH", "client1-11", "1111", NULL); zstr_sendx (client1, "PUBLISH", "client1-22", "2222", NULL); zstr_sendx (client1, "CONNECT", "inproc://server1", NULL); zactor_t *client2 = zactor_new (zgossip, "client2"); assert (client2); zstr_sendx (client2, "BIND", "inproc://client2", NULL); zstr_sendx (client2, "CONNECT", "inproc://server1", NULL); zstr_sendx (client2, "PUBLISH", "client2-00", "0000", NULL); zstr_sendx (client2, "PUBLISH", "client2-11", "1111", NULL); zstr_sendx (client2, "PUBLISH", "client2-22", "2222", NULL); zactor_t *client3 = zactor_new (zgossip, "client3"); assert (client3); zstr_sendx (client3, "CONNECT", "inproc://server2", NULL); zactor_t *client4 = zactor_new (zgossip, "client4"); assert (client4); zstr_sendx (client4, "CONNECT", "inproc://server2", NULL); zclock_sleep (100); assert_status (server1, 6); assert_status (server2, 6); assert_status (client1, 6); assert_status (client2, 6); assert_status (client3, 6); assert_status (client4, 6); zactor_destroy (&server1); zactor_destroy (&server2); zactor_destroy (&client1); zactor_destroy (&client2); zactor_destroy (&client3); zactor_destroy (&client4); printf ("OK\n"); // Test case 2: swarm of peers printf ("Starting swarm test case: "); fflush (stdout); // Default limit on file handles is 1024 (POSIX), and fixed setup // costs 8 handles (3 standard I/O plus 5 for CZMQ/libzmq). So the // most nodes we can test by default is (1024 - 8) / 4 = 254. To // test more, run "ulimit -n xxx" beforehand and pass swarm size // as argument to this program. With e.g. Ubuntu, ceiling is 4K // file handles per process, so the largest swarm I've tested is // 1022 nodes. int swarm_size = 254; if (argn >= 2) swarm_size = atoi (argv [1]); printf ("swarm_size=%d ", swarm_size); // The set size defines the total number of properties we spread // across the swarm. By default this is the swarm_size * 5. You can // specify a different set size as second command line argument. int set_size = swarm_size * 5; if (argn >= 3) set_size = atoi (argv [2]); printf ("set_size=%d ", set_size); // Swarm is an array of actors zactor_t *nodes [swarm_size]; // We'll poll all actors for activity (actors act like sockets) zpoller_t *poller = zpoller_new (NULL); assert (poller); // Create swarm uint node_nbr; for (node_nbr = 0; node_nbr < swarm_size; node_nbr++) { nodes [node_nbr] = zactor_new (zgossip, NULL); assert (nodes [node_nbr]); zpoller_add (poller, nodes [node_nbr]); } printf ("."); fflush (stdout); // Interconnect swarm; ever node connects to one arbitrary node to // create a directed graph, then oldest node connects to youngest // node to create a loop, to test we're robust against cycles. for (node_nbr = 0; node_nbr < swarm_size; node_nbr++) { zstr_sendm (nodes [node_nbr], "BIND"); zstr_sendf (nodes [node_nbr], "inproc://swarm-%d", node_nbr); if (node_nbr > 0) { zstr_sendm (nodes [node_nbr], "CONNECT"); zstr_sendf (nodes [node_nbr], "inproc://swarm-%d", randof (node_nbr)); } } zstr_sendm (nodes [0], "CONNECT"); zstr_sendf (nodes [0], "inproc://swarm-%d", node_nbr - 1); printf ("."); fflush (stdout); // Publish the data set randomly across the swarm int item_nbr; for (item_nbr = 0; item_nbr < set_size; item_nbr++) { node_nbr = randof (swarm_size); assert (node_nbr != swarm_size); assert (node_nbr < swarm_size); zstr_sendm (nodes [node_nbr], "PUBLISH"); zstr_sendfm (nodes [node_nbr], "key-%d", item_nbr); zstr_send (nodes [node_nbr], "value"); } printf (". "); fflush (stdout); // Each actor will deliver us tuples; count these until we're done int total = set_size * swarm_size; int pending = total; int64_t ticker = zclock_mono () + 2000; while (pending) { zsock_t *which = (zsock_t *) zpoller_wait (poller, 100); if (!which) { puts (" - stuck test, aborting"); break; } char *command; zstr_recvx (which, &command, NULL); assert (streq (command, "DELIVER")); pending--; freen (command); if (zclock_mono () > ticker) { printf ("(%d%%)", (int) ((100 * (total - pending)) / total)); fflush (stdout); ticker = zclock_mono () + 2000; } } // Destroy swarm for (node_nbr = 0; node_nbr < swarm_size; node_nbr++) zactor_destroy (&nodes [node_nbr]); printf ("(100%%) OK\n"); #if defined (__WINDOWS__) zsys_shutdown(); #endif return 0; }