From 3b151690e856d29653c17ccaa07cf84317a0b2f6 Mon Sep 17 00:00:00 2001 From: Daniel Lowrey Date: Thu, 7 Aug 2014 15:47:47 -0400 Subject: [PATCH] still more fun with readme --- README.md | 357 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 260 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 7470f57..db91e20 100644 --- a/README.md +++ b/README.md @@ -30,57 +30,61 @@ $ php composer.phar require rdlowrey/alert:~0.11.x ``` ----------------------------------------------------------------------------------------------------- + The Guide ---------------------------------------------------------------------------------------------------- -**1. Event Reactor Concepts** +**Event Reactor Concepts** - - a. Reactor Implementations - - b. Reactor == Task Scheduler - - c. The Universal Reactor + - Reactor Implementations + - Reactor == Task Scheduler + - The Universal Reactor -**2. Controlling the Reactor** +**Controlling the Reactor** - - a. `run()` - - b. `tick()` - - c. `stop()` + - `run()` + - `tick()` + - `stop()` -**2. Timer Watchers** +**Timer Watchers** - - a. `immediately()` - - b. `once()` - - c. `repeat()` - - d. `at()` + - `immediately()` + - `once()` + - `repeat()` + - `at()` -**3. Stream IO Watchers** +**Stream IO Watchers** - - a. `onReadable()` - - b. `onWritable()` - - c. `watchStream()` - - d. A Note on Writability - - e. A Note on IO Performance + - `onReadable()` + - `onWritable()` + - `watchStream()` -**4. Process Signal Watchers** +**Process Signal Watchers** - @TODO -**5. Pausing, Resuming and Cancelling Watchers** +**Pausing, Resuming and Cancelling Watchers** - - a. `disable()` - - b. `enable()` - - c. `cancel()` + - `disable()` + - `enable()` + - `cancel()` -**6. Common Patterns** +**Common Patterns** - @TODO +**Addenda** + +- An Important Note on Writability Watchers +- Process Signal Numbers +- IO Performance + ---------------------------------------------------------------------------------------------------- -## 1. Event Reactor Concepts +## Event Reactor Concepts -##### a. Reactor Implementations +#### Reactor Implementations It may surprise people to learn that the PHP standard library already has everything we need to write event-driven and non-blocking applications. We only reach the limits of native PHP's @@ -108,9 +112,9 @@ control signals. The current implementations are listed here: As mentioned, only `UvReactor` and `LibeventReactor` implement the `Alert\SignalReactor` interface to offer cross-operating system signal handling capabilities. At this time use of the `UvReactor` is recommended over `LibeventReactor` as the php-uv extension offers more in the way of tangentially -related (but useful) functionality for robust non-blocking applications than libevent. +related (but useful) functionality for robust non-blocking applications. -##### b. Reactor == Task Scheduler +#### Reactor == Task Scheduler The first thing we need to understand to program effectively using an event loop is this: @@ -142,9 +146,9 @@ tick -after stop()- ``` -This simple example should be enough to demonstrate the concept that what happens inside the event -reactor's run loop is like its own separate program. Your script will not continue past the point -where `Reactor::run()` unless one of the previously mentioned conditions for stoppage is met. +Hopefully this output demonstrates the concept that what happens inside the event reactor's run loop +is like its own separate program. Your script will not continue past the point of `Reactor::run()` +unless one of the previously mentioned conditions for stoppage is met. While an application can and often does take place entirely inside the confines of the run loop, we can also use the reactor to do things like the following example which imposes a short-lived @@ -169,21 +173,23 @@ Alert\run(function() use (&$stdinWatcher, &$number) { }); if (is_null($number)) { - echo "You took too long to respond, so we chose the number, '4' by fair dice roll\n"; + echo "You took too long so we chose the number '4' by fair dice roll\n"; } else { echo "Your number is: ", (int) $number, "\n"; } Alert\cancel($stdinWatcher); // <-- clean up after ourselves stream_set_blocking(STDIN, true); + +// Continue doing regular synchronous things here. ``` The details of what's happening in this example are unimportant and involve functionality that will -be covered later. For now, the takeaway should simply be that you can move in and out of the event -loop like a ninja if you wish. +be covered later. For now, the takeaway should simply be that it's possible tomove in and out of the +event loop like a ninja. -##### c. The Universal Reactor +#### The Universal Reactor In the above example we use the reactor's procedural API to register stream IO and timere watchers. However, Alert also exposes an object API. Though it almost never makes sense to run multiple event @@ -222,60 +228,60 @@ stream_set_blocking(STDIN, true); ``` Always remember: *bugs arising from the existence of multiple reactor instances are exceedingly -difficult to debug.* The reason for this should be relatively clear: running one event loop will -block script execution and prevent others from executing at the same time. This sort of "loop -starvation" results in events that inexplicably fail to trigger. You should endeavor to always use -the same reactor instance in your application when you instantiate and use the object API. Because -the event loop is often a truly global feature of an application the procedural API functions use -a static instance to ensure the same `Reactor` is reused. Be careful about instantiating reactors -manually and mixing in calls to the function API. +difficult to debug.* The reason for this should be relatively clear. It's because running one event +loop will block script execution and prevent others from executing at the same time. This sort of +"loop starvation" results in events that inexplicably fail to trigger. You should endeavor to always +use the same reactor instance in your application when you instantiate and use the object API. +Because the event loop is often a truly global feature of an application the procedural API +functions use a static instance to ensure the same `Reactor` is reused. Be careful about +instantiating reactors manually and mixing in calls to the function API. -## 2. Controlling the Reactor +## Controlling the Reactor @TODO -##### a. `run()` +#### a. `run()` @TODO -##### b. `tick()` +#### b. `tick()` @TODO -##### c. `stop()` +#### c. `stop()` @TODO -## 3. Timer Watchers +## Timer Watchers -Alert exposes several ways to schedule future events: +Alert exposes several ways to schedule timer" events: - `Alert\Reactor::immediately()` | `Alert\immediately()` - `Alert\Reactor::once()` | `Alert\once()` - `Alert\Reactor::repeat()` | `Alert\repeat()` - `Alert\Reactor::at()` | `Alert\at()` -Let's look at the details for these messages ... +Let's look at some details for each method ... -##### a. `immediately()` +#### `immediately()` - Schedule a callback to execute in the next iteration of the event loop - This method guarantees a "clean" call stack to avoid starvation of other events in the - current iteration of the loop if called continuously + current iteration of the loop if called continuously. - After an "immediately" timer watcher executes it is automatically garbage collected by the reactor so there is no need for applications to manually cancel the associated watcher ID. - Like all watchers, "immediately" timers may be disabled and reenabled. If you disable this watcher between when you first schedule it and when it runs the reactor *will not* be able to garbage collect it until it executes. Therefore you must manually cancel an immediately - watcher yourself if it never actually executes to free associate resources. + watcher yourself if it never actually executes to free the associated resources. -##### b. `once()` +#### `once()` - Schedule a callback to execute after a delay of *n* milliseconds - A "once" watcher is also automatically garbage collected by the reactor after execution and @@ -287,7 +293,7 @@ Let's look at the details for these messages ... cancelled to free resources if it never runs due to being disabled by the application after creation. -##### c. `repeat()` +#### `repeat()` - Schedule a callback to repeatedly execute every *n* millisconds. - Unlike one-time watchers, "repeat" timer resources must be explicitly cancelled to free @@ -295,14 +301,14 @@ Let's look at the details for these messages ... will result in memory leaks in your application. - Like all other watchers, "repeat" timers may be disabled/reenabled at any time. -##### d. `at()` +#### `at()` - Schedule a callback to execute at a specific time in the future. Future time may either be an integer unix timestamp or any string parsable by PHP's `strtotime()` function. - In all other respects "at" watchers are the same as "immediately" and "once" timers. -## 4. Stream IO Watchers +## Stream IO Watchers Stream watchers are how we know that data exists to read or that write buffers are empty. These notifications are how we're able to actually *create* things like http servers and asynchronous @@ -314,25 +320,12 @@ There are two classes of IO watcher: - Readability watchers - Writability watchers -##### a. An Important Note on Writability - -Before continuing we should note one very important point about writability watchers: - -> **IMPORTANT:** Because streams are essentially *"always"* writable you should only enable -> writability watchers while you have data to send. If you leave these watchers enabled when your -> application doesn't have anything to write the watcher will trigger endlessly until disabled -> or cancelled. This will max out your CPU. If you're seeing inexplicably high CPU usage in your -> application it's a good bet you've got a writability watcher that you failed to disable or -> cancel after you were finished with it. - -Now that's out of the way let's look at how Alert reactors expose IO watcher functionality ... - -##### b. `onReadable()` +#### `onReadable()` Watchers registered via `Reactor::onReadable()` trigger their callbacks in the following situations: - - Triggered when data is available to read on the watched stream - - Also triggered if the stream is at EOF (for sockets, this means the connection is lost) + - When data is available to read on the stream under observation + - When the stream is at EOF (for sockets, this means the connection is lost) A common usage pattern for reacting to readable data looks something like this example: @@ -358,15 +351,17 @@ $client->watcherId = Alert\onReadable($client->socket, function() use ($client) In the above example we've done a few very simple things: - 1. Register a readability watcher for a socket that will trigger our callback when there is - data available to read. - 2. When we read data from the stream in our triggered callback we pass that to a stateful parser - that does something domain-specific when certain conditions are met. - 3. If the `fread()` call indicates that the socket connection is dead we clean up any resources - we've allocated for the storage of this stream. This process should always include calling - `Reactor::cancel()` on any reactor watchers we registered in relation to the stream. + - Register a readability watcher for a socket that will trigger our callback when there is + data available to read. -##### c. `onWritable()` + - When we read data from the stream in our triggered callback we pass that to a stateful parser + that does something domain-specific when certain conditions are met. + + - If the `fread()` call indicates that the socket connection is dead we clean up any resources + we've allocated for the storage of this stream. This process should always include calling + `Reactor::cancel()` on any reactor watchers we registered in relation to the stream. + +#### `onWritable()` - Streams are essentially *"always"* writable. The only time they aren't is when their respective write buffers are full. @@ -378,7 +373,7 @@ A common usage pattern for reacting to writability looks something like this exa // @TODO Add example code ``` -##### d. `watchStream()` +#### `watchStream()` The `Reactor::watchStream()` functionality exposes both readability and writability watcher registration in a single function as a convenience for programmers who wish to use the same @@ -404,31 +399,199 @@ $readWatcherId = Alert\watchStream($stream, $myCallbackFunction, $flags); > registration time via watchStream() you *must* pass the `WATCH_NOW` flag. -##### e. A Note on IO Performance +## Process Signal Watchers -@TODO Talk about why we don't use event emitters, buffers and thenables for low-level operations ... +The `Alert\SignalReactor` extends the base reactor interface to expose an API for handling process +control signals in your application like any other event. Simply use a compatible event reactor +implementation (`UvReactor` or `LibeventReactor`, preferably the former) and interact with its +`SignalReactor::onSignal()` method. Consider: + +```php +run(function($reactor) { + // Let's tick off output once per second so we can see activity. + $reactor->repeat(function() { + echo "tick: ", date('c'), "\n"; + }, $msInterval = 1000); + + // What to do when a SIGINT signal is received + $watcherId = $reactor->onSignal(UV::SIGINT, function() { + echo "Caught SIGINT! exiting ...\n"; + exit; + }); +}); +``` + +As should be clear from the above example, signal watchers may be enabled, disabled and cancelled +like any other event. + +## Pausing, Resuming and Cancelling Watchers + +All watchers, regardless of type, can be temporarily disabled and enabled in addition to being +cleared via `Reactor::cancel()`. This allows for advanced capabilities such as disabling the +acceptance of new socket clients in server applications when simultaneity limits are reached. In +general, the performance characteristics of watcher reuse via pause/resume are favorable by +comparison to repeatedly cancelling and re-registering watchers. + +#### `disable()` + +A simple disable example: + +```php +once(function() { + echo "I'll never execute in one second because: disable()\n"; +}, $msDelay = 1000); + +// Register a watcher to perform the disable() operation +$reactor->once(function() use ($watcherIdToDisable, $reactor) { + echo "Disabling WatcherId: ", $watcherIdToDisable, "\n"; + $reactor->disable($watcherIdToDisable); +}, $msDelay = 500); + +$reactor->run(); +``` + +After our second watcher callback executes the reactor loop exits because there are no longer any +enabled watchers registered to process. + +#### `enable()` + +Using `enable()` is just as simple as the `disable()` example we just saw: + +```php +repeat(function() { + echo "tick\n"; +}, $msDelay = 1000); + +// Disable the watcher +$reactor->disable($myWatcherId); + +// Remember, nothing happens until the reactor runs, so it doesn't matter that we +// previously created and disabled $myWatcherId +$reactor->run(function($reactor) use ($myWatcherId) { + // Immediately enable the watcher when the reactor starts + $reactor->enable($myWatcherId); + // Now that it's enabled we'll see tick output in our console every 1000ms. +}); +``` + +For a slightly more complex use case, let's look at a common scenario where a server might create a +write watcher that is initially disabled but subsequently enabled as necessary: + +```php +reactor = $reactor; + } + + public function startServer() { + // ... server bind and accept logic would exist here + $this->reactor->run(); + } + + private function onNewClient($socket) { + $socketId = (int) $socket; + $client = new ClientStruct; + $client->socket = $socket; + $client->readWatcher = $this->reactor->onReadable($socket, function() use ($client) { + $this->onReadable($client); + }); + $client->writeWatcher = $this->reactor->onReadable($socket, function() use ($client) { + $this->doWrite($client); + }, $enableNow = false); // <--- notice how we don't enable the write watcher now + $this->clients[$socketId] = $client; + } + + // ... other class implementation details here ... + + private function writeToClient($client, $data) { + $client->writeBuffer .= $data; + $this->doWrite($client); + } + + private function doWrite(ClientStruct $client) { + $bytesToWrite = strlen($client->writeBuffer); + $bytesWritten = @fwrite($client->socket, $client->writeBuffer); + + if ($bytesToWrite === $bytesWritten) { + $this->reactor->disable($client->writeWatcher); + } elseif ($bytesWritten >= 0) { + $client->writeBuffer = substr($client->writeBuffer, $bytesWritten); + $this->reactor->enable($client->writeWatcher); + } elseif ($this->isSocketDead($client->socket)) { + $this->unloadClient($client); + } + } + + // ... other class implementation details here ... +} +``` + +#### `cancel()` + +It's important to *always* cancel persistent watchers once you're finished with them or you'll +create memory leaks in your application. This functionality works in exactly the same way as the +above enable/disable examples: + +```php +