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 tosystem
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
), whererefaddr
is the object’s reference address from "refaddr" in Scalar::Util. - - Threaded:
<refaddr>;<pid>;<tid>
(e.g.,1234567;1234;1
), wheretid
is the thread ID from "tid" in threads.
The inclusion of
tid
whenHAS_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.- - Non-Threaded:
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
whenCAN_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.