DiscordPHP-BridgeBot Documentation

Filesystem
in package

FinalYes

Every disk access the bridge makes while the event loop is running, in one place, behind promises.

Why this exists

A blocking file_put_contents() stops the loop: for as long as it runs, no Discord heartbeat is sent, no Telegram update is read, nothing is relayed. Small as those pauses are here, they are real — this bot's own saves measure around 2-3 ms on a Windows host — and they are exactly the kind of thing that gets worse on the machine you did not test on.

So writes go through react/filesystem, which performs them off the loop when a native async backend is available.

What actually happens on each platform

react/filesystem picks a backend at runtime, and this matters more than it looks:

  • ext-eio (POSIX only) — genuinely asynchronous.
  • ext-uv (POSIX and Windows; php_uv.dll is published on PECL) — genuinely asynchronous, provided the process is running ReactPHP's ExtUvLoop, which it does automatically when the extension is loaded.
  • neither — react/filesystem falls back to an adapter that calls file_get_contents() and file_put_contents() and wraps the result in an already-resolved promise. It has the asynchronous shape and none of the asynchronous behaviour.

That last case is the common one on Windows, so this class does not pretend otherwise. When no async backend is present it does the write itself, blocking — and since it is going to block anyway, it blocks properly: fflush() and fsync(), so a machine that loses power cannot leave a zero-length file where the configuration was. putContents() cannot express that, and trading durability for a promise that resolves just as late would be a bad deal.

adapter() reports which of the three is in use, and the bot says so at startup, so nobody has to guess whether they are getting real async I/O.

What is not routed through here

rename(), mkdir() and is_file() are metadata operations measured in microseconds, and react/filesystem offers no asynchronous equivalent of the first two at all. They stay as direct calls, deliberately.

Tags
author

Valithor Obsidion valithor@discordphp.org

Table of Contents

Constants

ADAPTER_BLOCKING  : mixed = 'blocking'
ADAPTER_EIO  : mixed = 'ext-eio'
ADAPTER_UV  : mixed = 'ext-uv'

Properties

$adapter  : AdapterInterface|null
$name  : string

Methods

adapter()  : string
Which backend is in use: `ext-eio`, `ext-uv`, or `blocking`.
blocking()  : self
Durable, blocking I/O — what runs when no async backend is installed.
create()  : self
Picks the best backend this machine offers.
delete()  : PromiseInterface<string|int, bool>
Deletes a file. Resolves `true` when it is gone, including when it was never there.
describe()  : string
One line for the startup log, including what to do about it.
ensureDirectory()  : bool
Creates a directory and its parents if they are not there yet.
isAsynchronous()  : bool
Whether disk access actually happens off the event loop.
move()  : bool
Renames a file over another.
read()  : PromiseInterface<string|int, string|null>
The contents of a file, or `null` when it is not there or cannot be read.
readBlocking()  : string|null
Reads a file without involving the loop at all.
with()  : self
A specific adapter, for tests.
write()  : PromiseInterface<string|int, bool>
Writes a file, replacing whatever was there.
writeDurably()  : bool
Writes a file and waits for the disk to acknowledge it.
__construct()  : mixed

Constants

ADAPTER_BLOCKING

public mixed ADAPTER_BLOCKING = 'blocking'

ADAPTER_EIO

public mixed ADAPTER_EIO = 'ext-eio'

Properties

$adapter read-only

private AdapterInterface|null $adapter

Methods

adapter()

Which backend is in use: `ext-eio`, `ext-uv`, or `blocking`.

public adapter() : string
Return values
string

blocking()

Durable, blocking I/O — what runs when no async backend is installed.

public static blocking() : self
Return values
self

create()

Picks the best backend this machine offers.

public static create() : self

react/filesystem's own factory is asked first, and its answer is only kept when it is one of the two native backends: its fallback adapter is synchronous, and this class has a better synchronous path of its own.

Return values
self

delete()

Deletes a file. Resolves `true` when it is gone, including when it was never there.

public delete(string $path) : PromiseInterface<string|int, bool>
Parameters
$path : string
Return values
PromiseInterface<string|int, bool>

describe()

One line for the startup log, including what to do about it.

public describe() : string
Return values
string

ensureDirectory()

Creates a directory and its parents if they are not there yet.

public static ensureDirectory(string $path) : bool
Parameters
$path : string
Return values
bool

isAsynchronous()

Whether disk access actually happens off the event loop.

public isAsynchronous() : bool
Return values
bool

move()

Renames a file over another.

public static move(string $from, string $to) : bool

Stays a direct call on every backend: react/filesystem has no asynchronous rename, and this is a metadata operation that does not move bytes. On Windows PHP maps it to MoveFileEx with MOVEFILE_REPLACE_EXISTING, so replacing an existing target works there as it does on POSIX — which is what makes the atomic-save dance portable.

Parameters
$from : string
$to : string
Return values
bool

read()

The contents of a file, or `null` when it is not there or cannot be read.

public read(string $path) : PromiseInterface<string|int, string|null>

The existence check is deliberate rather than left to the adapter: the fallback adapter hands a missing path straight to file_get_contents(), which emits a warning and resolves with false instead of rejecting.

Parameters
$path : string
Return values
PromiseInterface<string|int, string|null>

readBlocking()

Reads a file without involving the loop at all.

public static readBlocking(string $path) : string|null

For the one moment when that is the right thing to do: loading configuration during construction, before run() has been called and before there is a loop to block.

Parameters
$path : string
Return values
string|null

with()

A specific adapter, for tests.

public static with(AdapterInterface $adapter[, string $name = 'custom' ]) : self

React\Filesystem\Fallback\Adapter is a real AdapterInterface that resolves immediately, which makes it an honest stand-in for the async path on a machine that has no async extension.

Parameters
$adapter : AdapterInterface
$name : string = 'custom'
Return values
self

write()

Writes a file, replacing whatever was there.

public write(string $path, string $contents) : PromiseInterface<string|int, bool>
Parameters
$path : string
$contents : string
Return values
PromiseInterface<string|int, bool> —

whether the contents reached the disk

writeDurably()

Writes a file and waits for the disk to acknowledge it.

public static writeDurably(string $path, string $contents) : bool

The fsync is the point: without it a rename can land while the content is still in the page cache, so a machine that loses power comes back to a zero-length file where the configuration used to be.

Parameters
$path : string
$contents : string
Return values
bool

__construct()

private __construct(AdapterInterface|null $adapter, string $name) : mixed
Parameters
$adapter : AdapterInterface|null
$name : string
On this page

Search results