Beispiel #1
0
 /**
  * Display a command prompt, block on input from STDIN, then parse and execute the specified commands.
  *
  * Multiple processes share a single command prompt by accessing a semaphore identified by the current application.
  * This method will block the process while it waits for the mutex, and then again while it waits for input on STDIN.
  *
  * The text of the prompt itself will be written when get_text_prompt() is called. Custom prompts for a given $method
  * can be added to the $prompts array.
  *
  * Several commands are built-in, and additional commands can be added with addParser().
  *
  * Parsers can either:
  * 1. Continue from the prompt.
  * 2. Abort from the prompt. Call any interrupt_callable that may be registered for this $method.
  * 3. Take some action or perform some activity and then return to the same prompt for additional commands.
  *
  * @param $method
  * @param $args
  * @return bool|int|mixed|null
  * @throws Exception
  */
 public function prompt($method, $args)
 {
     if (!is_resource($this->shm)) {
         return true;
     }
     // The single debug shell is shared across the parent and all worker processes. Use a mutex to serialize
     // access to the shell. If the mutex isn't owned by this process, this will block until this process acquires it.
     $this->mutex_acquire();
     if (!$this->is_breakpoint_active($method)) {
         $this->mutex_release();
         return true;
     }
     // Pass a simple print-line closure to parsers to use instead of just "echo" or "print"
     $printer = function ($message, $maxlen = null) {
         if (empty($message)) {
             return;
         }
         if ($maxlen && strlen($message) > $maxlen) {
             $message = substr($message, 0, $maxlen - 3) . '...';
         }
         $message = str_replace(PHP_EOL, PHP_EOL . ' ', $message);
         echo " {$message}\n\n";
     };
     try {
         $this->print_banner();
         $pid = getmypid();
         $prompt = $this->get_text_prompt($method, $args);
         $break = false;
         // We have to clear the buffer of any input that occurred in the terminal in the space after they submitted their last
         // command and before this new prompt. Otherwise it'll be read from fgets below and probably ruin everything.
         stream_set_blocking(STDIN, 0);
         while (fgets(STDIN)) {
             continue;
         }
         stream_set_blocking(STDIN, 1);
         // Commands that set $break=true will continue forward from the command prompt.
         // Otherwise it will just do the action (or display an error) and then repeat the prompt
         while (!$break) {
             echo $prompt;
             $input = trim(fgets(STDIN));
             $input = preg_replace('/\\s+/', ' ', $input);
             $matches = false;
             $message = '';
             // Use the familiar bash !! to re-run the last command
             if (substr($input, -2) == '!!') {
                 $input = $this->debug_state('last');
             } elseif (!empty($input)) {
                 $this->debug_state('last', $input);
             }
             // Validate the input as an expression
             $matches = array();
             foreach ($this->parsers as $parser) {
                 if (preg_match($parser['regex'], $input, $matches) == 1) {
                     $break = $parser['closure']($matches, $printer);
                     break;
                 }
             }
             if ($matches) {
                 continue;
             }
             // If one of the parsers didn't catch the message
             // fall through to the built-in commands
             switch (strtolower($input)) {
                 case 'help':
                     $out = array();
                     $out[] = 'For the PHP Simple Daemon debugging guide, see: ';
                     $out[] = 'https://github.com/shaneharter/PHP-Daemon/wiki/Debugging-Workers';
                     $out[] = '';
                     $out[] = 'Available Commands:';
                     $out[] = 'y                 Step to the next break point';
                     $out[] = 'n                 Interrupt';
                     $out[] = '';
                     $out[] = 'capture           Call the current method and capture its return value. Will print_r the return value and return a prompt.';
                     $out[] = 'end               End the debugging session, continue the daemon as normal.';
                     $out[] = 'help              Print This Help';
                     $out[] = 'kill              Kill the daemon and all of its worker processes.';
                     $out[] = 'skip              Skip this breakpoint from now on.';
                     $out[] = 'shutdown          End Debugging and Gracefully shutdown the daemon after the current loop_interval.';
                     $out[] = 'trace             Print A Stack Trace';
                     if (is_callable($this->indent_callback)) {
                         $out[] = 'indent [y|n]      When turned-on, indentation will be used to group messages from the same call in a column so you can easily match them together.';
                     }
                     $out[] = '';
                     foreach ($this->parsers as $parser) {
                         $out[] = sprintf('%s%s', str_pad($parser['command'], 18, ' ', STR_PAD_RIGHT), $parser['description']);
                     }
                     $out[] = '';
                     $out[] = '!!                Repeat previous command';
                     $printer(implode(PHP_EOL, $out));
                     break;
                 case 'indent y':
                     $this->debug_state('indent', true);
                     $printer('Indent enabled');
                     break;
                 case 'indent n':
                     $this->debug_state('indent', false);
                     $printer('Indent disabled');
                     break;
                 case 'show args':
                     $printer(print_r($args, true));
                     break;
                 case 'shutdown':
                     //$this->daemon->shutdown();
                     $printer("Shutdown In Progress... Use `end` command to cease debugging until shutdown is complete.");
                     $break = true;
                     break;
                 case 'trace':
                     $e = new exception();
                     $printer($e->getTraceAsString());
                     break;
                 case 'end':
                     $this->debug_state('enabled', false);
                     $break = true;
                     $printer('Debugging Ended..');
                     $input = true;
                     break;
                 case 'skip':
                     $this->debug_state("skip_{$method}", true);
                     $printer('Breakpoint "' . $method . '" Turned Off..');
                     $break = true;
                     $input = true;
                     break;
                 case 'kill':
                     @fclose(STDOUT);
                     @fclose(STDERR);
                     @exec('ps -C "php ' . Core_Daemon::get('filename') . '" -o pid= | xargs kill -9 ');
                     break;
                 case 'capture':
                     $backtrace = debug_backtrace();
                     if ($backtrace[1]['function'] !== '__call' || $method == self::CAPTURE) {
                         $printer('Cannot capture this :(');
                         break;
                     }
                     $input = self::CAPTURE;
                     $break = true;
                     break;
                 case 'y':
                     $input = self::CONT;
                     $break = true;
                     break;
                 case 'n':
                     $input = self::ABORT;
                     $break = true;
                     break;
                 default:
                     if ($input) {
                         $printer("Unknown Command! See `help` for list of commands.");
                     }
             }
         }
     } catch (Exception $e) {
         $this->mutex_release();
         throw $e;
     }
     $this->mutex_release();
     return $input;
 }
Beispiel #2
0
 public function setup()
 {
     // This class implements both the Task and the Plugin interfaces. Like plugins, this setup() method will be
     // called in the parent process during application init. And like tasks, this setup() method will be called right
     // after the process is forked.
     $that = $this;
     if (Core_Daemon::is('parent')) {
         // Use the ftok() method to create a deterministic memory address.
         // This is a bit ugly but ftok needs a filesystem path so we give it one using the daemon filename and
         // current worker alias.
         $tmp = sys_get_temp_dir();
         $ftok = sprintf($tmp . '/%s_%s', str_replace('/', '_', $this->daemon->get('filename')), $this->alias);
         if (!touch($ftok)) {
             $this->fatal_error("Unable to create Worker ID. ftok() failed. Could not write to {$tmp} directory at {$ftok}");
         }
         $this->guid = ftok($ftok, $this->alias[0]);
         @unlink($ftok);
         if ($this->guid == -1) {
             $this->fatal_error("Unable to create Worker ID. ftok() failed. Unexpected return value: {$this->guid}");
         }
         $this->via->setup();
         $this->via->purge();
         if ($this->daemon->get('debug_workers')) {
             $this->debug();
         }
         $this->daemon->on(Core_Daemon::ON_PREEXECUTE, array($this, 'run'));
         $this->daemon->on(Core_Daemon::ON_IDLE, array($this, 'garbage_collector'), ceil(120 / ($this->workers * 0.5)));
         // Throttle the garbage collector
         $this->daemon->on(Core_Daemon::ON_SIGNAL, array($this, 'dump'), null, function ($args) {
             return $args[0] == SIGUSR1;
         });
         $this->fork();
     } else {
         unset($this->calls, $this->running_calls, $this->on_return, $this->on_timeout, $this->call_count);
         $this->calls = $this->call_count = $this->running_calls = array();
         $this->via->setup();
         $event_restart = function () use($that) {
             $that->log('Restarting Worker Process...');
         };
         $this->daemon->on(Core_Daemon::ON_SIGNAL, $event_restart, null, function ($args) {
             return $args[0] == SIGUSR1;
         });
         call_user_func($this->get_callback('setup'));
         $this->log('Worker Process Started');
     }
 }
Beispiel #3
0
 public function setup()
 {
     $ftok = ftok(Core_Daemon::get('filename'), 'L');
     $this->shm = shm_attach($ftok, 512, 0666);
 }