Skip to content

Commit

Permalink
Merge pull request #2 from Innmind/next
Browse files Browse the repository at this point in the history
Next major version
  • Loading branch information
Baptouuuu committed Mar 10, 2024
2 parents 0fe4a1b + 3eec172 commit 92dbff6
Show file tree
Hide file tree
Showing 29 changed files with 316 additions and 142 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## [Unreleased]

### Added

- `Innmind\OperatingSystem\Filesystem::temporary()`

### Changed

- `Innmind\OperatingSystem\Remote::socket()` returned socket is now wrapped in a `Innmind\IO\Sockets\Client`
- `Innmind\OperatingSystem\Sockets::connectTo()` returned socket is now wrapped in a `Innmind\IO\Sockets\Client`
- `Innmind\OperatingSystem\Sockets::open()` returned socket is now wrapped in a `Innmind\IO\Sockets\Server`
- `Innmind\OperatingSystem\Sockets::takeOver()` returned socket is now wrapped in a `Innmind\IO\Sockets\Server`
- `Innmind\OperatingSystem\Ports::open()` returned socket is now wrapped in a `Innmind\IO\Sockets\Server`
- `Innmind\OperatingSystem\CurrentProcess\Generic::of()` is now declared `internal`
- `Innmind\OperatingSystem\Filesystem\Generic::of()` is now declared `internal`
- `Innmind\OperatingSystem\Ports\Unix::of()` is now declared `internal`
- `Innmind\OperatingSystem\Remote\Generic::of()` is now declared `internal`
- `Innmind\OperatingSystem\Ports\Sockets::of()` is now declared `internal`
- Requires `innmind/file-watch:~4.0`

## 4.2.0 - 2023-12-14

### Added
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ $server = $os
Port::of(1337),
)
->match(
static fn($server) => $server,
static fn($server) => $server->unwrap(),
static fn() => throw new \RuntimeException('Cannot open the socket'),
);
```
Expand All @@ -84,7 +84,7 @@ $server = $os
use Innmind\Socket\Address\Unix;

$server = $os->sockets()->open(Unix::of('/tmp/foo.sock'))->match(
static fn($server) => $server,
static fn($server) => $server->unwrap(),
static fn() => throw new \RuntimeException('Cannot open the socket'),
);
```
Expand All @@ -95,7 +95,10 @@ $server = $os->sockets()->open(Unix::of('/tmp/foo.sock'))->match(
# process B
use Innmind\Socket\Address\Unix;

$client = $os->sockets()->connectTo(Unix::of('/tmp/foo.sock'));
$client = $os->sockets()->connectTo(Unix::of('/tmp/foo.sock'))->match(
static fn($client) => $client->unwrap(),
static fn() => throw new \RuntimeException('Cannot connect to the socket'),
);
```

`$client` is an instance of `Innmind\Socket\Client`.
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
"innmind/http-transport": "~7.2",
"innmind/time-warp": "~3.0",
"innmind/signals": "~3.0",
"innmind/file-watch": "~3.1",
"innmind/file-watch": "~4.0",
"innmind/stream": "~4.0",
"formal/access-layer": "^2.0",
"innmind/io": "~2.2"
"innmind/io": "~2.7"
},
"autoload": {
"psr-4": {
Expand Down
15 changes: 7 additions & 8 deletions documentation/use_cases/filesystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,24 +112,23 @@ It is a great way to forget about where the tmp folder is located and simply foc
A pattern we don't see much in PHP is an infinite loop to react to an event to perform another task. Here we can build such pattern by watching for changes in a file or a directory.

```php
use Innmind\FileWatch\Stop;
use Innmind\Immutable\Either;
use Innmind\FileWatch\Continuation;

$runTests = $os->filesystem()->watch(Path::of('/path/to/project/src/'));

$count = $runTests(0, function(int $count) use ($os): Either {
$count = $runTests(0, function(int $count, Continuation $continuation) use ($os): Continuation {
if ($count === 42) {
return Either::left(Stop::of($count));
return $continuation->stop($count);
}

$os->control()->processes()->execute($phpunitCommand);

return Either::right(++$count);
return $continuation->continue(++$count);
});
```

Here it will run phpunit tests every time the `src/` folder changes. Concrete examples of this pattern can be found in [`innmind/lab-station`](https://github.com/Innmind/LabStation/blob/develop/src/Agent/WatchSources.php#L38) to run a suite of tools when sources change or in [`halsey/journal`](https://github.com/halsey-php/journal/blob/develop/src/Command/Preview.php#L58) to rebuild the website when the markdown files change.
Here it will run phpunit tests every time the `src/` folder changes. Concrete examples of this pattern can be found in [`innmind/lab-station`](https://github.com/Innmind/LabStation/blob/develop/src/Agent/WatchSources.php#L38) to run a suite of tools when sources change.

This operation is a bit like an `array_reduce` as you can keep a state record between each calls of the callable via the first argument (here `0`, but it can be anything) and the argument of your callable will be the previous value returned by `Either::right()`.
This operation is a bit like an `array_reduce` as you can keep a state record between each calls of the callable via the first argument (here `0`, but it can be anything) and the argument of your callable will be the previous value returned by `$continuation->continue()`.

**Important**: since there is not builtin way to watch for changes in a directory it checks the directory every second, so use it with care. Watching an individual file is a bit safer as it uses the `tail` command so there is no `sleep()` used.
**Important**: since there is no builtin way to watch for changes in a directory it checks the directory every second, so use it with care. Watching an individual file is a bit safer as it uses the `tail` command so there is no `sleep()` used.
48 changes: 24 additions & 24 deletions documentation/use_cases/ipc.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ The later is the safest of the two (but not exempt of problems) and you will fin
# process acting as a server
use Innmind\Socket\Address\Unix as Address;
use Innmind\TimeContinuum\Earth\ElapsedPeriod;
use Innmind\Immutable\Str;
use Innmind\Immutable\{
Sequence,
Str,
};

$server = $os->sockets()->open(Address::of('/tmp/foo'))->match(
static fn($server) => $server,
Expand All @@ -19,45 +22,42 @@ $server = $os->sockets()->open(Address::of('/tmp/foo'))->match(
$watch = $os->sockets()->watch(new ElapsedPeriod(1000))->forRead($server);

while (true) {
$watch()
->flatMap(static fn($ready) => $ready->toRead()->find(static fn($socket) => $socket === $stream))
->flatMap(static fn($server) => $server->accept())
$_ = $server
->timeoutAfter(ElapsedPeriod::of(1_000))
->accept()
->match(
static fn($client) => $client
->write(Str::of('Hello 👋'))
->flatMap(static fn($client) => $client->close())
->send(Sequence::of(Str::of('Hello')))
->flatMap(static fn() => $client->close())
->match(
static fn() => null, // everyhting is ok
static fn() => throw new \RuntimeException('Unable to send data or close the connection'),
),
static fn() => null, // no new connection available
);
),
}
```

```php
# process acting as client
use Innmind\Socket\Address\Unix as Address;
use Innmind\TimeContinuum\Earth\ElapsedPeriod;

$client = $os->sockets()->connectTo(Address::of('/tmp/foo'));
$watch = $os->sockets()->watch(new ElapsedPeriod(1000))->forRead($client);
use Innmind\IO\Readable\Frame;

do {
$ready = $watch()
->flatMap(static fn($ready) => $ready->toRead()->find(static fn($ready) => $ready === $client))
->match(
static fn() => true,
static fn() => false,
);
} while (!$ready);

echo $client->read()->match(
static fn($data) => $data->toString(),
static fn() => 'unable to read the stream',
$client = $os->sockets()->connectTo(Address::of('/tmp/foo'))->match(
static fn($client) => $client,
static fn() => throw new \RuntimeException('Unable to connect to the server'),
);

echo $client
->watch()
->frames(Frame\Chunk::of(5))
->one()
->match(
static fn($data) => $data->toString(),
static fn() => 'unable to read the stream',
);
```

In the case the server is started first then the client would print `Hello 👋`.
In the case the server is started first then the client would print `Hello`.

**Important**: this is a very rough implementation of communication between processes. **DO NOT** use this simple implementation in your code, instead use a higher level API such as [`innmind/ipc`](https://github.com/innmind/ipc).
41 changes: 26 additions & 15 deletions documentation/use_cases/signals.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,52 @@

Any process can receive [signals](https://en.wikipedia.org/wiki/Signal_(IPC)) either through user interaction (in a terminal), from another process or via the `kill` command. PHP processes can handle them and perform actions to safely free resources or prevent the process from being terminated.

Examples below only use one listener per signal but you can add as many as you want (which is complicated when dealing manually with PHP builtin functions).
Examples below only use one listener per signal but you can add as many as you wish (which is complicated when dealing manually with PHP builtin functions).

## Free resources before stopping

This is a reuse of the [socket example](socket.md).

```php
use Innmind\Url\Url;
use Innmind\IO\Readable\Frame;
use Innmind\Socket\Internet\Transport;
use Innmind\TimeContinuum\Earth\ElapsedPeriod;
use Innmind\Signals\Signal;
use Innmind\Immutable\{
Sequence,
Str,
};

$client = $os->remote()->socket(Transport::tcp(), Ur::of('tcp://127.0.0.1:8080')->authority())->match(
static fn($client) => $client,
static fn() => throw new \RuntimeException('Unable to connect to the server'),
);
$watch = $os->sockets()->watch(new ElapsedPeriod(1000))->forRead($client);
$continue = true;
$os->process()->signals()->listen(Signal::terminate, function() use (&$continue, $client) {
$continue = false;
$client->close();
$signaled = true;
$os->process()->signals()->listen(Signal::terminate, function() use (&$signaled) {
$signaled = false;
});

do {
$ready = $watch()
->flatMap(static fn($ready) => $ready->toRead()->find(static fn($ready) => $ready === $client))
->match(
static fn() => true,
static fn() => false,
);
} while ($continue && !$ready);

if (!$client->closed()) {
$receivedData = $client
->timeoutAfter(ElapsedPeriod::of(1_000))
// it sends this every second to keep the connection alive
->heartbeatWith(static fn() => Sequence::of(Str::of('foo')))
->abortWhen(function() use (&$signaled) {
return $signaled;
})
->frames(Frame\Chunk::of(1))
->one()
->match(
static fn() => true,
static fn() => false,
);

if ($receivedData) {
echo 'Server has responded'.
}

$client->unwrap()->close();
```

When the process receive the `SIGTERM` signal it will be paused then the anonymous function will be called and the process will then be resumed.
Expand Down
32 changes: 16 additions & 16 deletions documentation/use_cases/socket.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ $server = $os->ports()->open(Transport::tcp(), IPv4::localhost(), Port::of(8080)
static fn($server) => $server,
static fn() => throw new \RuntimeException('Unable to start the server'),
);
$watch = $os->sockets()->watch(new ElapsedPeriod(1000))->forRead($server);

while (true) {
$watch()
->flatMap(static fn($ready) => $ready->toRead()->find(static fn($ready) => $ready === $server))
->flatMap(static fn($server) => $server->accept())
$server
->timeoutAfter(ElapsedPeriod::of(1_000))
->accept()
->match(
static fn($client) => /* talk to the client */,
static fn() => null, // no client available yet
Expand All @@ -42,23 +41,24 @@ This example will open a connection to the server defined above but can be chang

```php
use Innmind\Url\Url;
use Innmind\IO\Readable\Frame;
use Innmind\Socket\Internet\Transport;
use Innmind\TimeContinuum\Earth\ElapsedPeriod;

$client = $os->remote()->socket(Transport::tcp(), Ur::of('tcp://127.0.0.1:8080')->authority())->match(
$client = $os->remote()->socket(Transport::tcp(), Url::of('tcp://127.0.0.1:8080')->authority())->match(
static fn($client) => $client,
static fn() => throw new \RuntimeException('Unable to connect to the client'),
);
$watch = $os->sockets()->watch(new ElapsedPeriod(1000))->forRead($client);

do {
$ready = $watch()
->flatMap(static fn($ready) => $ready->toRead()->find(static fn($ready) => $ready === $client))
->match(
static fn() => true,
static fn() => false,
);
} while (!$ready);
$receivedData = $client
->watch()
->frames(Frame\Chunk::of(1))
->one()
->match(
static fn() => true,
static fn() => false,
);

echo 'Server has responded'.
if ($receivedData) {
echo 'Server has responded'.
}
```
5 changes: 4 additions & 1 deletion src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ public static function of(): self
default => $streams->watch()->timeoutAfter($timeout),
}),
new Halt\Usleep,
EnvironmentPath::of(\getenv('PATH') ?: ''),
EnvironmentPath::of(match ($path = \getenv('PATH')) {
false => '',
default => $path,
}),
$maxHttpConcurrency,
$httpHeartbeat,
false,
Expand Down
8 changes: 3 additions & 5 deletions src/CurrentProcess/Generic.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@
use Innmind\TimeContinuum\Period;
use Innmind\TimeWarp\Halt;
use Innmind\Signals\Handler;
use Innmind\Immutable\{
Set,
Either,
SideEffect,
};

final class Generic implements CurrentProcess
{
Expand All @@ -25,6 +20,9 @@ private function __construct(Halt $halt)
$this->halt = $halt;
}

/**
* @internal
*/
public static function of(Halt $halt): self
{
return new self($halt);
Expand Down
5 changes: 0 additions & 5 deletions src/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@

namespace Innmind\OperatingSystem;

use Innmind\TimeContinuum\{
Clock,
Earth,
};

final class Factory
{
public static function build(Config $config = null): OperatingSystem
Expand Down
25 changes: 23 additions & 2 deletions src/Filesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@

namespace Innmind\OperatingSystem;

use Innmind\Filesystem\Adapter;
use Innmind\Filesystem\{
Adapter,
File\Content,
};
use Innmind\Url\Path;
use Innmind\FileWatch\Ping;
use Innmind\Immutable\Maybe;
use Innmind\Immutable\{
Maybe,
Str,
Sequence,
};

interface Filesystem
{
Expand All @@ -18,4 +25,18 @@ public function contains(Path $path): bool;
*/
public function require(Path $path): Maybe;
public function watch(Path $path): Ping;

/**
* This method is to be used to generate a temporary file content even if it
* doesn't fit in memory.
*
* Usually the sequence of chunks comes from reading a socket meaning it
* can't be read twice. By using this temporary file content you can read it
* multiple times.
*
* @param Sequence<Maybe<Str>> $chunks
*
* @return Maybe<Content>
*/
public function temporary(Sequence $chunks): Maybe;
}

0 comments on commit 92dbff6

Please sign in to comment.