package Data::Log::Shared;
use strict;
use warnings;
our $VERSION = '0.01';
require XSLoader;
XSLoader::load('Data::Log::Shared', $VERSION);

sub each_entry {
    my ($self, $cb, $from) = @_;
    $from //= 0;
    while (1) {
        my ($data, $next) = $self->read_entry($from);
        last unless defined $data;
        $cb->($data, $from);
        $from = $next;
    }
    return $from;
}

1;
__END__

=encoding utf-8

=head1 NAME

Data::Log::Shared - Append-only shared-memory log (WAL) for Linux

=head1 SYNOPSIS

    use Data::Log::Shared;

    my $log = Data::Log::Shared->new(undef, 1_000_000);
    my $off = $log->append("first entry");
    $log->append("second entry");

    # replay from beginning
    my $pos = 0;
    while (my ($data, $next) = $log->read_entry($pos)) {
        say "offset=$pos: $data";
        $pos = $next;
    }

    # iterate
    $log->each_entry(sub { say $_[0] });

    # tail: block until new entries
    my $count = $log->entry_count;
    $log->wait_for($count, 5.0);

    # file-backed / memfd
    $log = Data::Log::Shared->new('/tmp/log.shm', 1_000_000);
    $log = Data::Log::Shared->new_memfd("my_log", 1_000_000);
    $log = Data::Log::Shared->new_from_fd($fd);

=head1 DESCRIPTION

Append-only log in shared memory. Multiple writers append variable-length
entries via CAS on a tail offset. Readers replay from any position.
Entries persist until explicit C<reset>.

Unlike L<Data::Queue::Shared> (consumed on read) and
L<Data::PubSub::Shared> (ring overwrites), the log retains all entries
until truncation. Useful for audit trails, event sourcing, debug logging.

B<Linux-only>. Requires 64-bit Perl.

=head1 METHODS

=head2 Append

    my $off = $log->append($data);  # returns offset, or undef if full

C<$data> must be non-empty (empty strings are rejected since len=0
is the internal uncommitted marker).

=head2 Read

    my ($data, $next_off) = $log->read_entry($offset);
    # returns () if no entry at offset (end of log or uncommitted)

    my $final_pos = $log->each_entry(sub {
        my ($data, $offset) = @_;
    });
    my $final_pos = $log->each_entry(\&cb, $start_offset);

=head2 Status

    my $off  = $log->tail_offset;   # byte offset past last entry
    my $n    = $log->entry_count;   # number of committed entries
    my $sz   = $log->data_size;     # total data region size
    my $free = $log->available;     # remaining bytes

=head2 Waiting

    my $ok = $log->wait_for($expected_count);           # block until count changes
    my $ok = $log->wait_for($expected_count, $timeout);  # with timeout
    my $ok = $log->wait_for($expected_count, 0);         # non-blocking poll

Returns 1 if new entries arrived (count != expected), 0 on timeout.

=head2 Lifecycle

    $log->reset;    # clear all entries (NOT concurrency-safe)
    $log->sync;     # msync to disk
    $log->unlink;   # remove backing file

=head2 Common

    my $p  = $log->path;
    my $fd = $log->memfd;
    my $s  = $log->stats;

=head2 eventfd

    my $fd = $log->eventfd;
    $log->eventfd_set($fd);
    my $fd = $log->fileno;
    $log->notify;
    my $n  = $log->eventfd_consume;

=head1 STATS

C<stats()> returns: C<data_size>, C<tail>, C<count>, C<available>,
C<waiters>, C<appends>, C<waits>, C<timeouts>, C<mmap_size>.

=head1 SECURITY

The mmap region is writable by all processes that open it.
Do not share backing files with untrusted processes.

=head1 BENCHMARKS

Single-process (1M ops, x86_64 Linux, Perl 5.40):

    append (12B entries)     8.9M/s
    append (200B entries)    8.0M/s
    read_entry sequential   4.1M/s

Multi-process (8 workers, 200K appends each):

    concurrent append       6.2M/s aggregate

=head1 SEE ALSO

L<Data::Queue::Shared> - FIFO queue (consumed on read)

L<Data::ReqRep::Shared> - request-reply

L<Data::PubSub::Shared> - publish-subscribe ring (overwrites)

L<Data::Stack::Shared> - LIFO stack

L<Data::Deque::Shared> - double-ended queue

L<Data::Pool::Shared> - fixed-size object pool

L<Data::Buffer::Shared> - typed shared array

L<Data::Sync::Shared> - synchronization primitives

L<Data::HashMap::Shared> - concurrent hash table

=head1 AUTHOR

vividsnow

=head1 LICENSE

Same terms as Perl itself.

=cut
