Skip to content

Commit

Permalink
pradosoft#972 TProcessHelper, TSignalsDispatcher, and related classes…
Browse files Browse the repository at this point in the history
… & functions

- TProcessHelper for process related functions like forking (pcntl_fork) and isSystemWindows().
- TSignalsDispatcher for raising signal related global events from signals for multiple handlers per signal, alarm and disarm callbacks at a specific time (1 second precision), and for callbacks per child processes (proc_open, pcntl_fork, etc) when they end (or "stop and start"?).
- ISingleton is a new interface for application singleton objects
- TShellWriter is updated to use the TProcessHelper::isSystemWindows function
-TComponent only hasMethod of global event handlers that do actually exist.  As a global event it exists always, but not the method.
- TEventSubscription example is updated to reflect TSignalsDispatcher and TSignalParameter
- TApplicationSignals behavior for configuration and attaching of TSignalsDispatcher
- TCaptureForkLog behavior for capturing the logs of forked child processes in the main log.  Effectively a ForkLogRouter but registering as a behavior on TApplication.
- TForkable behavior for attaching the owner's ::fxPrepareForFork() and ::fxRestoreAfterFork() handlers to their respective global events.
-TGlobalClassAware behavior for attaching the owner's ::fxAttachClassBehavior() and ::fxDetachClassBehavior() to their respective global events so they change with global class changes without listening.
- TProcessWindowsPriority specifies the numeric priorities that windows uses for each respective priority level.
- TProcessWindowsPriorityName specifies the text priorities that windows uses for each respective priority level.
  • Loading branch information
belisoful committed Jul 24, 2023
1 parent 695aa69 commit b43eadd
Show file tree
Hide file tree
Showing 28 changed files with 3,277 additions and 27 deletions.
4 changes: 3 additions & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ ENH: Issue #944 - TExitException for gracefully exiting the application anywhere
ENH: Issue #979 - TComponent::raiseEvent optionally execute handlers in reverse; asa() and getBehaviors() searches for behaviors based on class after failing on name. (belisoful)
ENH: Issue #975 - Prado base object methods for each log type and automatic discovery of calling object for log category. (belisoful)
ENH: Issue #977 - THttpRequest::onResolveRequest for custom service resolution and TRequestConnectionUpgrade behavior for selecting service on http headers for "websocket". (belisoful)
ENH: Issue #982 General Logging update: Profiling, Flushing large logs for long running processes, optional Tracing, tracks PID for multi-threaded logging, TBrowserLogRoute colorizes the time delta, TDbLogRoute adds a new DB field 'prefix' and functions for getting the DB log, DB log count, and deleting DB logs, TDBLogRoute also adds a RetainPeriod for automatically removing old logs, Adds an event TLogger::OnFlushLogs and flushes as a register_shutdown_function, adds the TSysLogRoute, and adds unit tests for logging
ENH: Issue #982 - General Logging update: Profiling, Flushing large logs for long running processes, optional Tracing, tracks PID for multi-threaded logging, TBrowserLogRoute colorizes the time delta, TDbLogRoute adds a new DB field 'prefix' and functions for getting the DB log, DB log count, and deleting DB logs, TDBLogRoute also adds a RetainPeriod for automatically removing old logs, Adds an event TLogger::OnFlushLogs and flushes as a register_shutdown_function, adds the TSysLogRoute, and adds unit tests for logging. (belisoful)
ENH: Issue #984 - TEventSubscription for temporary event handlers. (belisoful)
ENG: Issue #972 - TProcessHelper (isSystemWindows, forking, kill, priority) and TSignalsDispatcher for delegating signals to respective global events, alarm interrupt callbacks at specific times, and per child PIDs callbacks. TEventSubscription can subscribe to a PHP process signal, an integer, as an event "name" (in TSignalsDispatcher). (belisoful)

## Version 4.2.2 - April 6, 2023

Expand Down
9 changes: 9 additions & 0 deletions framework/Exceptions/messages/messages.txt
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,15 @@ parameterizebehavior_cannot_set_defaultValue_after_attach = TParameterizeBehavio
parameterizebehavior_cannot_set_localize_after_attach = TParameterizeBehavior cannot set Localize after being attached.
parameterizebehavior_cannot_set_routeBehaviorName_after_attach = TParameterizeBehavior cannot set RouteBehaviorName after being attached.

processhelper_no_forking = Process Forking is not supported.
processhelper_no_signals = Process signals cannot be sent on Windows.

appsignals_no_change = TApplicationSignals::{0} cannot be changed after attaching.
appsignals_not_a_dispatcher = '{0}' is not a TSignalsDispatcher.

signalsdispatcher_bad_pid = '{0}' is not a valid PID.
signalsdispatcher_no_change = TSignalsDispatcher::{0} cannot be changed after attaching.

template_closingtag_unexpected = Unexpected closing tag '{0}' is found.
template_closingtag_expected = Closing tag '{0}' is expected, found '{1}'.
template_directive_nonunique = Directive '<%@ ... %>' must appear at the beginning of the template and can appear at most once.
Expand Down
27 changes: 27 additions & 0 deletions framework/ISingleton.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* ISingleton interface file.
*
* @author Brad Anderson <[email protected]>
* @link https://github.com/pradosoft/prado
* @license https://github.com/pradosoft/prado/blob/master/LICENSE
*/

namespace Prado;

/**
* ISingleton interface.
*
* This interface is for getting specific class (application) singletons.
*
* @author Brad Anderson <[email protected]>
* @since 4.2.3
*/
interface ISingleton
{
/**
* @param bool $create Should the singleton be created if it doesn't exist.
* @return ?object The singleton instance of the class
*/
public static function singleton(bool $create = true): ?object;
}
14 changes: 3 additions & 11 deletions framework/Shell/TShellWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace Prado\Shell;

use Prado\TPropertyValue;
use Prado\Util\Helpers\TProcessHelper;

/**
* TShellWriter class.
Expand Down Expand Up @@ -308,7 +309,7 @@ public function unformat($str)
*/
protected function isColorSupported()
{
if (static::isRunningOnWindows()) {
if (TProcessHelper::isSystemWindows()) {
return getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON';
}

Expand Down Expand Up @@ -519,7 +520,7 @@ public static function getScreenSize($refresh = false)
return $size;
}

if (static::isRunningOnWindows()) {
if (TProcessHelper::isSystemWindows()) {
$output = [];
exec('mode con', $output);
if (isset($output[1]) && strpos($output[1], 'CON') !== false) {
Expand Down Expand Up @@ -595,13 +596,4 @@ public function wrapText($text, $indent = 0, $refresh = false)

return implode("\n", $lines);
}

/**
* Returns true if the console is running on windows.
* @return bool
*/
public static function isRunningOnWindows()
{
return DIRECTORY_SEPARATOR === '\\';
}
}
4 changes: 2 additions & 2 deletions framework/TComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @author Qiang Xue <[email protected]>
*
* Global Events, intra-object events, Class behaviors, expanded behaviors
* @author Brad Anderson <javalizard@mac.com>
* @author Brad Anderson <belisoful@icloud.com>
*
* @author Qiang Xue <[email protected]>
* @link https://github.com/pradosoft/prado
Expand Down Expand Up @@ -1136,7 +1136,7 @@ protected function getCallChain($method, ...$args): ?TCallChain
*/
public function hasMethod($name)
{
if (Prado::method_visible($this, $name) || strncasecmp($name, 'fx', 2) === 0 || strncasecmp($name, 'dy', 2) === 0) {
if (Prado::method_visible($this, $name) || strncasecmp($name, 'dy', 2) === 0) {
return true;
} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
foreach ($this->_m->toArray() as $behavior) {
Expand Down
5 changes: 4 additions & 1 deletion framework/TEventSubscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
* {
* $exitLoop = false;
* $subscription = new TEventSubscription($dispatcher, 'fxSignalInterrupt',
* function ($sender, $param) use (&$exitLoop){$exitLoop = true;});
* function ($sender, $param) use (&$exitLoop){
* $exitLoop = true;
* $param->setExit(false);
* });
* ...
* } // dereference unsubscribes
* ```
Expand Down
166 changes: 166 additions & 0 deletions framework/Util/Behaviors/TApplicationSignals.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<?php
/**
* TApplicationSignals class file.
*
* @author Brad Anderson <[email protected]>
* @link https://github.com/pradosoft/prado
* @license https://github.com/pradosoft/prado/blob/master/LICENSE
*/

namespace Prado\Util\Behaviors;

use Prado\Exceptions\TInvalidDataValueException;
use Prado\Exceptions\TInvalidOperationException;
use Prado\TComponent;
use Prado\TPropertyValue;
use Prado\Util\TBehavior;
use Prado\Util\TSignalsDispatcher;

/**
* TApplicationSignals class.
*
* This behavior installs the {@see \Prado\Util\TSignalsDispatcher} (or subclass) for
* the application when PHP pcntl_* is available. The signals dispatcher class can
* be specified with {@see self::setSignalsClass()} and is installed when the TApplicationSignals
* behavior is attached to the TApplication owner.
*
* There is a TSignalsDispatcher getter {@see self::getSignalsDispatcher} added to
* the owner (TApplication) for retrieving the dispatcher.
*
* There are two properties of TApplicationSignals for TSignalsDispatcher. {@see
* self::setAsyncSignals} changes how signals are handled. When synchronous,
* {@see \Prado\Util\TSignalsDispatcher::syncDispatch()} must be called for signals
* to be processed. When asynchronous, the signals will be handled by atomic interrupt.
*
* ```xml
* <behavior name="appSignals" AttachToClass="Prado\TApplication" class="Prado\Util\Behaviors\TApplicationSignals" PriorHandlerPriority="5" />
* ```
*
* @author Brad Anderson <[email protected]>
* @since 4.2.3
*/
class TApplicationSignals extends TBehavior
{
/** @var string the signals class. */
protected ?string $_signalsClass = null;

/**
* Attaches the TSignalsDispatcher to handle the process signals.
* @param TComponent $component The owner.
* @return bool Should the behavior's event handlers be attached.
*/
protected function attachEventHandlers(TComponent $component): bool
{
if ($return = parent::attachEventHandlers($component)) {
($this->getSignalsClass())::singleton();
}
return $return;
}

/**
* Detaches the TSignalsDispatcher from handling the process signals.
* @param TComponent $component The owner.
* @return bool Should the behavior's event handlers be detached.
*/
protected function detachEventHandlers(TComponent $component): bool
{
if ($return = parent::detachEventHandlers($component)) {
if ($dispatcher = ($this->getSignalsClass())::singleton(false)) {
$dispatcher->detach();
unset($dispatcher);
}
}
return $return;
}

/**
* @return ?object The Signal Dispatcher.
*/
public function getSignalsDispatcher(): ?object
{
return ($this->getSignalsClass())::singleton(false);
}

/**
* @return ?string The class of the Signals Dispatcher.
*/
public function getSignalsClass(): ?string
{
if ($this->_signalsClass === null) {
$this->_signalsClass = TSignalsDispatcher::class;
}

return $this->_signalsClass;
}

/**
* @param ?string $value The class of the Signals Dispatcher.
* @throws TInvalidOperationException When already attached, this cannot be changed.
* @throws TInvalidDataValueException When the class is not a TSignalsDispatcher.
* @return static The current object.
*/
public function setSignalsClass($value): static
{
if ($this->getOwner()) {
throw new TInvalidOperationException('appsignals_no_change', 'SignalsClass');
}
if ($value === '') {
$value = null;
}

if ($value !== null) {
$value = TPropertyValue::ensureString($value);
if (!is_a($value, TSignalsDispatcher::class, true)) {
throw new TInvalidDataValueException('appsignals_not_a_dispatcher', $value);
}
}

$this->_signalsClass = $value;

return $this;
}

/**
* @return bool Is the system executing signal handlers asynchronously.
*/
public function getAsyncSignals(): bool
{
return ($this->getSignalsClass())::getAsyncSignals();
}

/**
* @param mixed $value Set the system to execute signal handlers asynchronously (or synchronously on false).
* @return bool Was the system executing signal handlers asynchronously.
*/
public function setAsyncSignals($value): bool
{
return ($this->getSignalsClass())::setAsyncSignals(TPropertyValue::ensureBoolean($value));
}

/**
* When the original signal handlers are placed into the Signals Events this is the
* priority of original signal handlers.
* @return ?float The priority of the signal handlers that were installed before
* the TSignalsDispatcher attaches.
*/
public function getPriorHandlerPriority(): ?float
{
return ($this->getSignalsClass())::getPriorHandlerPriority();
}

/**
* @param null|float|string $value The priority of the signal handlers that were installed before
* the TSignalsDispatcher attaches.
* @return bool Is the Prior Handler Priority changed.
*/
public function setPriorHandlerPriority($value): bool
{
if ($value === '') {
$value = null;
}
if ($value !== null) {
$value = TPropertyValue::ensureFloat($value);
}
return ($this->getSignalsClass())::setPriorHandlerPriority($value);
}
}
Loading

0 comments on commit b43eadd

Please sign in to comment.