Separate CommandReader impl from CommandReaderThread

This commit is contained in:
Dylan K. Taylor 2021-05-29 23:41:11 +01:00
parent 4bcc7e09cb
commit 0402e7e697
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
6 changed files with 110 additions and 133 deletions

View File

@ -28,7 +28,7 @@ declare(strict_types=1);
namespace pocketmine;
use pocketmine\command\Command;
use pocketmine\command\CommandReader;
use pocketmine\command\CommandReaderThread;
use pocketmine\command\CommandSender;
use pocketmine\command\ConsoleCommandSender;
use pocketmine\command\SimpleCommandMap;
@ -227,7 +227,7 @@ class Server{
/** @var MemoryManager */
private $memoryManager;
/** @var CommandReader */
/** @var CommandReaderThread */
private $console;
/** @var SimpleCommandMap */
@ -1133,11 +1133,12 @@ class Server{
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_USERS, $consoleSender);
$consoleNotifier = new SleeperNotifier();
$this->console = new CommandReader($consoleNotifier);
$this->tickSleeper->addNotifier($consoleNotifier, function() use ($consoleSender) : void{
$commandBuffer = new \Threaded();
$this->console = new CommandReaderThread($commandBuffer, $consoleNotifier);
$this->tickSleeper->addNotifier($consoleNotifier, function() use ($commandBuffer, $consoleSender) : void{
Timings::$serverCommand->startTiming();
while(($line = $this->console->getLine()) !== null){
$this->dispatchCommand($consoleSender, $line);
while(($line = $commandBuffer->shift()) !== null){
$this->dispatchCommand($consoleSender, (string) $line);
}
Timings::$serverCommand->stopTiming();
});
@ -1422,7 +1423,7 @@ class Server{
$this->configGroup->save();
}
if($this->console instanceof CommandReader){
if($this->console instanceof CommandReaderThread){
$this->getLogger()->debug("Closing console");
$this->console->shutdown();
$this->console->notify();

View File

@ -23,149 +23,58 @@ declare(strict_types=1);
namespace pocketmine\command;
use pocketmine\snooze\SleeperNotifier;
use pocketmine\thread\Thread;
use pocketmine\thread\ThreadException;
use function fclose;
use function fgets;
use function fopen;
use function fstat;
use function is_resource;
use function microtime;
use function preg_replace;
use function readline;
use function stream_isatty;
use function stream_select;
use function trim;
use function usleep;
class CommandReader extends Thread{
public const TYPE_READLINE = 0;
public const TYPE_STREAM = 1;
public const TYPE_PIPED = 2;
final class CommandReader{
/** @var resource */
private static $stdin;
private $stdin;
/** @var \Threaded */
protected $buffer;
/** @var bool */
private $shutdown = false;
/** @var int */
private $type = self::TYPE_STREAM;
/** @var SleeperNotifier|null */
private $notifier;
public function __construct(?SleeperNotifier $notifier = null){
$this->buffer = new \Threaded;
$this->notifier = $notifier;
}
public function shutdown() : void{
$this->shutdown = true;
}
public function quit() : void{
$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);
public function __construct(){
$this->initStdin();
}
private function initStdin() : void{
if(is_resource(self::$stdin)){
fclose(self::$stdin);
if(is_resource($this->stdin)){
fclose($this->stdin);
}
self::$stdin = fopen("php://stdin", "r");
if($this->isPipe(self::$stdin)){
$this->type = self::TYPE_PIPED;
}else{
$this->type = self::TYPE_STREAM;
}
}
/**
* Checks if the specified stream is a FIFO pipe.
*
* @param resource $stream
*/
private function isPipe($stream) : bool{
return is_resource($stream) and (!stream_isatty($stream) or ((fstat($stream)["mode"] & 0170000) === 0010000));
$this->stdin = fopen("php://stdin", "r");
}
/**
* 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{
if(!is_resource(self::$stdin)){
public function readLine() : ?string{
if(!is_resource($this->stdin)){
$this->initStdin();
}
$r = [self::$stdin];
$r = [$this->stdin];
$w = $e = null;
if(($count = stream_select($r, $w, $e, 0, 200000)) === 0){ //nothing changed in 200000 microseconds
return true;
return null;
}elseif($count === false){ //stream error
$this->initStdin();
}
if(($raw = fgets(self::$stdin)) === false){ //broken pipe or EOF
if(($raw = fgets($this->stdin)) === false){ //broken pipe or EOF
$this->initStdin();
$this->synchronized(function() : void{
$this->wait(200000);
}); //prevent CPU waste if it's end of pipe
return true; //loop back round
usleep(200000); //prevent CPU waste if it's end of pipe
return null; //loop back round
}
$line = trim($raw);
if($line !== ""){
$this->buffer[] = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", $line);
if($this->notifier !== null){
$this->notifier->wakeupSleeper();
}
}
return true;
return $line !== "" ? $line : null;
}
/**
* Reads a line from console, if available. Returns null if not available
*/
public function getLine() : ?string{
if($this->buffer->count() !== 0){
return (string) $this->buffer->shift();
}
return null;
}
protected function onRun() : void{
$this->initStdin();
while(!$this->shutdown and $this->readLine());
fclose(self::$stdin);
}
public function getThreadName() : string{
return "Console";
public function __destruct(){
fclose($this->stdin);
}
}

View File

@ -0,0 +1,82 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\command;
use pocketmine\snooze\SleeperNotifier;
use pocketmine\thread\Thread;
use pocketmine\thread\ThreadException;
use function microtime;
use function preg_replace;
use function usleep;
final class CommandReaderThread extends Thread{
private \Threaded $buffer;
private ?SleeperNotifier $notifier;
public bool $shutdown = false;
public function __construct(\Threaded $buffer, ?SleeperNotifier $notifier = null){
$this->buffer = $buffer;
$this->notifier = $notifier;
}
public function shutdown() : void{
$this->shutdown = true;
}
public function quit() : void{
$wait = microtime(true) + 0.5;
while(microtime(true) < $wait){
if($this->isRunning()){
usleep(100000);
}else{
parent::quit();
return;
}
}
throw new ThreadException("CommandReader is stuck in a blocking STDIN read");
}
protected function onRun() : void{
$buffer = $this->buffer;
$notifier = $this->notifier;
$reader = new CommandReader();
while(!$this->shutdown){
$line = $reader->readLine();
if($line !== null){
$buffer[] = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", $line);
if($notifier !== null){
$notifier->wakeupSleeper();
}
}
}
}
public function getThreadName() : string{
return "Console";
}
}

View File

@ -6,7 +6,7 @@ parameters:
path: ../../../src/CrashDump.php
-
message: "#^Instanceof between pocketmine\\\\command\\\\CommandReader and pocketmine\\\\command\\\\CommandReader will always evaluate to true\\.$#"
message: "#^Instanceof between pocketmine\\\\command\\\\CommandReaderThread and pocketmine\\\\command\\\\CommandReaderThread will always evaluate to true\\.$#"
count: 1
path: ../../../src/Server.php

View File

@ -37,7 +37,7 @@ parameters:
-
message: "#^Cannot cast mixed to string\\.$#"
count: 1
count: 2
path: ../../../src/Server.php
-
@ -80,11 +80,6 @@ parameters:
count: 2
path: ../../../src/VersionInfo.php
-
message: "#^Cannot cast mixed to string\\.$#"
count: 1
path: ../../../src/command/CommandReader.php
-
message: "#^Part \\$host \\(mixed\\) of encapsed string cannot be cast to string\\.$#"
count: 1

View File

@ -436,17 +436,7 @@ parameters:
path: ../../../src/block/tile/TileFactory.php
-
message: "#^Cannot access offset 'mode' on array\\(0 \\=\\> int, 1 \\=\\> int, 2 \\=\\> int, 3 \\=\\> int, 4 \\=\\> int, 5 \\=\\> int, 6 \\=\\> int, 7 \\=\\> int, \\.\\.\\.\\)\\|false\\.$#"
count: 1
path: ../../../src/command/CommandReader.php
-
message: "#^Parameter \\#1 \\$stream of method pocketmine\\\\command\\\\CommandReader\\:\\:isPipe\\(\\) expects resource, resource\\|false given\\.$#"
count: 1
path: ../../../src/command/CommandReader.php
-
message: "#^Static property pocketmine\\\\command\\\\CommandReader\\:\\:\\$stdin \\(resource\\) does not accept resource\\|false\\.$#"
message: "#^Property pocketmine\\\\command\\\\CommandReader\\:\\:\\$stdin \\(resource\\) does not accept resource\\|false\\.$#"
count: 1
path: ../../../src/command/CommandReader.php