/** * Enter main loop * * The <var>$time</var> parameter can have different meanings: * <ul> * <li>int or float > 0 : the main loop will run once and will wait for activity for a maximum of <var>$time</var> seconds</li> * <li>0 : the main loop will run once and will not wait for activity when polling, only handling waiting packets and timers</li> * <li>int or float < 0 : the main loop will run for -<var>$time</var> seconds exactly, whatever may happen</li> * <li>NULL : the main loop will run forever</li> * </ul> * * @param float $time how much time should we run, if omited nanoserv will enter an endless loop * @param array $user_streams if specified, user streams will be polled along with internal streams * @return array the user streams with pending data * @since 0.9 */ public static function Run($time = NULL, array $user_streams = NULL) { $tmp = 0; $ret = array(); if (isset($time)) { if ($time < 0) { $poll_max_wait = -$time; $exit_mt = microtime(true) - $time; } else { $poll_max_wait = $time; $exit = true; } } else { $poll_max_wait = 60; $exit = false; } do { $t = microtime(true); // Timers if (self::$timers_updated) { usort(self::$timers, function (Timer $a, Timer $b) { return $a->microtime > $b->microtime; }); self::$timers_updated = false; } unset($next_timer_md); foreach (self::$timers as $k => $tmr) { if ($tmr->microtime > $t) { $next_timer_md = $tmr->microtime - $t; break; } else { if ($tmr->active) { $tmr->Deactivate(); call_user_func($tmr->callback); } } unset(self::$timers[$k]); } if (self::$timers_updated) { $t = microtime(true); usort(self::$timers, function (Timer $a, Timer $b) { return $a->microtime > $b->microtime; }); foreach (self::$timers as $k => $tmr) { if ($tmr->microtime > $t) { $next_timer_md = $tmr->microtime - $t; break; } } self::$timers_updated = false; } // Write buffers to non blocked sockets foreach (self::$write_buffers as $write_buffers) { if (!$write_buffers || $write_buffers[0]->socket->blocked || !$write_buffers[0]->socket->connected) { continue; } foreach ($write_buffers as $wb) { while ($wb->Waiting_Data() && !$wb->socket->blocked) { $wb->Write(); if (!$wb->Waiting_Data()) { array_shift(self::$write_buffers[$wb->socket->id]); if (!self::$write_buffers[$wb->socket->id]) { self::Free_Write_Buffers($wb->socket->id); } break; } } } } unset($handler, $write_buffers, $l, $c, $wbs, $wb, $data); // Prepare socket arrays $fd_lookup_r = $fd_lookup_w = $rfd = $wfd = $efd = array(); foreach (self::$listeners as $l) { if ($l->active && (!$l->forking || self::$nb_forked_processes <= self::$max_forked_processes)) { $rfd[] = $l->socket->fd; $fd_lookup_r[(int) $l->socket->fd] = $l; } } foreach (self::$connections as $c) { if ($c->socket->pending_crypto) { $cr = $c->socket->Enable_Crypto(); if ($cr === true) { $c->on_Accept(); } else { if ($cr === false) { $c->on_Connect_Fail(Connection_Handler::FAIL_CRYPTO); self::Free_Connection($c); } else { $rfd[] = $c->socket->fd; $fd_lookup_r[(int) $c->socket->fd] = $c; } } } else { if ($c->socket->connected) { if (!$c->socket->block_reads) { $rfd[] = $c->socket->fd; $fd_lookup_r[(int) $c->socket->fd] = $c; } } else { if ($c->socket->connect_timeout < $t) { $c->on_Connect_Fail(Connection_Handler::FAIL_TIMEOUT); self::Free_Connection($c); } else { if ($c->socket->pending_connect) { $wfd[] = $c->socket->fd; $fd_lookup_w[(int) $c->socket->fd] = $c; } } } } } foreach (self::$dgram_handlers as $l) { if ($l->active) { $rfd[] = $l->socket->fd; $fd_lookup_r[(int) $l->socket->fd] = $l; } } foreach (self::$write_buffers as $wbs) { if ($wbs[0]->socket->blocked) { $wfd[] = $wbs[0]->socket->fd; $fd_lookup_w[(int) $wbs[0]->socket->fd] = self::$connections[$wbs[0]->socket->id]; } } foreach (self::$forked_pipes as $fp) { $rfd[] = $fp->fd; $fd_lookup_r[(int) $fp->fd] = $fp; } if (isset($user_streams)) { foreach ((array) $user_streams[0] as $tmp_r) { $rfd[] = $tmp_r; } foreach ((array) $user_streams[1] as $tmp_w) { $wfd[] = $tmp_w; } } // Main select $wait_mds = array($poll_max_wait); if (isset($next_timer_md)) { $wait_mds[] = $next_timer_md; } if (isset($exit_mt)) { $wait_mds[] = $exit_mt - $t; } $wait_md = min($wait_mds); $tv_sec = (int) $wait_md; $tv_usec = ($wait_md - $tv_sec) * 1000000; if (($rfd || $wfd) && @stream_select($rfd, $wfd, $efd, $tv_sec, $tv_usec)) { foreach ($rfd as $act_rfd) { $handler = $fd_lookup_r[(int) $act_rfd]; if (!isset($handler)) { // User stream $ret[0][] = $act_rfd; } else { if ($handler instanceof Connection_Handler) { if ($handler->socket->pending_crypto) { $cr = $handler->socket->Enable_Crypto(); if ($cr === true) { $handler->on_Accept(); } else { if ($cr === false) { $handler->on_Connect_Fail(Connection_Handler::FAIL_CRYPTO); self::Free_Connection($handler); } } } else { if (!$handler->socket->connected) { continue; } } $data = $handler->socket->Read(); if ($data === "" || $data === false) { if ($handler->socket->Eof()) { // Disconnected socket $handler->socket->connected = false; $handler->on_Disconnect(); self::Free_Connection($handler); } } else { // Data available $handler->on_Read($data); } } else { if ($handler instanceof Datagram_Handler) { $from = ""; $data = $handler->socket->Read_From($from); $handler->on_Read($from, $data); } else { if ($handler instanceof Listener) { while ($fd = $handler->socket->Accept()) { // New connection accepted $sck = new Socket($fd, $handler->socket->crypto_type); $hnd = new $handler->handler_classname($handler->handler_options); $hnd->socket = $sck; if ($handler->forking) { $hnd->on_Fork_Prepare(); if (self::Fork() === 0) { $hnd->on_Fork_Done(); self::$write_buffers = self::$listeners = array(); self::$connections = array($sck->id => $hnd); self::$forked_connection = $hnd; self::Clear_Timers(); if ($sck->Setup()) { $hnd->on_Accept(); } $handler = $hnd = $sck = $l = $c = $wbs = $wb = $fd_lookup_r = $fd_lookup_w = $loops = false; break; } $hnd->on_Fork_Done(); if (self::$nb_forked_processes >= self::$max_forked_processes) { break; } } else { self::$connections[$sck->id] = $hnd; if ($sck->Setup()) { $hnd->on_Accept(); } } unset($sck, $hnd); } } else { if ($handler instanceof IPC_Socket) { while ($ipcm = $handler->Read()) { if (!($ipcq = unserialize($ipcm)) || !is_object($o = self::$shared_objects[$ipcq["oid"]])) { continue; } switch ($ipcq["action"]) { case "G": $handler->Write(serialize($o->{$ipcq}["var"])); break; case "S": $o->{$ipcq}["var"] = $ipcq["val"]; break; case "C": Shared_Object::$caller_pid = $handler->pid; $handler->Write(serialize(call_user_func_array(array($o, $ipcq["func"]), $ipcq["args"]))); break; } } unset($o, $ipcq, $ipcm); } } } } } } foreach ($wfd as $act_wfd) { $handler = $fd_lookup_w[$act_wfd]; if (!isset($handler)) { // User stream $ret[1][] = $act_wfd; } else { if ($handler->socket->connected) { // Unblock buffered write if ($handler->socket->Eof()) { $handler->on_Disconnect(); self::Free_Connection($handler); } else { $handler->socket->blocked = false; } } else { if ($handler->socket->pending_connect) { // Pending connect if ($handler->socket->Eof()) { $handler->on_Connect_Fail(Connection_Handler::FAIL_CONNREFUSED); self::Free_Connection($handler); } else { $handler->socket->Setup(); $handler->socket->connected = true; $handler->socket->pending_connect = false; $handler->on_Connect(); } } } } } } if (self::$nb_forked_processes && !self::$child_process) { while (($pid = pcntl_wait($tmp, WNOHANG)) > 0 && self::$nb_forked_processes--) { unset(self::$forked_pipes[$pid]); } } if ($ret) { return $ret; } else { if (isset($exit_mt)) { $exit = $exit_mt <= $t; } } } while (!$exit); }