NAME

PAGI::Util::AsyncFile - Non-blocking file I/O for PAGI applications

SYNOPSIS

use PAGI::Util::AsyncFile;

# Get the loop from PAGI scope
my $loop = $scope->{pagi}{loop};

# Read entire file
my $content = await PAGI::Util::AsyncFile->read_file($loop, '/path/to/file');

# Read file in chunks (streaming)
await PAGI::Util::AsyncFile->read_file_chunked($loop, '/path/to/file', async sub  {
    my ($chunk) = @_;
    # Process each chunk
    await $send->({ type => 'http.response.body', body => $chunk, more => 1 });
}, chunk_size => 65536);

# Write file
await PAGI::Util::AsyncFile->write_file($loop, '/path/to/file', $content);

# Append to file
await PAGI::Util::AsyncFile->append_file($loop, '/path/to/file', $log_line);

DESCRIPTION

This module provides non-blocking file I/O operations for use in PAGI async applications. It uses IO::Async::Function to offload blocking file operations to worker processes, preventing the main event loop from being blocked during disk I/O.

Regular file I/O in POSIX is always blocking at the kernel level - even select()/poll()/epoll() report regular files as always "ready". This module works around this limitation by running file operations in separate worker processes, similar to how Node.js/libuv handles file I/O.

CLASS METHODS

read_file

my $content = await PAGI::Util::AsyncFile->read_file($loop, $path);

Read the entire contents of a file asynchronously. Returns a Future that resolves to the file contents.

Parameters:

  • $loop - IO::Async::Loop instance

  • $path - Path to the file to read

Throws an exception if the file cannot be read.

read_file_chunked

await PAGI::Util::AsyncFile->read_file_chunked($loop, $path, async sub  {
    my ($chunk) = @_;
    # Process chunk
}, chunk_size => 65536);

# For Range requests (partial file):
await PAGI::Util::AsyncFile->read_file_chunked($loop, $path, $callback,
    offset => 1000,      # Start at byte 1000
    length => 5000,      # Read 5000 bytes total
);

Read a file in chunks, calling a callback for each chunk. This is suitable for streaming large files without loading the entire file into memory.

Parameters:

  • $loop - IO::Async::Loop instance

  • $path - Path to the file to read

  • $callback - Async callback called with each chunk. Receives the chunk data.

  • %opts - Options:

    • chunk_size - Size of each chunk in bytes (default: 65536)

    • offset - Byte offset to start reading from (default: 0)

    • length - Maximum bytes to read; omit to read to EOF

Returns a Future that resolves to the number of bytes read when complete. The callback should return/await properly if it needs to do async operations.

write_file

await PAGI::Util::AsyncFile->write_file($loop, $path, $content);

Write content to a file asynchronously, replacing any existing content.

Parameters:

  • $loop - IO::Async::Loop instance

  • $path - Path to the file to write

  • $content - Content to write

Returns a Future that resolves to the number of bytes written.

append_file

await PAGI::Util::AsyncFile->append_file($loop, $path, $content);

Append content to a file asynchronously.

Parameters:

  • $loop - IO::Async::Loop instance

  • $path - Path to the file

  • $content - Content to append

Returns a Future that resolves to the number of bytes written.

file_size

my $size = await PAGI::Util::AsyncFile->file_size($loop, $path);

Get the size of a file asynchronously.

file_exists

my $exists = await PAGI::Util::AsyncFile->file_exists($loop, $path);

Check if a file exists asynchronously.

cleanup

PAGI::Util::AsyncFile->cleanup($loop);

Clean up the worker pool for a given loop. Call this during application shutdown to properly terminate worker processes.

CONFIGURATION

The worker pool is configured with sensible defaults:

  • min_workers: 1 - Minimum worker processes to keep alive

  • max_workers: 4 - Maximum concurrent worker processes

  • idle_timeout: 30 - Seconds before idle workers are shut down

These settings balance responsiveness with resource usage. For applications with heavy file I/O, you may want to adjust these values by modifying the _get_function method or by configuring at the application level.

THREAD SAFETY

Each IO::Async::Loop gets its own worker pool. Worker processes are forked from the main process, so they inherit the initial state but operate independently. File operations in workers do not affect the main process state.

SEE ALSO

IO::Async::Function, IO::Async::Loop

AUTHOR

PAGI Contributors