Skip to content

Commit b94d965

Browse files
authored
Merge pull request #6 from ace411/v0.x
v0.3.0 changes
2 parents c99a670 + 2bc995b commit b94d965

File tree

16 files changed

+594
-220
lines changed

16 files changed

+594
-220
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,22 @@ jobs:
66
strategy:
77
fail-fast: false
88
matrix:
9-
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
9+
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4']
1010
name: PHP ${{ matrix.php }}
1111
steps:
12-
- uses: actions/checkout@v1
12+
- uses: actions/checkout@v3
1313
- name: Install PHP
14-
uses: shivammathur/setup-php@master
14+
uses: shivammathur/setup-php@v2
1515
with:
1616
php-version: ${{ matrix.php }}
17+
extensions: parallel
1718
- name: Validate composer.json and composer.lock
1819
run: composer validate
1920
- name: Get Composer cache directory
2021
id: composer-cache
2122
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
2223
- name: Cache dependencies
23-
uses: actions/cache@v1
24+
uses: actions/cache@v3
2425
with:
2526
path: ${{ steps.composer-cache.outputs.dir }}
2627
key: ${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}

.php-cs-fixer.php

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/**
44
* php-cs file
55
* contains rubric for code-style fixes
6-
*
6+
*
77
* @package chemem/asyncify
88
* @author Lochemem Bruno Michael
99
* @license Apache-2.0
@@ -15,18 +15,24 @@
1515
use PhpCsFixer\Finder;
1616

1717
$finder = Finder::create()
18-
->exclude(['vendor', 'cache', 'bin'])
18+
->exclude(['vendor', 'cache'])
1919
->in(__DIR__);
2020

2121
$config = new Config;
2222

2323
return $config
24-
->setRules([
25-
'@PSR12' => true,
26-
'linebreak_after_opening_tag' => true,
27-
'binary_operator_spaces' => [
28-
'operators' => ['=>' => 'align', '=' => 'align'],
29-
],
30-
])
24+
->setRules(
25+
[
26+
'@PSR12' => true,
27+
'linebreak_after_opening_tag' => true,
28+
'trailing_comma_in_multiline' => false,
29+
'binary_operator_spaces' => [
30+
'operators' => [
31+
'=>' => 'align',
32+
'=' => 'align',
33+
],
34+
],
35+
]
36+
)
3137
->setFinder($finder)
3238
->setIndent(' ');

README.md

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,20 @@
55
[![StyleCI](https://github.styleci.io/repos/365018048/shield?branch=master)](https://github.styleci.io/repos/365018048?branch=master)
66
[![asyncify CI](https://github.com/ace411/asyncify/actions/workflows/ci.yml/badge.svg)](https://github.com/ace411/asyncify/actions/workflows/ci.yml)
77
[![License](http://poser.pugx.org/chemem/asyncify/license)](https://packagist.org/packages/chemem/asyncify)
8-
[![composer.lock](http://poser.pugx.org/chemem/asyncify/composerlock)](https://packagist.org/packages/chemem/asyncify)
9-
[![Dependents](http://poser.pugx.org/chemem/asyncify/dependents)](https://packagist.org/packages/chemem/asyncify)
108
[![Latest Stable Version](http://poser.pugx.org/chemem/asyncify/v)](https://packagist.org/packages/chemem/asyncify)
9+
[![PHP Version Require](http://poser.pugx.org/chemem/asyncify/require/php)](https://packagist.org/packages/chemem/asyncify)
1110

1211
</span>
1312

14-
A simple PHP library that runs your synchronous PHP functions asynchronously.
13+
A simple library with which to run blocking I/O in a non-blocking fashion.
1514

1615
## Requirements
1716

18-
- PHP 7.2 or higher
17+
- PHP 7.2 or newer
1918

2019
## Rationale
2120

22-
PHP is a largely synchronous (blocking) runtime. Asynchrony - achievable via ReactPHP and other similar suites - is a potent approach to mitigating the arduousness of I/O operations that feature prominently in day-to-day programming. Melding blocking and non-blocking routines in PHP can be a tricky proposition: when attempted haphazardly, it can yield unsightly outcomes.
23-
24-
The impetus for creating and maintaining `asyncify` is combining blocking and non-blocking PHP. Built atop ReactPHP, `asyncify` is a tool that allows one to run blocking PHP functions in an event-driven I/O environment.
21+
PHP is home to a host of functions that condition CPU idleness between successive (serial) executions—blocking functions. The expense of blocking calls—invocations of such functions—is such that they can, when deployed haphazardly in evented systems, inflict unnecessary CPU waiting behavior whilst the kernel attempts to interleave non-blocking calls. `asyncify` is a bridge between the blocking I/O in the language userspace and the evented I/O in ReactPHP. It allows those who choose to avail themselves of it the ability to run their blocking code, with minimal plumbing, in evented systems, without derailing them.
2522

2623
## Installation
2724

@@ -31,6 +28,14 @@ Though it is possible to clone the repo, Composer remains the best tool for inst
3128
$ composer require chemem/asyncify
3229
```
3330

31+
Newer versions of the library prioritize multithreading. The precondition for operationalizing multithreading is installing the [parallel](https://github.com/krakjoe/parallel) extension (`ext-parallel`) and [`react-parallel/runtime`](https://github.com/reactphp-parallel/runtime) library which can be done with the directives in the snippet below.
32+
33+
```sh
34+
$ pie install pecl/parallel
35+
$ echo "\nextension=parallel" >> "/path/to/php.ini"
36+
$ composer require react-parallel/runtime
37+
```
38+
3439
## Usage
3540

3641
If you want to take a Functional Programming approach, facilitated by currying, the example below should suffice.
@@ -74,7 +79,33 @@ The examples directory contains more nuanced uses of the library that I recommen
7479

7580
- `asyncify` is no panacea, but is capable of asynchronously executing a plethora of blocking calls. As presently constituted, the library is **incapable of processing inputs and outputs that cannot be serialized**. Its quintessential asynchronous function application primitive - `call()` - works almost exclusively with string encodings of native language functions and lambdas imported via an autoloading mechanism.
7681

77-
- The library cannot parse closures. All executable arbitrary code should be emplaced in a string whose sole constituent is an immediately invokable anonymous function the format of which is `(function (...$args) { /* signature */ })`.
82+
- The library, in its default configuration, cannot parse closures. All executable arbitrary code should be emplaced in a string whose sole constituent is an immediately invokable anonymous function the format of which is `(function (...$args) { /* signature */ })`.
83+
84+
## Multithreading
85+
86+
With multithreading enabled, it is possible to invoke closures and other lambdas without necessarily representing them as strings. Although string encodings are still workable, lambdas like closures should be the preferred option for representing arbitrary blocking logic. The code in the following example should work with multithreading enabled.
87+
88+
```php
89+
use function Chemem\Asyncify\call;
90+
91+
$exec = call(
92+
function (...$args) {
93+
return \file_get_contents(...$args);
94+
},
95+
['/path/to/file']
96+
);
97+
98+
$exec->then(
99+
function (string $contents) {
100+
echo $contents;
101+
},
102+
function (\Throwable $err) {
103+
echo $err->getMessage();
104+
}
105+
);
106+
```
107+
108+
> It must be noted that string representations of lambdas (anonymous functions, closures and such) that are compatible with the default child process configuration, are not usable in versions that support multithreading.
78109
79110
## API Reference
80111

@@ -83,11 +114,16 @@ The examples directory contains more nuanced uses of the library that I recommen
83114
```php
84115
namespace Chemem\Asyncify;
85116

117+
use React\{
118+
EventLoop\LoopInterface,
119+
Promise\PromiseInterface,
120+
};
121+
86122
class Async {
87123

88124
/* Methods */
89-
public static create( ?string $autoload = null [, ?React\EventLoop\LoopInterface $rootDir = null ] ) : Async;
90-
public function call( string $function [, array $args ] ) : React\Promise\PromiseInterface;
125+
public static create( ?string $autoload = null [, ?LoopInterface $rootDir = null ] ) : Async;
126+
public function call( string|callable $function [, array $args ] ) : PromiseInterface;
91127
}
92128
```
93129

@@ -100,12 +136,20 @@ class Async {
100136
```php
101137
namespace Chemem\Asyncify;
102138

103-
call ( string $func [, array $args [, ?string $autoload = null [, ?React\EventLoop\LoopInterface $args = null ] ] ] ) : React\Promise\PromiseInterface;
139+
use React\{
140+
EventLoop\LoopInterface,
141+
Promise\PromiseInterface,
142+
};
143+
144+
call ( string|callable $func [, array $args [, ?string $autoload = null [, ?LoopInterface $args = null ] ] ] ) : PromiseInterface;
104145
```
105146

106147
`call` - Curryied function that bootstraps asynchronous function calls
107148

108-
> **Note:** `asyncify` utilizes the autoload file in the root directory of the project from which it is invoked.
149+
### Important Considerations
150+
151+
- `asyncify`, by default, utilizes the autoload file (`autoload.php`) in the `vendor` directory of the composer project in which it resides.
152+
- The library converts all errors in the functions slated for non-blocking execution to exceptions.
109153

110154
## Dealing with problems
111155

composer.json

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chemem/asyncify",
3-
"description": "A package that runs synchronous PHP functions asynchronously.",
3+
"description": "A simple library with which to run blocking I/O in a non-blocking fashion.",
44
"license": "Apache-2.0",
55
"type": "library",
66
"authors": [
@@ -12,15 +12,18 @@
1212
],
1313
"require": {
1414
"php": ">=7.2",
15-
"chemem/bingo-functional": "~2",
16-
"react/child-process": "~0",
17-
"react/promise": "~2"
15+
"react/child-process": "^0",
16+
"react/promise": "^2 || ^3"
1817
},
1918
"require-dev": {
20-
"ergebnis/composer-normalize": "~2",
21-
"friendsofphp/php-cs-fixer": "~2 || ~3",
22-
"phpunit/phpunit": "~8 || ~9",
23-
"react/async": "~3 || ~4"
19+
"ergebnis/composer-normalize": "^2",
20+
"friendsofphp/php-cs-fixer": "^2 || ^3",
21+
"phpunit/phpunit": "^8 || ^9",
22+
"react/async": "^3 || ^4"
23+
},
24+
"suggest": {
25+
"ext-parallel": "A succinct parallel concurrency API for PHP 8",
26+
"react-parallel/runtime": "Convenience wrapper around the ext-parallel Runtime and ReactPHP"
2427
},
2528
"minimum-stability": "stable",
2629
"autoload": {

examples/readFile.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919

2020
$call = call('file_get_contents', [])
2121
->then(
22-
function (?int $result) {
23-
echo \sprintf("Wrote %d bytes\n", $result);
22+
function (?string $result) {
23+
echo \sprintf(
24+
"Read %d bytes\n",
25+
\strlen($result)
26+
);
2427
},
2528
function (\Throwable $err) {
2629
echo $err->getMessage() . PHP_EOL;

src/Async.php

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@
1414

1515
use React\EventLoop\LoopInterface;
1616
use React\Promise\PromiseInterface;
17+
use ReactParallel\EventLoop\EventLoopBridge;
18+
use ReactParallel\Runtime\Runtime;
1719

1820
use function Chemem\Asyncify\Internal\asyncify;
21+
use function Chemem\Asyncify\Internal\thread;
22+
use function Chemem\Asyncify\Internal\Functional\filepath;
23+
24+
use const Chemem\Asyncify\Internal\PHP_THREADABLE;
1925

2026
class Async
2127
{
@@ -33,10 +39,31 @@ class Async
3339
*/
3440
private $autoload;
3541

42+
/**
43+
* Runtime object
44+
*
45+
* @var Runtime $runtime
46+
*/
47+
private $runtime;
48+
3649
public function __construct(?string $autoload = null, ?LoopInterface $loop = null)
3750
{
3851
$this->loop = $loop;
3952
$this->autoload = $autoload;
53+
54+
if (PHP_THREADABLE) {
55+
$this->runtime = new Runtime(
56+
new EventLoopBridge($this->loop),
57+
$this->autoload ?? filepath(0, 'vendor/autoload.php')
58+
);
59+
}
60+
}
61+
62+
public function __destruct()
63+
{
64+
if (isset($this->runtime)) {
65+
$this->runtime->close();
66+
}
4067
}
4168

4269
/**
@@ -49,18 +76,20 @@ public function __construct(?string $autoload = null, ?LoopInterface $loop = nul
4976
* @param string $autoload
5077
* @return Async
5178
*/
52-
public static function create(?string $autoload = null, ?LoopInterface $loop = null): Async
53-
{
79+
public static function create(
80+
?string $autoload = null,
81+
?LoopInterface $loop = null
82+
): Async {
5483
return new static($autoload, $loop);
5584
}
5685

5786
/**
5887
* call
5988
* asynchronously calls a synchronous PHP function and subsumes result in promise
6089
*
61-
* call :: String -> Array -> Promise s a
90+
* call :: Sum String (a -> b) -> Array -> Promise s b
6291
*
63-
* @param string $function
92+
* @param string|callable $function
6493
* @param array $args
6594
* @return PromiseInterface
6695
* @example
@@ -75,11 +104,28 @@ public static function create(?string $autoload = null, ?LoopInterface $loop = n
75104
* function (Throwable $err) {
76105
* echo $err->getMessage() . PHP_EOL;
77106
* }
78-
* )
107+
* );
79108
* => file_get_contents(/path/to/file): Failed to open stream: No such file or directory
80109
*/
81-
public function call(string $function, array $args): PromiseInterface
110+
public function call($function, array $args): PromiseInterface
82111
{
83-
return asyncify($function, $args, $this->autoload, $this->loop);
112+
$params = [$function, $args];
113+
114+
return PHP_THREADABLE ?
115+
thread(
116+
...\array_merge(
117+
$params,
118+
[$this->runtime]
119+
)
120+
) :
121+
asyncify(
122+
...\array_merge(
123+
$params,
124+
[
125+
$this->autoload,
126+
$this->loop
127+
]
128+
)
129+
);
84130
}
85131
}

0 commit comments

Comments
 (0)