Skip to content

Commit 7c14347

Browse files
committed
Reintroduce the ext-eio adapter
During the initial rewrite this adapter was removed to make the rewrite easier. Since that is now in adding the ext-eio adapter will provide several high performance adapters.
1 parent 57d8f3a commit 7c14347

File tree

11 files changed

+512
-2
lines changed

11 files changed

+512
-2
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@ jobs:
2222
extensions:
2323
- ""
2424
- "uv-amphp/ext-uv@master"
25+
- "eio"
2526
steps:
2627
- uses: actions/checkout@v2
2728
- name: Install libuv
2829
if: matrix.os == 'ubuntu-latest'
2930
run: sudo apt-get install libuv1-dev
31+
- name: Install ext-eio
32+
if: matrix.os == 'ubuntu-latest' && matrix.extensions == 'eio'
33+
run: sudo pecl install eio || sudo pecl install eio-beta
3034
- uses: shivammathur/setup-php@v2
3135
with:
3236
php-version: ${{ matrix.php }}

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* [Factory](#factory)
1717
* [create()](#create)
1818
* [Filesystem implementations](#filesystem-implementations)
19+
* [Eio](#eio)
1920
* [Uv](#uv)
2021
* [AdapterInterface](#adapterinterface)
2122
* [detect()](#detect)
@@ -110,6 +111,15 @@ manually instantiate one of the following classes.
110111
Note that you may have to install the required PHP extensions for the respective
111112
event loop implementation first or they will throw a `BadMethodCallException` on creation.
112113

114+
#### Eio
115+
116+
An `ext-eio` based filesystem.
117+
118+
This filesystem uses the [`eio` PECL extension](https://pecl.php.net/package/eio), that
119+
provides an interface to `libeio` library.
120+
121+
This filesystem is known to work with PHP 7+.
122+
113123
#### Uv
114124

115125
An `ext-uv` based filesystem.

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
}
3535
},
3636
"suggest": {
37+
"ext-eio": "* for great I/O performance",
3738
"ext-uv": "* for better I/O performance"
3839
},
3940
"config": {

src/Eio/Adapter.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace React\Filesystem\Eio;
4+
5+
use React\EventLoop\ExtUvLoop;
6+
use React\EventLoop\Loop;
7+
use React\EventLoop\LoopInterface;
8+
use React\Filesystem\AdapterInterface;
9+
use React\Filesystem\ModeTypeDetector;
10+
use React\Filesystem\PollInterface;
11+
use React\Filesystem\Stat;
12+
use React\Promise\PromiseInterface;
13+
use React\Filesystem\Node;
14+
15+
final class Adapter implements AdapterInterface
16+
{
17+
use StatTrait;
18+
19+
private LoopInterface $loop;
20+
private PollInterface $poll;
21+
22+
public function __construct()
23+
{
24+
$this->loop = Loop::get();
25+
$this->poll = new Poll($this->loop);
26+
}
27+
28+
public function detect(string $path): PromiseInterface
29+
{
30+
return $this->internalStat($path)->then(function (?Stat $stat) use ($path) {
31+
if ($stat === null) {
32+
return new NotExist($this->poll, $this, $this->loop, dirname($path) . DIRECTORY_SEPARATOR, basename($path));
33+
}
34+
35+
switch (ModeTypeDetector::detect($stat->mode())) {
36+
case Node\FileInterface::class:
37+
return $this->file($stat->path());
38+
break;
39+
case Node\DirectoryInterface::class:
40+
return $this->directory($stat->path());
41+
break;
42+
default:
43+
return new Node\Unknown($stat->path(), $stat->path());
44+
break;
45+
}
46+
});
47+
}
48+
49+
public function directory(string $path): Node\DirectoryInterface
50+
{
51+
return new Directory($this->poll, $this, dirname($path) . DIRECTORY_SEPARATOR, basename($path));
52+
}
53+
54+
public function file(string $path): Node\FileInterface
55+
{
56+
return new File($this->poll, dirname($path) . DIRECTORY_SEPARATOR, basename($path));
57+
}
58+
59+
protected function activate(): void
60+
{
61+
$this->poll->activate();
62+
}
63+
64+
protected function deactivate(): void
65+
{
66+
$this->poll->deactivate();
67+
}
68+
}

src/Eio/Directory.php

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
namespace React\Filesystem\Eio;
4+
5+
use React\Filesystem\AdapterInterface;
6+
use React\Filesystem\Node;
7+
use React\Filesystem\PollInterface;
8+
use React\Promise\Deferred;
9+
use React\Promise\PromiseInterface;
10+
use function React\Promise\all;
11+
12+
final class Directory implements Node\DirectoryInterface
13+
{
14+
use StatTrait;
15+
16+
private PollInterface $poll;
17+
private AdapterInterface $filesystem;
18+
private string $path;
19+
private string $name;
20+
private int $workInProgress = 0;
21+
22+
public function __construct(PollInterface $poll, AdapterInterface $filesystem, string $path, string $name)
23+
{
24+
$this->poll = $poll;
25+
$this->filesystem = $filesystem;
26+
$this->path = $path;
27+
$this->name = $name;
28+
}
29+
30+
public function stat(): PromiseInterface
31+
{
32+
return $this->internalStat($this->path . $this->name);
33+
}
34+
35+
public function ls(): PromiseInterface
36+
{
37+
$this->activate();
38+
$deferred = new Deferred();
39+
eio_readdir($this->path . $this->name . DIRECTORY_SEPARATOR, \EIO_READDIR_STAT_ORDER | \EIO_READDIR_DIRS_FIRST, \EIO_PRI_DEFAULT, function ($_, $contents) use ($deferred): void {
40+
$list = [];
41+
foreach ($contents['dents'] as $node) {
42+
$fullPath = $this->path . $this->name . DIRECTORY_SEPARATOR . $node['name'];
43+
switch ($node['type'] ?? null) {
44+
case EIO_DT_DIR:
45+
$list[] = $this->filesystem->directory($fullPath);
46+
break;
47+
case EIO_DT_REG :
48+
$list[] = $this->filesystem->file($fullPath);
49+
break;
50+
default:
51+
$list[] = $this->filesystem->detect($this->path . $this->name . DIRECTORY_SEPARATOR . $node['name']);
52+
break;
53+
}
54+
}
55+
$this->deactivate();
56+
$deferred->resolve(all($list));
57+
});
58+
59+
return $deferred->promise();
60+
}
61+
62+
public function unlink(): PromiseInterface
63+
{
64+
$this->activate();
65+
$deferred = new Deferred();
66+
eio_readdir($this->path . $this->name . DIRECTORY_SEPARATOR, \EIO_READDIR_STAT_ORDER | \EIO_READDIR_DIRS_FIRST, \EIO_PRI_DEFAULT, function ($_, $contents) use ($deferred): void {
67+
$this->deactivate();
68+
if (count($contents['dents']) > 0) {
69+
$deferred->resolve(false);
70+
71+
return;
72+
}
73+
74+
$this->activate();
75+
eio_rmdir($this->path . $this->name, function () use ($deferred): void {
76+
$this->deactivate();
77+
$deferred->resolve(true);
78+
});
79+
});
80+
81+
82+
return $deferred->promise();
83+
}
84+
85+
public function path(): string
86+
{
87+
return $this->path;
88+
}
89+
90+
public function name(): string
91+
{
92+
return $this->name;
93+
}
94+
95+
protected function activate(): void
96+
{
97+
$this->poll->activate();
98+
}
99+
100+
protected function deactivate(): void
101+
{
102+
$this->poll->deactivate();
103+
}
104+
}

src/Eio/File.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
namespace React\Filesystem\Eio;
4+
5+
use React\EventLoop\ExtUvLoop;
6+
use React\Filesystem\Node\FileInterface;
7+
use React\Filesystem\PollInterface;
8+
use React\Promise\Promise;
9+
use React\Promise\PromiseInterface;
10+
use RuntimeException;
11+
use React\EventLoop\LoopInterface;
12+
use React\Filesystem\Node;
13+
use UV;
14+
15+
final class File implements FileInterface
16+
{
17+
use StatTrait;
18+
19+
private PollInterface $poll;
20+
private string $path;
21+
private string $name;
22+
23+
public function __construct(PollInterface $poll, string $path, string $name)
24+
{
25+
$this->poll = $poll;
26+
$this->path = $path;
27+
$this->name = $name;
28+
}
29+
30+
public function stat(): PromiseInterface
31+
{
32+
return $this->internalStat($this->path . $this->name);
33+
}
34+
35+
public function getContents(int $offset = 0 , ?int $maxlen = null): PromiseInterface
36+
{
37+
$this->activate();
38+
return new Promise(function (callable $resolve) use ($offset, $maxlen): void {
39+
\eio_open(
40+
$this->path . DIRECTORY_SEPARATOR . $this->name,
41+
\EIO_O_RDONLY,
42+
0,
43+
\EIO_PRI_DEFAULT,
44+
function ($_, $fileDescriptor) use ($resolve, $offset, $maxlen): void {
45+
\eio_fstat($fileDescriptor, \EIO_PRI_DEFAULT, function ($fileDescriptor, $stat) use ($resolve, $offset, $maxlen): void {
46+
\eio_read($fileDescriptor, $maxlen ?? (int)$stat['size'], $offset, \EIO_PRI_DEFAULT, function ($fileDescriptor, string $buffer) use ($resolve): void {
47+
\eio_close($fileDescriptor, \EIO_PRI_DEFAULT, function () use ($resolve, $buffer) {
48+
$this->deactivate();
49+
$resolve($buffer);
50+
});
51+
}, $fileDescriptor);
52+
}, $fileDescriptor);
53+
}
54+
);
55+
});
56+
}
57+
58+
public function putContents(string $contents, int $flags = 0)
59+
{
60+
$this->activate();
61+
return new Promise(function (callable $resolve) use ($contents, $flags): void {
62+
\eio_open(
63+
$this->path . DIRECTORY_SEPARATOR . $this->name,
64+
(($flags & \FILE_APPEND) == \FILE_APPEND) ? \EIO_O_RDWR | \EIO_O_APPEND : \EIO_O_RDWR | \EIO_O_CREAT,
65+
0644,
66+
\EIO_PRI_DEFAULT,
67+
function ($_, $fileDescriptor) use ($resolve, $contents, $flags): void {
68+
\eio_write($fileDescriptor, $contents, strlen($contents), 0, \EIO_PRI_DEFAULT, function ($fileDescriptor, int $bytesWritten) use ($resolve): void {
69+
\eio_close($fileDescriptor, \EIO_PRI_DEFAULT, function () use ($resolve, $bytesWritten): void {
70+
$this->deactivate();
71+
$resolve($bytesWritten);
72+
});
73+
}, $fileDescriptor);
74+
}
75+
);
76+
});
77+
}
78+
79+
public function unlink(): PromiseInterface
80+
{
81+
$this->activate();
82+
return new Promise(function (callable $resolve): void {
83+
\eio_unlink($this->path . DIRECTORY_SEPARATOR . $this->name, \EIO_PRI_DEFAULT, function () use ($resolve): void {
84+
$this->deactivate();
85+
$resolve(true);
86+
});
87+
});
88+
}
89+
90+
public function path(): string
91+
{
92+
return $this->path;
93+
}
94+
95+
public function name(): string
96+
{
97+
return $this->name;
98+
}
99+
100+
protected function activate(): void
101+
{
102+
$this->poll->activate();
103+
}
104+
105+
protected function deactivate(): void
106+
{
107+
$this->poll->deactivate();
108+
}
109+
}

0 commit comments

Comments
 (0)