Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

Commit

Permalink
Fixed CommandReader hanging on shutdown, close pmmp/PocketMine-MP#25 (p…
Browse files Browse the repository at this point in the history
…mmp/PocketMine-MP#171)

Use stream_select to poll stdin status before reading
Add detection for FIFO pipes, rewrite half of the CommandReader (again)
Add timeout for CommandReader to prevent hang in Windows custom consoles (unknown reason)
  • Loading branch information
dktapps committed Jan 9, 2017
1 parent d45860d commit 6ce9807
Show file tree
Hide file tree
Showing 24 changed files with 142 additions and 971 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "src/spl"]
path = src/spl
url = https://github.com/pmmp/PocketMine-SPL.git
29 changes: 22 additions & 7 deletions src/pocketmine/PocketMine.php
Original file line number Diff line number Diff line change
Expand Up @@ -477,19 +477,34 @@ function cleanPath($path){

$logger->info("Stopping other threads");

foreach(ThreadManager::getInstance()->getAll() as $id => $thread){
$logger->debug("Stopping " . (new \ReflectionClass($thread))->getShortName() . " thread");
$thread->quit();
}

$killer = new ServerKiller(8);
$killer->start();

$erroredThreads = 0;

foreach(ThreadManager::getInstance()->getAll() as $id => $thread){
$logger->debug("Stopping " . $thread->getThreadName() . " thread");
try{
$thread->quit();
$logger->debug($thread->getThreadName() . " thread stopped successfully.");
}catch(\ThreadException $e){
++$erroredThreads;
$logger->debug("Could not stop " . $thread->getThreadName() . " thread: " . $e->getMessage());
}
}

$logger->shutdown();
$logger->join();

echo "Server has stopped" . Terminal::$FORMAT_RESET . "\n";
echo Terminal::$FORMAT_RESET . PHP_EOL;

exit(0);
if($erroredThreads > 0){
if(\pocketmine\DEBUG > 1){
echo "Some threads could not be stopped, performing a force-kill" . PHP_EOL . PHP_EOL;
}
kill(getmypid());
}else{
exit(0);
}

}
167 changes: 116 additions & 51 deletions src/pocketmine/command/CommandReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,49 +19,141 @@
*
*/

declare(strict_types = 1);

namespace pocketmine\command;

use pocketmine\Thread;
use pocketmine\utils\MainLogger;
use pocketmine\utils\Utils;

class CommandReader extends Thread{
private $readline;

const TYPE_READLINE = 0;
const TYPE_STREAM = 1;
const TYPE_PIPED = 2;

/** @var \Threaded */
protected $buffer;
private $shutdown = false;
/** @var MainLogger */
private $logger;

public function __construct($logger){
$opts = getopt("", ["disable-readline"]);
$this->readline = (extension_loaded("readline") and !isset($opts["disable-readline"]));
$this->logger = $logger;
private $type = self::TYPE_STREAM;
private $lastLine = -1;

public function __construct(){
$this->buffer = new \Threaded;

$opts = getopt("", ["disable-readline"]);
if((extension_loaded("readline") and !isset($opts["disable-readline"]) and !$this->isPipe(STDIN))){
$this->type = self::TYPE_READLINE;
}

$this->start();
}

public function shutdown(){
$this->shutdown = true;
}

private function readline_callback($line){
if($line !== ""){
$this->buffer[] = $line;
readline_add_history($line);
public function quit(){
$wait = microtime(true) + 0.5;
while(microtime(true) < $wait){
if($this->isRunning()){
usleep(100000);
}else{
parent::quit();
return;
}
}

$message = "Thread blocked for unknown reason";
if($this->type === self::TYPE_PIPED){
$message = "STDIN is being piped from another location and the pipe is blocked, cannot stop safely";
}

throw new \ThreadException($message);
}

private function readLine(){
if(!$this->readline){
global $stdin;
$line = trim(fgets($stdin));
private function initStdin(){
global $stdin;

if(is_resource($stdin)){
fclose($stdin);
}

$stdin = fopen("php://stdin", "r");
if($this->isPipe($stdin)){
$this->type = self::TYPE_PIPED;
}else{
$this->type = self::TYPE_STREAM;
}
}

/**
* Checks if the specified stream is a FIFO pipe.
*
* @return bool
*/
private function isPipe($stream) : bool{
return is_resource($stream) and ((function_exists("posix_isatty") and !posix_isatty($stream)) or ((fstat($stream)["mode"] & 0170000) === 0010000));
}

/**
* Reads a line from the console and adds it to the buffer. This method may block the thread.
*
* @return bool if the main execution should continue reading lines
*/
private function readLine() : bool{
$line = "";
if($this->type === self::TYPE_READLINE){
$line = trim(readline("> "));
if($line !== ""){
$this->buffer[] = $line;
readline_add_history($line);
}else{
return true;
}
}else{
readline_callback_read_char();
global $stdin;

if(!is_resource($stdin)){
$this->initStdin();
}

switch($this->type){
case self::TYPE_STREAM:
$r = [$stdin];
if(($count = stream_select($r, $w, $e, 0, 200000)) === 0){ //nothing changed in 200000 microseconds
return true;
}elseif($count === false){ //stream error
$this->initStdin();
}

if(($raw = fgets($stdin)) !== false){
$line = trim($raw);
}else{
return false; //user pressed ctrl+c?
}

break;
case self::TYPE_PIPED:
if(($raw = fgets($stdin)) === false){ //broken pipe or EOF
$this->initStdin();
$this->synchronized(function(){
$this->wait(200000);
}); //prevent CPU waste if it's end of pipe
return true; //loop back round
}else{
$line = trim($raw);
}
break;
}
}

if($line !== ""){
$this->buffer[] = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", $line);
}

return true;
}

/**
Expand All @@ -77,45 +169,18 @@ public function getLine(){
return null;
}

public function quit(){
// Windows sucks
if(Utils::getOS() !== "win"){
parent::quit();
}
}

public function run(){
global $stdin;
$stdin = fopen("php://stdin", "r");
if($this->readline){
readline_callback_handler_install("Genisys> ", [$this, "readline_callback"]);
$this->logger->setConsoleCallback("readline_redisplay");
if($this->type !== self::TYPE_READLINE){
$this->initStdin();
}

while(!$this->shutdown){
$r = [$stdin];
$w = null;
$e = null;
if(stream_select($r, $w, $e, 0, 200000) > 0){
// PHP on Windows sucks
if(feof($stdin)){
if(Utils::getOS() == "win"){
$stdin = fopen("php://stdin", "r");
if(!is_resource($stdin)){
break;
}
}else{
break;
}
}
$this->readLine();
}
}
while(!$this->shutdown and $this->readLine());

if($this->readline){
$this->logger->setConsoleCallback(null);
readline_callback_handler_remove();
if($this->type !== self::TYPE_READLINE){
global $stdin;
fclose($stdin);
}

}

public function getThreadName(){
Expand Down
1 change: 1 addition & 0 deletions src/spl
Submodule spl added at 23ac7b
20 changes: 0 additions & 20 deletions src/spl/ArrayOutOfBoundsException.php

This file was deleted.

36 changes: 0 additions & 36 deletions src/spl/AttachableLogger.php

This file was deleted.

67 changes: 0 additions & 67 deletions src/spl/AttachableThreadedLogger.php

This file was deleted.

Loading

0 comments on commit 6ce9807

Please sign in to comment.