=encoding utf-8

=head1 NAME

Module::Generic::SharedMem - Shared Memory Manipulation

=head1 SYNOPSIS

    # Check if IPC::SysV is supported on this system
    if( Module::Generic::SharedMem->supported )
    {
        my $shmem = Module::Generic::SharedMem->new( key => 'some_identifier' ) ||
            die( Module::Generic::SharedMem->error );
    }

    my $shmem = Module::Generic::SharedMem->new(
        # Create if necessary, or re-use if already exists
        create => 1,
        # Self-destroy upon end of object. Default to false
        destroy => 0,
        # make access exclusive
        exclusive => 1,
        key => 'some_identifier',
        mode => 0666,
        # 100K
        size => 102400,
        debug => 3,
    ) || die( Module::Generic::SharedMem->error );

    # Check if it already exists
    if( $shmem->exists )
    {
        # do something
    }

    $shmem->create(0);
    $shmem->destroy(0);
    $shmem->exclusive(0);
    # Then get the bitwise flags based on those options set above:
    my $flags = $shmem->flags;
    # or specify overriding values:
    my $flags = $shmem->flags({
        create => 0,
        destroy => 0,
        exclusive => 0,
        mode => 0644,
    });

    my $s = $shmem->open || die( $shmem->error );

    # Get the shared memory id
    my $id = $s->id;

    my $key = $s->key;

    # Get the actual key used in interacting with shared memory
    # You should not mess with this unless you know what you are doing
    my $shem_key = $s->serial;

    use Module::Generic::SharedMem qw( :all );
    $s->lock( LOCK_EX ) || die( $s->error );
    # Is it locked?
    my $is_locked = $s->locked;

    # example: 0666
    my $mode = $s->mode;
    my $s = $shmem->open || die( $shmem->error );

    # Actually the process pid
    my $owner = $s->owner;

    # The semaphore pid
    my $sempid = $s->pid;

    # Get a random key to use to create shared memory block
    my $random_key = $shmem->rand;

    my $data = $s->read;
    my $buffer;
    $s->read( $buffer );
    # You can control how much to read and allocate a buffer to put the read data onto
    # Data is automatically transcoded using Storable::Improved::thaw
    my $len = $s->read( $buffer, 1024 ) || die( $s->error );

    $s->remove;

    my $semaphore_id = $s->semid;

    # or $s->size;
    my $shared_mem_size = $shmem->size;

    # See Module::Generic::SemStat doc
    my $stat = $s->stat;

    # See Module::Generic::SharedStat doc
    my $stat = $s->shmstat;

    # Remove lock
    $s->unlock;

    # Data is automatically transcoded using Storable::Improved::freeze
    $s->write( $data ) || die( $s->error );

=head1 VERSION

    v0.5.4

=head1 DESCRIPTION

L<Module::Generic::SharedMem> provides an easy to use api to manipulate shared memory block. See L<perlipc> for more information.

As stipulated in L<perlport>, this is not supported on the following platforms: android, dos, MSWin32, OS2, VMS and Risc OS.

You can check if the system is supported with L</supported>

    if( Module::Generic::SharedMem->supported )
    {
        # do something
    }

This module only works with reference data, such as array, hash or reference to scalar. Anything that L<CBOR::XS>, L<Sereal>. or L<Storable::Improved> knows how to L<Storable::Improved/freeze> and L<Storable::Improved/thaw>

=head1 DEBUGGING

To list all used shared memory, at least on Unix type systems such as Linux or FreeBSD (including MacOSX), use:

    ipcs -m

=head1 METHODS

=head2 new

This instantiates a shared memory object. It takes the following parameters:

=over 4

=item * C<cbor>

Provided with a value (true or false does not matter), and this will set L<CBOR::XS> as the data serialisation mechanism when storing data to memory or reading data from memory.

=item * C<debug>

A debug value will enable debugging output (equal or above 3 actually)

=item * C<create>

A boolean value to indicate whether the shared memory block should be created if it does not exist. Default to false.

=item * C<destroy>

A boolean value to indicate if the shared memory block should be removed when the object is destroyed upon end of the script process.
See L<perlmod> for more about object destruction.

=item * C<destroy_semaphore>

A boolean value to indicate if the semaphore should be removed when the object is destroyed upon end of the script process.
See L<perlmod> for more about object destruction.

C<destroy_semaphore> is automatically enabled if C<destroy> is set to true.

Thus, one can deactive auto removal of the shared memory block, but enable auto removal of the semaphore. This is useful when there are two processes accessing the same shared memory block and one wants to give the first process the authority to create and remove the shared memory block, while the second only access and write to the shared memory block, but does not remove it. Still to avoid having semaphores surviving the process, by enabling this option and disabling C<destroy>, it will remove the semaphore and leave the shared memory.

=item * C<exclusive>

A boolean value to set the shared memory as exclusive. This will affect the flags set by L</flags> which are used by L</open>.

=item * C<json>

Provided with a value (true or false does not matter), and this will set L<JSON> as the data serialisation mechanism when storing data to memory or reading data from memory.

=item * C<key>

The shared memory key identifier to use. It defaults to C<IPC::SysV::IPC_PRIVATE>

If you provide an empty value, it will revert to C<IPC::SysV::IPC_PRIVATE>.

If you provide a number, it will be used to call L<IPC::SysV/ftok>.

Otherwise, if you provide a key as string, the characters in the string will be converted to their numeric value and added up. The resulting id, called C<project id> by L<IPC::SysV>, will be used to call L<IPC::SysV/ftok> and will produce an hopefully unique and repeatable value.

Either way, the resulting value is used to create a shared memory segment and a semaphore by L</open>.

=item * C<mode>

The octal mode value to use when opening the shared memory block.

Shared memory are owned by system users and access to shared memory segment is ruled by the initial permissions set to it.

If you do not want to share it with any other user than yourself, setting mode to C<0600> is fine.

=item * C<sereal>

Provided with a value (true or false does not matter), and this will set L<Sereal> as the data serialisation mechanism when storing data to memory or reading data from memory.

=item * C<serialiser>

You can provide the serialiser with this option. Possible values are: C<cbor>, C<json>, C<sereal>, C<storable>

=item * C<size>

The size in byte of the shared memory.

This is set once it is created. You can create again the shared memory segment with a smaller size, but not a bigger one. If you want to increase the size, you would need to remove it first.

=item * C<storable>

Provided with a value (true or false does not matter), and this will set L<Storable::Improved> as the data serialisation mechanism when storing data to memory or reading data from memory.

=back

An object will be returned if it successfully initiated, or undef() upon error, which can then be retrieved with C< Module::Generic::SharedMem->error >. You should always check the return value of the methods used here for their definedness.

    my $shmem = Module::Generic::SharedMem->new(
        create => 1,
        destroy => 0,
        key => 'my_memory',
        # 64K
        size => 65536,
    ) || die( Module::Generic::SharedMem->error );

=head2 addr

Returns the address of the shared memory segment once it has been attached to this address space.

=head2 attach

Attach the shared memory segment to this address space and returns its address.

Upon error, it returns C<undef> and sets an error that can be retrieved with the error method:

    my $addr = $shem->attach || die( $shem->error );

A shared memory segment object must be first created with the L</open> method, because L</attach> calls L<IPC::SysV/shmat> with the shared memory id and this id is returned upon using the L</open> method.

=head2 cbor

When called, this will set L<CBOR::XS> as the data serialisation mechanism when storing data to memory or reading data from memory.

=head2 create

Set or get the boolean value to true to indicate you want to create the shared memory block if it does not exist already. Default to false.

=head2 delete

This is an alias for L</remove>

=head2 destroy

Set or get the boolean value to indicate that the shared memory should be automatically destroyed when the module object is destroyed. See L<perlmod> for more information about module object destruction.

=head2 detach

Quoting the IPC documentation, this detaches the shared memory segment located at the address specified by L</attach> from this address space.

It returns C<undef> if it is not attached anymore, but without setting an error.

=head2 exclusive

Set or get the boolean value to affect the open flags in exclusive mode.

=head2 exists

Checks if the shared memory identified with C<key> exists.

It takes the same arguments as L</open> and returns 1 if the shared memory exists or 0 otherwise.

It does this by performing a L<perlfunc/shmget> such as:

    shmget( $shared_mem_key, $size, 0444 );

This will typically return the shared memory id if it exists or C<undef()> with an error set in C<$!> by perl otherwise.

=head2 flags

Provided with an optional hash or hash reference and this return a bitwise value of flags used by L</open>.

    my $flags = $shmem->flags({
        create => 1,
        exclusive => 0,
        mode => 0600,
    }) || die( $shmem->error );

=head2 id

Returns the id of the shared memory once it has been opened with L</open>

    my $s = $shmem->open || die( $shmem->error );
    my $id = $s->id;

=head2 json

When called, this will set L<JSON> as the data serialisation mechanism when storing data to memory or reading data from memory.

=head2 key

Sets or gets the shared memory key identifier.

    $shem->key( 'some_identifier' );

=head2 lock

It takes an optional bitwise lock value, and defaults to C<LOCK_SH> if none is provided and issues a lock on the shared memory.

    use Module::Generic::SharedMem qw( :all );
    my $s = $shem->open || die( $shmem->error );
    $s->lock( LOCK_EX );
    # Do something
    $s->unlock;

=head2 locked

Returns a positive value when a lock is active or 0 when there is no active lock.

The value is the bitwise value of the lock used.

=head2 mode

Sets or gets the mode for the shared memory as used by L</open>

    $shmem->mode( 0666 );
    my $s = $shmem->open || die( $shmem->error );

=head2 op

Issue an opeation on the L<semaphore|https://en.wikipedia.org/wiki/Semaphore_(programming)>.

Provided value sould be a set of 3.

    ＄s->op( @{$Module::Generic::SharedMem::SEMOP_ARGS->{(LOCK_SH)}} ) ||
        die( $s->error );

=head2 open

Creates or accesses a shared memory segment and returns a new L<Module::Generic::SharedMem> object. It takes an optional hash or hash reference with parameters such as C<key>, C<mode>, C<size>, and C<semid>.

    my $shmem = Module::Generic::SharedMem->new(
        create => 1,
        destroy => 0,
        # If not provided, will use the one provided during object instantiation
        key => 'my_memory',
        # 64K
        size => 65536,
    ) || die( Module::Generic::SharedMem->error );
    # Overriding some default value set during previous object instantiation
    my $s = $shmem->open({
        mode => 0600,
        size => 1024,
    }) || die( $shmem->error );

If called on an already opened object, it reuses the existing shared memory segment and semaphore, creating a new object that shares the same resources. This is particularly useful in threaded applications to ensure all threads access the same shared memory segment safely.

In threaded applications, the shared memory segment must be opened in the main thread before starting threads to ensure thread-safety. The module automatically manages semaphore reuse via a shared repository (C<$SERIAL2SEMID>) or the current object's semaphore ID, ensuring thread-safe operations without additional configuration.

        require threads;
        require threads::shared;
        threads->import();
        threads::shared->import();
        my $shem = Module::Generic::SharedMem->new(
            debug => $DEBUG,
            key => 'my_memory',
            size => 65536,
            mode => 0666,
            create => 1,
        ) || die( Module::Generic::SharedMem->error );
        my $obj = $shem->open({ mode => 'w' }) || die( $shem->error );
        my @threads;
        for(1..3)
        {
            push @threads, threads->create(sub
            {
                my $tid = threads->tid();
                my $sh = $obj->open();
                if( !defined( $sh ) )
                {
                    warn( "Error with thread $tid: unable to open shared memory segment: ", $obj->error );
                    return(0);
                }
                my $data = $sh->read();
                my $old_counter = $data->{counter};
                $data->{counter}++;
                $sh->write( $data ) || return(0);
                return(1);
            });
        }
        my $success = 1;
        for my $thr ( @threads )
        {
            my $result = $thr->join();
            $success &&= $result;
        }
        my $data = $obj->read();
        # $data->{counter} is 3, and $success is true

Parameters:

=over 4

=item * C<key>

The shared memory key identifier. Defaults to the key set during object instantiation or C<IPC::SysV::IPC_PRIVATE>.

=item * C<mode>

The octal mode for the shared memory (e.g., C<0600> for owner-only access). Defaults to the mode set during object instantiation.

=item * C<size>

The size of the shared memory segment in bytes. Defaults to the size set during object instantiation.

=item * C<semid>

An explicit semaphore ID to use, overriding automatic semaphore management.

=back

If the L</create> option is set to true, but the shared memory already exists, L</open> will detect it and attempt to open access to the shared memory without the L</create> bit on, which is C<IPC::SysV::IPC_CREAT>

Returns a new L<Module::Generic::SharedMem> object on success, or C<undef> on error, with the error accessible via L<Module::Generic/error>.

    my $s = $shem->open({ mode => 'r', size => 1024 }) || die( $shem->error );

=head2 owner

Sets or gets the shared memory owner, which is by default actually the process id (C<$$>)

=head2 pid

Get the L<semaphore|https://en.wikipedia.org/wiki/Semaphore_(programming)> pid once the shared memory has been opened.

    my $pid = $s->pid || die( $s->error );

=head2 rand

Get a random key to be used as identifier to create a shared memory.

=head2 read

Read the content of the shared memory and decode the data read using L<JSON>, L<CBOR|CBOR::XS>, L<Sereal> or L<Storable::Improved/thaw> depending on your choice of serialiser upon either object instantiation or upon using the methods L</json>, L</cbor>, L</sereal> or L</storable> or even more simply L</serialiser>. For example:

    my $s = Module::Generic::SharedMem->new( cbor => 1 ) ||
        die( Module::Generic::SharedMem->error );
    # or
    $s->cbor(1);
    # or
    my $s = Module::Generic::SharedMem->new( serialiser => 'cbor' ) ||
        die( Module::Generic::SharedMem->error );

By default, if no serialiser is specified, it will default to C<storable>.

You can optionally provide a buffer, and a maximum length and it will read that much length and put the shared memory content decoded in that buffer, if it were provided.

It then return the length read, or C<0E0> if no data was retrieved. C<0E0> still is treated as 0, but as a positive value, so you can do:

    my $len = $s->read( $buffer ) || die( $s->error );

But you really should more thoroughly do instead:

    my( $len, $buffer );
    if( !defined( $len = $s->read( $buffer ) ) )
    {
        die( $s->error );
    }

If you do not provide any buffer, you can call L</read> like this and it will return you the shared memory decoded content:

    my $buffer;
    if( !defined( $buffer = $s->read ) )
    {
        die( $s->error );
    }

=head2 remove

Remove entire the shared memory identified with L</key>

=head2 remove_semaphore

Remove the semaphore associated with the shared memory.

=head2 removed

Returns true if the shared memory was removed, false otherwise.

=head2 removed_semaphore

Returns true if the semaphore has been removed, false otherwise.

=head2 reset

Reset the shared memory value. If a value is provided, it will be used as the new reset value, othewise an empty string will be used.

=head2 semid

Return the L<semaphore|https://en.wikipedia.org/wiki/Semaphore_(programming)> id once the shared memory has been opened. See L<perlipc> for more information about semaphore and L<perlfunc>.

=head2 sereal

When called, this will set L<Sereal> as the data serialisation mechanism when storing data to memory or reading data from memory.

=head2 serial

Returns the serial number used to create or access the shared memory segment.

This serial is created based on the C<key> parameter provided either upon object instantiation or upon using the L</open> method.

The serial is created by calling L<IPC::SysV/ftok> to provide a reliable and repeatable numeric identifier.

=head2 serialiser

Sets or gets the serialiser. Possible values are: C<cbor>, C<json>, C<sereal>, C<storable>

=head2 shmstat

Returns an C<Module::Generic::SharedStat> object representing the current shared memory properties.

=head2 size

Sets or gets the shared memory block size.

This should be an integer representing bytes, so typically a multiple of 1024.

=head2 stat

Sets or retrieve value with L<semaphore|https://en.wikipedia.org/wiki/Semaphore_(programming)>.

If one parameter only is provided, it returns its corresponding value set.

It performs:

    # Get the semaphore id
    my $id = $s->semid;
    my $value = semctl( $id, $sem, IPC::SysV::GETVAL, 0 );

When 2 parameters are provided, this is treated as a key-value pair and sets the value for the corresponding key.

It performs:

    my $id = $s->semid;
    semctl( $id, $sem, IPC::SysV::SETVAL, $val )

If no parameter is provided it returns a L<Module::Generic::SemStat> object in scalar context or an array of value in list context.

=head2 storable

When called, this will set L<Storable::Improved> as the data serialisation mechanism when storing data to memory or reading data from memory.

=head2 supported

Returns true if IPC shared memory segments are supported by the system, and false otherwise.

=head2 unlock

Remove the lock, if any. The shared memory must first be opened.

    $s->unlock || die( $s->error );

=head2 write

Write the data provided to the shared memory, after having encoded it using L<JSON>, L<CBOR|CBOR::XS>, L<Sereal> or L<Storable::Improved/freeze> depending on your choice of serialiser. See L</json>, L</cbor>, L</sereal> and L</storable>

By default, if no serialiser is specified, it will default to C<storable>.

You can only store in shared memory reference, such as scalar reference, array or hash reference. You could also store module objects, but note that if you choose L<JSON> as a serialiser for your shared data, L<JSON> only supports encoding objects that are based on array or hash. As the L<JSON> documentation states "other blessed references will be converted into null". Thus if you use other reference types, you might want to use L<CBOR|CBOR::XS>, L<Sereal> or L<Storable> instead.

It returns the current object for chaining, or C<undef> if there was an error, which can then be retrieved with L<Module::Generic/error>

=head1 SERIALISATION

=for Pod::Coverage FREEZE

=for Pod::Coverage STORABLE_freeze

=for Pod::Coverage STORABLE_thaw

=for Pod::Coverage THAW

=for Pod::Coverage TO_JSON

Serialisation by L<CBOR|CBOR::XS>, L<Sereal> and L<Storable::Improved> (or the legacy L<Storable>) is supported by this package. To that effect, the following subroutines are implemented: C<FREEZE>, C<THAW>, C<STORABLE_freeze> and C<STORABLE_thaw>

=head1 THREAD & PROCESS SAFETY

L<Module::Generic::SharedMem> is designed for safe multi-process and multi-threaded access to shared memory using System V IPC. It uses semaphores to guard concurrent access, ensuring data integrity across processes and threads. In threaded applications, the module employs a shared repository (C<$SERIAL2SEMID>) and thread-safe locking to manage semaphore reuse, allowing multiple threads to access the same shared memory segment without conflicts.

Key considerations for thread and process safety:

=over 4

=item * B<Main Thread Initialization>

In threaded applications, the shared memory segment must be opened in the main thread before starting worker threads. This ensures the semaphore ID is stored in the shared repository (C<$SERIAL2SEMID>) or accessible via the opened object, allowing worker threads to reuse it safely.

    my $shem = Module::Generic::SharedMem->new( key => 'my_memory', create => 1 );
    my $obj = $shem->open({ mode => 'w' });
    my @threads = map {
        threads->create(sub {
            my $sh = $obj->open();
            # Work with $sh
        });
    } 1..10;

=item * B<Semaphore Reuse>

When calling L</open> on an already opened object (e.g., C<$obj->open()>), the module reuses the existing semaphore ID, ensuring all threads operate on the same shared memory segment. This is thread-safe due to internal locking mechanisms.

=item * B<Process Safety>

For multi-process applications, semaphores prevent concurrent access issues. Each process can open the shared memory segment using the same key, and the module ensures safe access via semaphore operations.

=item * B<Cleanup>

Shared memory and semaphores are system-wide resources and persist until explicitly removed or the system is rebooted. Use L</remove> to clean up shared memory and L</remove_semaphore> for semaphores, especially in daemonized or mod_perl environments. The L</destroy> and L</destroy_semaphore> options can automate cleanup when the object is destroyed, but manual removal is recommended for explicit control.

    $obj->remove() if $obj->exists();

=item * B<Thread Limitations>

While thread-safe, this module is primarily designed for inter-process communication. In threaded environments, ensure proper initialization and avoid creating new shared memory segments in worker threads to prevent semaphore conflicts.

=back

For debugging shared memory and semaphore usage on Unix-like systems (e.g., Linux, FreeBSD, macOS), use:

    ipcs -m  # List shared memory segments
    ipcs -s  # List semaphores
    ipcrm -m <shmid>  # Remove shared memory
    ipcrm -s <semid>  # Remove semaphore

=head1 AUTHOR

Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>

=head1 SEE ALSO

L<Module::Generic>, L<Module::Generic::SemStat>, L<Module::Generic::SharedStat>

L<perlipc>, L<perlmod>, L<IPC::Semaphore>

=head1 COPYRIGHT & LICENSE

Copyright (c) 2021-2024 DEGUEST Pte. Ltd.

You can use, copy, modify and redistribute this package and associated
files under the same terms as Perl itself.

=cut

