NAME

Module::Generic::Global - Contextual global storage by namespace, class or object

SYNOPSIS

use Module::Generic::Global;

# Class-level global repository
my $repo = Module::Generic::Global->new( 'errors' => 'My::Module' );
$repo->set( $exception );
my $err = $repo->get;

# Object-level global repository
my $repo2 = Module::Generic::Global->new( 'cache' => $obj );
$repo2->set( { foo => 42 } );
my $data = $repo2->get;

# System-level repository
# Here 'system' is a special keyword
my $repo = Module::Generic::Global->new( 'system_setting' => 'system' );
# Inside Some::Module:
$repo->set( $some_value );
# Inside Another::Module
my $repo = Module::Generic::Global->new( 'system_setting' => 'system' );
my $value = $repo->get; # $some_value retrieved

{
    $repo->lock;
    # Do something
    # Lock is freed once it is out of scope
}

VERSION

v0.1.0

DESCRIPTION

This module provides contextual, thread/process-safe global storage for modules that want to isolate data per-class or per-object, or even across modules (with the system context), using namespaces. Supports Perl ithreads or APR-based threading environments.

It can be used to store and access data in global repository whether Perl operates under a single process, under threads, including Apache Worker/Event MPM with mod_perl2

The repository used is locked in read or write mode before being accessed ensuring no collision and integrity.

It is designed to store one value at a time in the specified namespace in the global repository.

CONSTRUCTOR

# System-level repository
# 'system' is a special keyword
my $repo = Module::Generic::Global->new( 'global_settings' => 'system' );

# Class-level global repository
my $repo = Module::Generic::Global->new( 'errors' => 'My::Module' );
my $repo = Module::Generic::Global->new( 'errors' => 'My::Module', key => $unique_key );
my $repo = Module::Generic::Global->new( 'errors' => 'My::Module', { key => $unique_key } );

# Object-level global repository
my $repo2 = Module::Generic::Global->new( 'cache' => $obj );
my $repo2 = Module::Generic::Global->new( 'cache' => $obj, key => $unique_key );
my $repo2 = Module::Generic::Global->new( 'cache' => $obj, { key => $unique_key } );

new

Creates a new repository under a given namespace, and context, and return the new class instance.

A context key is composed of:

1. the class name, or the object ID retrieved with "refaddr" in Scalar::Util if a blessed object was provided,
2. the current process ID, and
3. optionally the thread tid if running under a thread.

However, if a context is system, then the key is also automatically set to system.

Possible options are:

  • key

    Specifies explicitly a key to use

    Please note that this option would be discarded if the context is set to system

METHODS

cleanup_register

# In your Apache/mod_perl2 script
sub handler : method
{
    my( $class, $r ) = @_;
    my $repo = Module::Generic::Global->new( 'errors' => 'My::Module' );
    $repo->cleanup_register( $r );
    # Rest of your code
}

This prepares a cleanup callback to empty the global variables when the Apache/mod_perl2 request is complete.

It takes an Apache2::RequestRec as its sole argument.

Pod::Coverage clear

clear_error

$repo->clear_error;
Module::Generic::Global->clear_error;

This clear the error for the current object, and the latest recorded error stored as a global variable.

Pod::Coverage debug

error

$repo->error( "Something went wrong: ", $some_value );
my $exception = $repo->error;

Used as a mutator, and this sets an exception object, and returns undef in scalar context, or an empty list in list context.

In accessor mode, this returns the currently set exception object, if any.

exists

Returns true (1) if a value is currently stored under the context, o false (0) otherwise. This only checks that an entry exists, not whether that entry has a true value.

get

Retrieves the stored value, deserialising it using Storable::Improved if it was serialised, and return it.

If an error occurs, it returns undef in scalar context, or an empty list in list context.

length

my $repo = Module::Generic::Global->new( 'my_repo' => 'My::Module' );
say $repo->length;

Returns the number of elements in the namespace.

lock

{
    $repo->lock;
    # Do some computing
    # Lock is freed automatically when it gets out of scope
}

Sets a lock to ensure the manipulation done is thread-safe. If the code runs in a single thread environment, then this does not do anything.

When the lock gets out of scope, it is automatically removed.

remove

Removes the stored value for the current context.

This can also be called as clear

set

$repo->set( { foo => 42 } );

Stores a scalar or serialisable reference in the current namespace and context. This overwrite any previous value for the same context.

The value provided is serialised using Storable::Improved before it is stored in the global repository.

Returns true upon success, and upon error, return undef in scalar context, or an empty list in list context.

unlock

$repo->unlock;

This is used to remove the lock set when under Apache2 ModPerl by using "unlock" in APR::ThreadRWLock

It is usually not necessary to call this explicitly, because when the lock set previously gets out of scope, it is automatically removed.

CONSTANTS

The constants that can be imported into your namespace are:

CAN_THREADS

This returns true (1) or false (0) depending on whether Perl was compiled with ithreads (Interpreter Threads) or not.

HAS_THREADS

This returns true (1) or false (0) depending on whether Perl was compiled with ithreads (Interpreter Threads) or not, and whether threads has been loaded.

This is not actually a constant. Its value will change if threads has been loaded or not. For example:

use Module::Generic::Global ':const';

say HAS_THREADS ? 'yes' : 'no'; # no
require threads;
say HAS_THREADS ? 'yes' : 'no'; # yes

IN_THREAD

This returns true (1) or false (0) depending on whether Perl was compiled with ithreads (Interpreter Threads) or not, and whether threads has been loaded, and we are inside a thread (tid returns a non-zero value). For example:

use Module::Generic::Global ':const';

say IN_THREAD ? 'yes' : 'no'; # no
require threads;
say IN_THREAD ? 'yes' : 'no'; # no
my $thr = threads->create(sub
{
    say IN_THREAD ? 'yes' : 'no'; # yes
});
$thr->join;

Note that this only works for Perl threads

MOD_PERL

This returns the ModPerl version if running under ModPerl, or undef otherwise.

MPM

This returns the Apache MPM (Multi-Processing Modules) used if running under ModPerl. Possible values are prefork, worker, event, winnt or undef if not running under ModPerl.

This uses "show" in Apache2::MPM to make that determination.

HAS_MPM_THREADS

This returns true (1) or false (0) depending on whether the code is running under ModPerl, and the Apache MPM (Multi-Processing Modules) used is threaded (e.g. worker, or event). This uses "is_threaded" in Apache2::MPM to make that determination.

See Apache2::MPM

THREAD & PROCESS SAFETY

This module is designed to be fully thread-safe and process-safe, ensuring data integrity across Perl ithreads and mod_perl’s threaded Multi-Processing Modules (MPMs) such as Worker or Event. It uses robust synchronisation mechanisms to prevent data corruption and race conditions in concurrent environments.

Synchronisation Mechanisms

Module::Generic::Global employs the following synchronisation strategies:

  • Perl ithreads

    When Perl is compiled with ithreads support (CAN_THREADS is true) and the threads module is loaded (HAS_THREADS is true), global repositories ($REPO, $ERRORS, $LOCKS) are marked :shared using threads::shared. Access to these repositories is protected by "lock" in perlfunc to ensure thread-safe read and write operations.

  • mod_perl Threaded MPMs

    In mod_perl environments with threaded MPMs (e.g., Worker or Event, where HAS_MPM_THREADS is true), the module uses APR::ThreadRWLock for locking if Perl lacks ithreads support or threads is not loaded, which is very unlikely, since mod_perl would normally would not work under threaded MPM if perl was not compiled with threads. This ensures thread-safety within Apache threads sharing the same process.

  • Non-Threaded Environments

    In single-threaded environments (e.g., mod_perl Prefork MPM or non-threaded Perl), locking is skipped, as no concurrent access occurs within a process. Data is isolated per-process via the process ID ($$) in context keys.

Shared Data Initialisation

To prevent race conditions during dynamic conversion of global variables to shared ones, the module adopts a conservative approach. At startup, if CAN_THREADS is true (Perl supports ithreads), the global repositories are initialised as :shared:

  • $REPO: Stores data for all namespaces and context keys.

  • $ERRORS: Stores error objects for error handling.

  • $LOCKS: Manages lock state for thread-safe operations.

This upfront initialisation ensures thread-safety without the risk of mid-air clashes that could occur if private globals were converted dynamically when threads are loaded.

Context Key Isolation

Data is stored in repositories using context keys that ensure isolation:

  • Class-Level Keys

    For class-level repositories (e.g., $class->new( 'ns' => 'My::Module' )), keys are formatted as <class>;<pid> (e.g., My::Module;1234). This isolates data per class and process, preventing cross-process interference.

  • Object-Level Keys

    For object-level repositories (e.g., $class->new( 'ns' => $obj )), keys are:

    - Non-Threaded: <refaddr>;<pid> (e.g., 1234567;1234), where refaddr is the object’s reference address from "refaddr" in Scalar::Util.
    - Threaded: <refaddr>;<pid>;<tid> (e.g., 1234567;1234;1), where tid is the thread ID from "tid" in threads.

    The inclusion of tid when HAS_THREADS is true ensures thread-level isolation for object-level data. Repositories created in non-threaded environments cannot be overwritten by threaded ones, and vice versa, due to differing key formats.

Error Handling

Errors are stored in both instance-level ($self->{_error}) and class-level ($ERRORS repository under the errors namespace) storage, supporting patterns like My::Module->new || die( My::Module->error ). Each class-process-thread combination (keyed by <class>;<pid>[;<tid>]) stores at most one error, with subsequent errors overwriting the previous entry to prevent memory growth. Errors are serialised using Storable::Improved for compatibility with threads::shared.

mod_perl Considerations

In mod_perl environments:

  • Prefork MPM

    Data is per-process, requiring no additional synchronisation, as each process operates independently.

  • Threaded MPMs (Worker/Event)

    Threads within a process share the same Perl interpreter clone, necessitating thread-safety. Since mod_perl requires threaded Perl ($Config{useithreads} true), threads::shared and "lock" in perlfunc are used unless threads is not loaded, in which case APR::ThreadRWLock is employed. Users should call "cleanup_register" in handlers to clear shared repositories after each request, preventing memory leaks.

  • Thread-Unsafe Functions

    Certain Perl functions (e.g., localtime, readdir, srand) and operations (e.g., chdir, umask, chroot) are unsafe in threaded MPMs, as they may affect all threads in a process. Users must avoid these and consult perlthrtut and mod_perl documentation for guidance.

Thread-Safety Considerations

The module’s thread-safety relies on:

  • Shared Repositories: Initialised as :shared when CAN_THREADS is true, ensuring safe access across threads.

  • Locking: "lock" in perlfunc or APR::ThreadRWLock protects all read/write operations.

  • Key Isolation: Thread-specific keys (<refaddr>;<pid>;<tid>) isolate object-level data when created in different threads.

In environments where %INC manipulation (e.g., by forks) emulates threads, HAS_THREADS and IN_THREAD may return true. This is generally safe, as forks provides a compatible tid method, but users in untrusted environments should verify $INC{'threads.pm'} points to the actual threads module.

For maximum safety, users running mod_perl with threaded MPMs should ensure Perl is compiled with ithreads and explicitly load threads, or use Prefork MPM for single-threaded operation.

Environment Variables

The following environment variables influence thread-safety:

  • MG_MAX_RETRIES: Sets the number of lock retry attempts (default: 10).

  • MG_RETRY_DELAY: Sets the base retry delay for data operations (microseconds, default: 10,000).

  • MG_ERROR_DELAY: Sets the base retry delay for error operations (microseconds, default: 5,000).

SEE ALSO

Module::Generic, Storable::Improved, Module::Generic::Exception

AUTHOR

Jacques Deguest <jack@deguest.jp>

COPYRIGHT & LICENSE

Copyright (c) 2025 DEGUEST Pte. Ltd.

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