mirror of
https://github.com/danog/amp.git
synced 2025-01-22 13:21:16 +01:00
still more fun with readme
This commit is contained in:
parent
233d82e083
commit
3b151690e8
349
README.md
349
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
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- 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.
|
||||
|
||||
##### c. `onWritable()`
|
||||
#### `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
|
||||
<?php
|
||||
(new Alert\UvReactor)->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
|
||||
<?php
|
||||
|
||||
$reactor = new Alert\NativeReactor;
|
||||
|
||||
// Register a watcher we'll disable
|
||||
$watcherIdToDisable = $reactor->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
|
||||
<?php
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$reactor = new Alert\NativeReactor;
|
||||
|
||||
// Register a watcher
|
||||
$myWatcherId = $reactor->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
|
||||
<?php
|
||||
|
||||
class Server {
|
||||
private $reactor;
|
||||
private $clients = [];
|
||||
public function __construct(Alert\Reactor $reactor) {
|
||||
$this->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
|
||||
<?php
|
||||
|
||||
```php
|
||||
<?php
|
||||
Alert\run(function() {
|
||||
$myWatcherId = Alert\repeat(function() {
|
||||
echo "tick\n";
|
||||
}, $msInterval = 1000);
|
||||
|
||||
// Cancel $myWatcherId in five seconds and exit the reactor loop
|
||||
Alert\once(function() use ($myWatcherId) {
|
||||
Alert\cancel($myWatcherId);
|
||||
}, $msDelay = 5000);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## 5. Process Signal Watchers
|
||||
## Common Patterns
|
||||
|
||||
@TODO
|
||||
|
||||
|
||||
## 6. Pausing, Resuming and Cancelling Watchers
|
||||
## Addenda
|
||||
|
||||
#### An Important Note on Writability
|
||||
|
||||
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.
|
||||
|
||||
|
||||
#### Process Signal Number Availability
|
||||
|
||||
@TODO
|
||||
|
||||
##### a. `disable()`
|
||||
#### IO Performance
|
||||
|
||||
@TODO
|
||||
|
||||
##### b. `enable()`
|
||||
|
||||
@TODO
|
||||
|
||||
##### c. `cancel()`
|
||||
|
||||
@TODO
|
||||
@TODO Talk about why we don't use event emitters, stream abstractions, userland buffers or thenables
|
||||
for low-level operations ...
|
||||
|
||||
|
||||
[libevent]: http://pecl.php.net/package/libevent "libevent"
|
||||
|
Loading…
x
Reference in New Issue
Block a user