Skip to content

Commit 0f7969e

Browse files
committed
[0.2.x] Improve file read/write tests
1 parent ca5e06f commit 0f7969e

File tree

9 files changed

+160
-64
lines changed

9 files changed

+160
-64
lines changed

src/Eio/Adapter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ final class Adapter implements AdapterInterface
2121
public function __construct()
2222
{
2323
$this->loop = Loop::get();
24-
$this->poll = new Poll($this->loop);
24+
$this->poll = new Poll();
2525
}
2626

2727
public function detect(string $path): PromiseInterface

src/Eio/Directory.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,15 @@ public function stat(): PromiseInterface
3434
public function ls(): PromiseInterface
3535
{
3636
$this->activate();
37-
return new Promise(function (callable $resolve): void {
38-
\eio_readdir($this->path . $this->name . DIRECTORY_SEPARATOR, \EIO_READDIR_STAT_ORDER | \EIO_READDIR_DIRS_FIRST, \EIO_PRI_DEFAULT, function ($_, $contents) use ($resolve): void {
37+
return new Promise(function (callable $resolve, callable $reject): void {
38+
\eio_readdir($this->path . $this->name . DIRECTORY_SEPARATOR, \EIO_READDIR_STAT_ORDER | \EIO_READDIR_DIRS_FIRST, \EIO_PRI_DEFAULT, function ($_, $contents, $resource) use ($resolve, $reject): void {
3939
$this->deactivate();
4040
$list = [];
41+
if ($contents === -1) {
42+
$reject(new \RuntimeException('Error reading from directory "' . $this->path . $this->name . DIRECTORY_SEPARATOR . '": ' . \eio_get_last_error($resource)));
43+
return;
44+
}
45+
4146
foreach ($contents['dents'] as $node) {
4247
$fullPath = $this->path . $this->name . DIRECTORY_SEPARATOR . $node['name'];
4348
switch ($node['type'] ?? null) {

src/Eio/File.php

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
final class File implements FileInterface
1212
{
13+
private const READ_CHUNK_FIZE = 65536;
14+
// private const READ_CHUNK_FIZE = 1;
15+
1316
use StatTrait;
1417

1518
private PollInterface $poll;
@@ -37,22 +40,37 @@ public function getContents(int $offset = 0 , ?int $maxlen = null): PromiseInter
3740
0,
3841
)->then(
3942
function ($fileDescriptor) use ($offset, $maxlen): PromiseInterface {
40-
if ($maxlen === null) {
41-
$sizePromise = $this->statFileDescriptor($fileDescriptor)->then(static function ($stat): int {
42-
return (int)$stat['size'];
43-
});
44-
} else {
45-
$sizePromise = resolve($maxlen);
46-
}
47-
return $sizePromise->then(function ($length) use ($fileDescriptor, $offset): PromiseInterface {
48-
return new Promise (function (callable $resolve) use ($fileDescriptor, $offset, $length): void {
49-
\eio_read($fileDescriptor, $length, $offset, \EIO_PRI_DEFAULT, function ($fileDescriptor, string $buffer) use ($resolve): void {
50-
$resolve($this->closeOpenFile($fileDescriptor)->then(function () use ($buffer): string {
51-
return $buffer;
52-
}));
53-
}, $fileDescriptor);
43+
$buffer = '';
44+
$bufferLength = 0;
45+
$read = function (bool $finalAttempt, int $offset) use ($fileDescriptor, $maxlen, &$read, &$buffer, &$bufferLength): PromiseInterface {
46+
return new Promise (function (callable $resolve) use ($fileDescriptor, $offset, $maxlen, $finalAttempt, &$read, &$buffer, &$bufferLength): void {
47+
\eio_read($fileDescriptor, $maxlen ?? self::READ_CHUNK_FIZE, $offset, \PHP_INT_MAX, function ($fileDescriptor, string $contents) use ($resolve, $maxlen, $finalAttempt, &$read, &$buffer, &$bufferLength): void {
48+
$contentLength = strlen($contents);
49+
$buffer .= $contents;
50+
$bufferLength += $contentLength;
51+
52+
if (
53+
($maxlen === null && $finalAttempt) ||
54+
($maxlen !== null && $bufferLength >= $maxlen)
55+
) {
56+
if ($maxlen !== null && $bufferLength > $maxlen) {
57+
$buffer = substr($buffer, 0, $maxlen);
58+
}
59+
60+
$resolve($this->closeOpenFile($fileDescriptor)->then(function () use ($buffer): string {
61+
$this->deactivate();
62+
return $buffer;
63+
}));
64+
} else if ($maxlen === null && !$finalAttempt && $contentLength === 0) {
65+
$resolve($read(true, $bufferLength));
66+
} else {
67+
$resolve($read(false, $bufferLength));
68+
}
69+
});
5470
});
55-
});
71+
};
72+
73+
return $read(false, $offset);
5674
}
5775
);
5876
}
@@ -61,9 +79,9 @@ public function putContents(string $contents, int $flags = 0)
6179
{
6280
$this->activate();
6381
return $this->openFile(
64-
$this->path . DIRECTORY_SEPARATOR . $this->name,
65-
(($flags & \FILE_APPEND) == \FILE_APPEND) ? \EIO_O_RDWR | \EIO_O_APPEND : \EIO_O_RDWR | \EIO_O_CREAT,
66-
0644
82+
$this->path . DIRECTORY_SEPARATOR . $this->name,
83+
(($flags & \FILE_APPEND) == \FILE_APPEND) ? \EIO_O_RDWR | \EIO_O_APPEND : \EIO_O_RDWR | \EIO_O_CREAT,
84+
0644
6785
)->then(
6886
function ($fileDescriptor) use ($contents, $flags): PromiseInterface {
6987
return new Promise (function (callable $resolve) use ($contents, $fileDescriptor): void {
@@ -77,15 +95,6 @@ function ($fileDescriptor) use ($contents, $flags): PromiseInterface {
7795
);
7896
}
7997

80-
private function statFileDescriptor($fileDescriptor): PromiseInterface
81-
{
82-
return new Promise(function (callable $resolve, callable $reject) use ($fileDescriptor) {
83-
\eio_fstat($fileDescriptor, \EIO_PRI_DEFAULT, function ($_, $stat) use ($resolve): void {
84-
$resolve($stat);
85-
}, $fileDescriptor);
86-
});
87-
}
88-
8998
private function openFile(string $path, int $flags, int $mode): PromiseInterface
9099
{
91100
return new Promise(function (callable $resolve, callable $reject) use ($path, $flags, $mode): void {

src/Eio/Poll.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@
22

33
namespace React\Filesystem\Eio;
44

5-
use React\EventLoop\LoopInterface;
5+
use React\EventLoop\Loop;
66
use React\Filesystem\PollInterface;
77

8+
/**
9+
* @internal
10+
*/
811
final class Poll implements PollInterface
912
{
10-
private LoopInterface $loop;
1113
private $fd;
1214
private \Closure $handleEvent;
1315
private int $workInProgress = 0;
1416

15-
public function __construct(LoopInterface $loop)
17+
public function __construct()
1618
{
1719
$this->fd = EventStream::get();
18-
$this->loop = $loop;
1920
$this->handleEvent = function () {
2021
$this->handleEvent();
2122
};
@@ -24,7 +25,7 @@ public function __construct(LoopInterface $loop)
2425
public function activate(): void
2526
{
2627
if ($this->workInProgress++ === 0) {
27-
$this->loop->addReadStream($this->fd, $this->handleEvent);
28+
Loop::addReadStream($this->fd, $this->handleEvent);
2829
}
2930
}
3031

@@ -38,7 +39,7 @@ private function handleEvent()
3839
public function deactivate(): void
3940
{
4041
if (--$this->workInProgress <= 0) {
41-
$this->loop->removeReadStream($this->fd);
42+
Loop::removeReadStream($this->fd);
4243
}
4344
}
4445
}

src/Fallback/File.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public function stat(): PromiseInterface
2929
public function getContents(int $offset = 0 , ?int $maxlen = null): PromiseInterface
3030
{
3131
$path = $this->path . $this->name;
32-
return resolve(file_get_contents($path, false, null, $offset, $maxlen ?? (int)stat($path)['size']));
32+
return resolve(file_get_contents($path, false, null, $offset, $maxlen));
3333
}
3434

3535
public function putContents(string $contents, int $flags = 0): PromiseInterface

src/Uv/File.php

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace React\Filesystem\Uv;
44

55
use React\EventLoop\ExtUvLoop;
6+
use React\EventLoop\Loop;
67
use React\Filesystem\Node\FileInterface;
78
use React\Filesystem\PollInterface;
89
use React\Promise\Promise;
@@ -11,6 +12,9 @@
1112

1213
final class File implements FileInterface
1314
{
15+
// private const READ_CHUNK_FIZE = 65536;
16+
private const READ_CHUNK_FIZE = 1;
17+
1418
use StatTrait;
1519

1620
private ExtUvLoop $loop;
@@ -36,41 +40,102 @@ public function stat(): PromiseInterface
3640
public function getContents(int $offset = 0 , ?int $maxlen = null): PromiseInterface
3741
{
3842
$this->activate();
39-
return new Promise(function (callable $resolve) use ($offset, $maxlen): void {
40-
uv_fs_open($this->uvLoop, $this->path . DIRECTORY_SEPARATOR . $this->name, UV::O_RDONLY, 0, function ($fileDescriptor) use ($resolve, $offset, $maxlen): void {
41-
uv_fs_fstat($this->uvLoop, $fileDescriptor, function ($fileDescriptor, array $stat) use ($resolve, $offset, $maxlen): void {
42-
uv_fs_read($this->uvLoop, $fileDescriptor, $offset, $maxlen ?? (int)$stat['size'], function ($fileDescriptor, string $buffer) use ($resolve): void {
43-
$resolve($buffer);
44-
uv_fs_close($this->uvLoop, $fileDescriptor, function () {
45-
$this->deactivate();
43+
return $this->openFile(
44+
$this->path . DIRECTORY_SEPARATOR . $this->name,
45+
UV::O_RDONLY,
46+
0,
47+
)->then(
48+
function ($fileDescriptor) use ($offset, $maxlen): PromiseInterface {
49+
$buffer = '';
50+
$bufferLength = 0;
51+
$read = function (bool $finalAttempt, int $offset) use ($fileDescriptor, $maxlen, &$read, &$buffer, &$bufferLength): PromiseInterface {
52+
return new Promise (function (callable $resolve) use ($fileDescriptor, $offset, $maxlen, $finalAttempt, &$read, &$buffer, &$bufferLength): void {
53+
\uv_fs_read($this->uvLoop, $fileDescriptor, $offset, $maxlen ?? self::READ_CHUNK_FIZE, function ($fileDescriptor, string $contents) use ($resolve, $maxlen, $finalAttempt, &$read, &$buffer, &$bufferLength): void {
54+
$contentLength = strlen($contents);
55+
$buffer .= $contents;
56+
$bufferLength += $contentLength;
57+
58+
if (
59+
($maxlen === null && $finalAttempt) ||
60+
($maxlen !== null && $bufferLength >= $maxlen)
61+
) {
62+
if ($maxlen !== null && $bufferLength > $maxlen) {
63+
$buffer = substr($buffer, 0, $maxlen);
64+
}
65+
66+
$resolve($this->closeOpenFile($fileDescriptor)->then(function () use ($buffer): string {
67+
$this->deactivate();
68+
69+
return $buffer;
70+
}));
71+
} else if ($maxlen === null && !$finalAttempt && $contentLength === 0) {
72+
$resolve($read(true, $bufferLength));
73+
} else {
74+
$resolve($read(false, $bufferLength));
75+
}
4676
});
4777
});
48-
});
49-
});
50-
});
78+
};
79+
80+
return $read(false, $offset);
81+
}
82+
);
5183
}
5284

5385
public function putContents(string $contents, int $flags = 0)
5486
{
5587
$this->activate();
56-
return new Promise(function (callable $resolve) use ($contents, $flags): void {
57-
uv_fs_open(
58-
$this->uvLoop,
59-
$this->path . DIRECTORY_SEPARATOR . $this->name,
60-
(($flags & \FILE_APPEND) == \FILE_APPEND) ? UV::O_RDWR | UV::O_CREAT | UV::O_APPEND : UV::O_RDWR | UV::O_CREAT,
61-
0644,
62-
function ($fileDescriptor) use ($resolve, $contents, $flags): void {
88+
return $this->openFile(
89+
$this->path . DIRECTORY_SEPARATOR . $this->name,
90+
(($flags & \FILE_APPEND) == \FILE_APPEND) ? UV::O_RDWR | UV::O_CREAT | UV::O_APPEND : UV::O_RDWR | UV::O_CREAT,
91+
0644,
92+
)->then(
93+
function ($fileDescriptor) use ($contents): PromiseInterface {
94+
return new Promise (function (callable $resolve) use ($contents, $fileDescriptor): void {
6395
uv_fs_write($this->uvLoop, $fileDescriptor, $contents, 0, function ($fileDescriptor, int $bytesWritten) use ($resolve): void {
64-
$resolve($bytesWritten);
65-
uv_fs_close($this->uvLoop, $fileDescriptor, function () {
96+
$resolve($this->closeOpenFile($fileDescriptor)->then(function () use ($bytesWritten): int {
6697
$this->deactivate();
67-
});
98+
return $bytesWritten;
99+
}));
68100
});
69101
}
70102
);
71103
});
72104
}
73105

106+
private function openFile(string $path, int $flags, int $mode): PromiseInterface
107+
{
108+
$this->activate();
109+
return new Promise(function (callable $resolve) use ($path, $flags, $mode): void {
110+
uv_fs_open(
111+
$this->uvLoop,
112+
$this->path . DIRECTORY_SEPARATOR . $this->name,
113+
$flags,
114+
$mode,
115+
function ($fileDescriptor) use ($resolve): void {
116+
$this->deactivate();
117+
$resolve($fileDescriptor);
118+
}
119+
);
120+
});
121+
}
122+
123+
private function closeOpenFile($fileDescriptor): PromiseInterface
124+
{
125+
$this->activate();
126+
return new Promise(function (callable $resolve) use ($fileDescriptor) {
127+
try {
128+
uv_fs_close($this->uvLoop, $fileDescriptor, function () use ($resolve) {
129+
$this->deactivate();
130+
$resolve();
131+
});
132+
} catch (\Throwable $error) {
133+
$this->deactivate();
134+
throw $error;
135+
}
136+
});
137+
}
138+
74139
public function unlink(): PromiseInterface
75140
{
76141
$this->activate();

tests/AbstractFilesystemTestCase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ final public function provideFilesystems(): iterable
3131
yield 'uv' => [new Uv\Adapter()];
3232
}
3333

34-
yield 'factory' => [Factory::create()];
34+
// yield 'factory' => [Factory::create()];
3535
}
3636
}

tests/DirectoryTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace React\Tests\Filesystem;
44

5-
use React\EventLoop\LoopInterface;
65
use React\Filesystem\AdapterInterface;
76
use React\Filesystem\Node\DirectoryInterface;
87
use React\Filesystem\Node\FileInterface;

0 commit comments

Comments
 (0)