protected function onPoll($events, $read, $write) { parent::onPoll($events, $read, $write); if ($events > 0) { foreach ($read as $socket) { //handle publisher if ($socket === $this->frontedSocket) { $zmsg = new Zmsg($this->frontedSocket); $zmsg->recv(); if ($this->verbose) { echo "I: received message from publisher size: "; echo strlen($zmsg->__toString()), PHP_EOL; } $zmsg->unwrap(); //time if ($this->queueLimit > sizeof($this->queue)) { array_unshift($this->queue, $zmsg->pop()); } } } } }
/** * This is the main listen and process loop */ public function listen() { $read = $write = array(); // Get and process messages forever or until interrupted while (true) { $poll = new ZMQPoll(); $poll->add($this->socket, ZMQ::POLL_IN); $events = $poll->poll($read, $write, HEARTBEAT_INTERVAL * ZMQ_POLL_MSEC); // Process next input message, if any if ($events) { $zmsg = new Zmsg($this->socket); $zmsg->recv(); if ($this->verbose) { echo "I: received message:", PHP_EOL, $zmsg->__toString(); } $sender = $zmsg->pop(); $empty = $zmsg->pop(); $header = $zmsg->pop(); if ($header == MDPC_CLIENT) { $this->client_process($sender, $zmsg); } else { if ($header = MDPW_WORKER) { $this->worker_process($sender, $zmsg); } else { echo "E: invalid message", PHP_EOL, $zmsg->__toString(); } } } // Disconnect and delete any expired workers // Send heartbeats to idle workers if needed if (microtime(true) > $this->heartbeat_at) { $this->purge_workers(); foreach ($this->workers as $worker) { $this->worker_send($worker, MDPW_HEARTBEAT, NULL, NULL); } $this->heartbeat_at = microtime(true) + HEARTBEAT_INTERVAL / 1000; } } }
/** * Start listen for messages in loop * @throws \Exception */ public function listen() { if (!$this->listener) { throw new \Exception("Empty listener"); } $this->isListen = true; while ($this->isListen) { $zmsg = new Zmsg($this->socket); $zmsg->recv(); if ($this->verbose) { echo "I: received message from broker:", PHP_EOL; echo $zmsg->__toString(), PHP_EOL; } $time = $zmsg->unwrap(); if (!$this->normalDelay) { $this->normalDelay = microtime(true) * 1000 - (double) $time; } $delayTime = microtime(true) * 1000 - (double) $time; if ($this->misser && $delayTime > $this->normalDelay + $this->maxAllowedDelay) { call_user_func($this->misser, $zmsg->pop(), $time, $delayTime); } call_user_func($this->listener, $zmsg->pop(), $time); } }
/** * Send reply, if any, to broker and wait for next request. * * @param Zmsg $reply * @return Zmsg Returns if there is a request to process */ public function recv($reply = null) { // Format and send the reply if we were provided one assert($reply || !$this->expect_reply); if ($reply) { $reply->wrap($this->reply_to); $this->send_to_broker(MDPW_REPLY, NULL, $reply); } $this->expect_reply = true; $read = $write = array(); while (true) { $poll = new ZMQPoll(); $poll->add($this->worker, ZMQ::POLL_IN); $events = $poll->poll($read, $write, $this->heartbeat); if ($events) { $zmsg = new Zmsg($this->worker); $zmsg->recv(); if ($this->verbose) { echo "I: received message from broker:", PHP_EOL; echo $zmsg->__toString(); } $this->liveness = HEARTBEAT_LIVENESS; // Don't try to handle errors, just assert noisily assert($zmsg->parts() >= 3); $zmsg->pop(); $header = $zmsg->pop(); assert($header == MDPW_WORKER); $command = $zmsg->pop(); if ($command == MDPW_REQUEST) { // We should pop and save as many addresses as there are // up to a null part, but for now, just save one... $this->reply_to = $zmsg->unwrap(); return $zmsg; // We have a request to process } else { if ($command == MDPW_HEARTBEAT) { // Do nothing for heartbeats } else { if ($command == MDPW_DISCONNECT) { $this->connect_to_broker(); } else { echo "E: invalid input message", PHP_EOL; echo $zmsg->__toString(); } } } } else { if (--$this->liveness == 0) { // poll ended on timeout, $event being false if ($this->verbose) { echo "W: disconnected from broker - retrying...", PHP_EOL; } usleep($this->reconnect * 1000); $this->connect_to_broker(); } } // Send HEARTBEAT if it's time if (microtime(true) > $this->heartbeat_at) { $this->send_to_broker(MDPW_HEARTBEAT, NULL, NULL); $this->heartbeat_at = microtime(true) + $this->heartbeat / 1000; } } }
if ($cycles > 3 && rand(0, 5) == 0) { printf("I: (%s) simulating a crash%s", $identity, PHP_EOL); break; } elseif ($cycles > 3 && rand(0, 5) == 0) { printf("I: (%s) simulating CPU overload%s", $identity, PHP_EOL); sleep(5); } printf("I: (%s) normal reply - %s%s", $identity, $zmsg->body(), PHP_EOL); $zmsg->send(); $liveness = HEARTBEAT_LIVENESS; sleep(1); // Do some heavy work } elseif ($zmsg->parts() == 1 && $zmsg->body() == 'HEARTBEAT') { $liveness = HEARTBEAT_LIVENESS; } else { printf("E: (%s) invalid message%s%s", $identity, PHP_EOL, $zmsg->__toString()); } $interval = INTERVAL_INIT; } elseif (--$liveness == 0) { printf("W: (%s) heartbeat failure, can't reach queue%s", $identity, PHP_EOL); printf("W: (%s) reconnecting in %d msec...%s", $identity, $interval, PHP_EOL); usleep($interval * 1000); if ($interval < INTERVAL_MAX) { $interval *= 2; } list($worker, $identity) = s_worker_socket($context); $liveness = HEARTBEAT_LIVENESS; } // Send heartbeat to queue if it's time if (microtime(true) > $heartbeat_at) { $heartbeat_at = microtime(true) + HEARTBEAT_INTERVAL;
/** * Process message sent to us by a worker * * @param string $sender * @param Zmsg $msg */ public function worker_process($sender, $msg) { $command = $msg->pop(); $worker_ready = isset($this->workers[$sender]); $worker = $this->worker_require($sender); if ($command == MDPW_READY) { if ($worker_ready) { $this->worker_delete($worker, true); // Not first command in session } else { if (strlen($sender) >= 4 && substr($sender, 0, 4) == 'mmi.') { $this->worker_delete($worker, true); } else { // Attach worker to service and mark as idle $service_frame = $msg->pop(); $worker->service = $this->service_require($service_frame); $worker->service->workers++; $this->worker_waiting($worker); } } } else { if ($command == MDPW_REPLY) { if ($worker_ready) { // Remove & save client return envelope and insert the // protocol header and service name, then rewrap envelope. $client = $msg->unwrap(); $msg->push($worker->service->name); $msg->push(MDPC_CLIENT); $msg->wrap($client, ""); $msg->set_socket($this->socket)->send(); $this->worker_waiting($worker); } else { $this->worker_delete($worker, true); } } else { if ($command == MDPW_HEARTBEAT) { if ($worker_ready) { $worker->expiry = microtime(true) + HEARTBEAT_EXPIRY / 1000; } else { $this->worker_delete($worker, true); } } else { if ($command == MDPW_DISCONNECT) { $this->worker_delete($worker, true); } else { echo "E: invalid input message", PHP_EOL, $msg->__toString(); } } } } }
/** * Returns the reply message or NULL if there was no reply. Does not * attempt to recover from a broker failure, this is not possible * without storing all unanswered requests and resending them all... * */ public function recv() { $read = $write = array(); // Poll socket for a reply, with timeout $poll = new ZMQPoll(); $poll->add($this->client, ZMQ::POLL_IN); $events = $poll->poll($read, $write, $this->timeout); // If we got a reply, process it if ($events) { $msg = new Zmsg($this->client); $msg->recv(); if ($this->verbose) { echo "I: received reply:", $msg->__toString(), PHP_EOL; } // Don't try to handle errors, just assert noisily assert($msg->parts() >= 4); $msg->pop(); // empty $header = $msg->pop(); assert($header == MDPC_CLIENT); $reply_service = $msg->pop(); return $msg; // Success } else { echo "W: permanent error, abandoning request", PHP_EOL; return; // Give up } }
protected function process($sender, Zmsg $zmsg) { $command = $zmsg->pop(); $hasWorker = $this->hasWorker($sender); switch ($command) { case Commands::W_READY: if (!$hasWorker) { $this->addWorker($sender); } else { echo "E: Ready from ready worker `{$sender}` - disconnect ", PHP_EOL; $this->deleteWorker($this->workers[$sender], true); } break; case Commands::W_HEARTBEAT: if ($hasWorker) { $this->live($this->workers[$sender]); } else { echo "E: Heartbeat from not ready worker `{$sender}` - disconnect ", PHP_EOL; $this->send($sender, Commands::W_RESPONSE); } break; case Commands::W_RESPONSE: if ($hasWorker) { if ($this->responder) { $response = $zmsg->pop(); call_user_func($this->responder, $response); } $this->free($this->workers[$sender]); } else { echo "E: Response from not ready worker `{$sender}` - disconnect ", PHP_EOL; $this->send($sender, Commands::W_RESPONSE); } break; default: echo "E: Unsupported command `{$command}`.", PHP_EOL; echo $zmsg->__toString(), PHP_EOL, PHP_EOL; } }
/** * Send request to broker and get reply by hook or crook * Takes ownership of request message and destroys it when sent. * Returns the reply message or NULL if there was no reply. * * @param string $service * @param Zmsg $request * @param string $client * @return Zmsg */ public function send($service, Zmsg $request) { // Prefix request with protocol frames // Frame 1: "MDPCxy" (six bytes, MDP/Client // Frame 2: Service name (printable string) $request->push($service); $request->push(MDPC_CLIENT); if ($this->verbose) { printf("I: send request to '%s' service:", $service); echo $request->__toString(); } $retries_left = $this->retries; $read = $write = array(); while ($retries_left) { $request->set_socket($this->client)->send(); // Poll socket for a reply, with timeout $poll = new ZMQPoll(); $poll->add($this->client, ZMQ::POLL_IN); $events = $poll->poll($read, $write, $this->timeout); // If we got a reply, process it if ($events) { $request->recv(); if ($this->verbose) { echo "I: received reply:", $request->__toString(), PHP_EOL; } // Don't try to handle errors, just assert noisily assert($request->parts() >= 3); $header = $request->pop(); assert($header == MDPC_CLIENT); $reply_service = $request->pop(); assert($reply_service == $service); return $request; // Success } elseif ($retries_left--) { if ($this->verbose) { echo "W: no reply, reconnecting...", PHP_EOL; } // Reconnect, and resend message $this->connect_to_broker(); $request->send(); } else { echo "W: permanent error, abandoning request", PHP_EOL; break; // Give up } } }
foreach ($read as $socket) { $zmsg = new Zmsg($socket); $zmsg->recv(); // Handle worker activity on backend if ($socket === $backend) { $identity = $zmsg->unwrap(); // Return reply to client if it's not a control message if ($zmsg->parts() == 1) { if ($zmsg->address() == "READY") { $queue->s_worker_delete($identity); $queue->s_worker_append($identity); } else { if ($zmsg->address() == 'HEARTBEAT') { $queue->s_worker_refresh($identity); } else { printf("E: invalid message from %s%s%s", $identity, PHP_EOL, $zmsg->__toString()); } } } else { $zmsg->set_socket($frontend)->send(); $queue->s_worker_append($identity); } } else { // Now get next client request, route to next worker $identity = $queue->s_worker_dequeue(); $zmsg->wrap($identity); $zmsg->set_socket($backend)->send(); } } if (microtime(true) > $heartbeat_at) { foreach ($queue as $id => $expiry) {
/** * Send request to broker * Takes ownership of request message and destroys it when sent. * * @param string $service * @param Zmsg $request */ public function send($service, Zmsg $request) { // Prefix request with protocol frames // Frame 0: empty (REQ emulation) // Frame 1: "MDPCxy" (six bytes, MDP/Client x.y) // Frame 2: Service name (printable string) $request->push($service); $request->push(MDPC_CLIENT); $request->push(""); if ($this->verbose) { printf("I: send request to '%s' service: %s", $service, PHP_EOL); echo $request->__toString(); } $request->set_socket($this->client)->send(); }
protected function onPoll($events, $read, $write) { $events = $this->poll->poll($read, $write, $this->heartbeatDelay); $sendHeartBeat = true; if ($events) { $zmsg = new Zmsg($this->socket); $zmsg->recv(); if ($this->verbose) { echo "I: received message from broker:", PHP_EOL; echo $zmsg->__toString(), PHP_EOL; } $this->heartbeatTriesLeft = $this->heartbeatMaxFails; $zmsg->pop(); $header = $zmsg->pop(); assert($header == Commands::W_WORKER); $command = $zmsg->pop(); if ($command == Commands::W_HEARTBEAT) { } elseif ($command == Commands::W_REQUEST) { //@todo: get address $result = call_user_func($this->executor, $zmsg->pop()); $this->send($result); //resp = HB $sendHeartBeat = false; } elseif ($command == Commands::W_RESPONSE) { $this->connect(); } else { echo "I: Unsupported command `{$command}`.", PHP_EOL; echo $zmsg->__toString(), PHP_EOL, PHP_EOL; } } elseif (--$this->heartbeatTriesLeft == 0) { if ($this->verbose) { echo "I: disconnected from broker - retrying... ", PHP_EOL; } usleep($this->reconnectDelay * 1000); $this->connect(); } $this->sendHeartbeat($sendHeartBeat); }