/** * 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; }
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'); } }
public function setup() { $ftok = ftok(Core_Daemon::get('filename'), 'L'); $this->shm = shm_attach($ftok, 512, 0666); }