NAME
Module::Generic::Global - Contextual global storage by namespace, class or object
SYNOPSIS
use Module::Generic::Global ':const';
# 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 safely
} # lock released at end of scope
# With limits
my $limited = Module::Generic::Global->new( 'bounded' => 'My::Module',
max_size => 10, # max keys in namespace
max_store_bytes => 1024, # max bytes per stored item (serialised)
max_total_bytes => 4096 # max bytes total in namespace (serialised)
);
$limited->set( 'data' ); # Evicts if over
# With auto-cleanup (mod_perl)
my $auto = Module::Generic::Global->new( 'temp' => 'My::Module',
auto_cleanup => 1 # Auto-registers cleanup for self namespace
);
# Or with options
my $custom_auto = Module::Generic::Global->new(
'multi' => 'My::Module',
auto_cleanup =>
{
namespaces => ['temp', 'cache'],
keep => ['persistent'],
callback => sub
{
my( $repo, $r ) = @_;
$r->log->notice( "Cleanup for " . $repo->namespace );
},
}
);
# Manual cleanup register (mod_perl)
$repo->cleanup_register( r => $apache_r ); # clears default namespace unless overridden
$repo->cleanup_register(
r => $apache_r,
namespaces => ['extra'],
keep => ['save'],
callback => sub { ... },
);
VERSION
v1.1.0
DESCRIPTION
This module provides contextual, thread/process-safe global storage, organised by namespace and a context key derived from a class name, object identity, or the special system context. Supports Perl ithreads or APR-based threading environments.
It has no dependencies except for Scalar::Util and Storable::Improved
It is designed to work in:
single-process non-threaded Perl
Perl ithreads (threads / threads::shared)
mod_perl under threaded MPMs (Worker/Event), including a fallback to APR::ThreadRWLock when shared variables are not available
Values are serialised before storage (default Storable::Improved) and deserialised on retrieval.
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.
Notes on context and scope
The repository is stored in memory. This means:
Under a multi-process server (such as Apache), the
systemcontext is per-process.Under threads, values may be shared within a process depending on the runtime environment and the locking backend.
In other words: system means "system within this process/runtime context", not "system across all server processes".
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 } );
# Declaring a callback used by the get() method
my $repo = Module::Generic::Global->new( 'cache' => $obj,
key => $unique_key,
on_get => sub
{
my $cache = shift( @_ );
my $repo = $_; # The object is accessible as $_
},
serialiser => 'Sereal',
);
# Or by providing some values in the callback
my $repo = Module::Generic::Global->new( 'cache' => $obj,
key => $unique_key,
on_get => [sub
{
my $cache = shift( @_ );
my $repo = $_; # The object is accessible as $_
},
host => 'localhost', user => 'john', password => 'some secret', port => 12345
]
);
# or by providing the arguments as an array reference
my $repo = Module::Generic::Global->new( 'cache' => $obj,
key => $unique_key,
on_get => [sub
{
my $cache = shift( @_ );
my $repo = $_; # The object is accessible as $_
},
[ host => 'localhost', user => 'john', password => 'some secret', port => 12345 ]
]
);
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
objectwas 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.
For object-level repositories, when running under Perl ithreads and threads is loaded, the thread id is included in the default key.
Possible options
auto_cleanupEnable auto cleanup mode. This is disabled by default. When enabled, this will empty the global repository. When running under mod_perl, this will call cleanup_register using "request" in Apache2::RequestUtil. If
GlobalRequestof mod_perl is not enabled, or an error occurs, it will be caught, so this is safe to use.Supported values are:
0(default)1(enable with default behaviour)a hash reference of options passed to "cleanup_register"
Example:
Module::Generic::Global->new( 'cache' => $module_class, auto_cleanup => { namespaces => ['ns1','ns2'], keep => ['persistent'], callback => sub { my( $repo, $r ) = @_; $r->log->notice( "Module::Generic::Global clean up ", $repo->namespace, " occurred." ); } } );auto_cleanupdoes not accept a plain CODE reference as a mode; use the hashref form and passcallbackinstead.keySpecifies explicitly a key to use instead of the default computed key.
Please note that this option would be discarded if the
contextis set tosystemmax_sizeMaximum number of keys in a namespace (integer > 0). If exceeded on
set, older entries are evicted.Eviction order is based on the internal ordering list, which is updated on successful
set. Overwriting an existing key refreshes its recency.max_store_bytesMaximum allowed size (in bytes) for a single stored value (integer > 0). The size is based on the serialised representation (and for scalars, the raw scalar size is also checked).
Default: 5MB (5242880).
max_total_bytesMaximum allowed total size (in bytes) for all stored values in a namespace (integer > 0). If exceeded on
set, older entries are evicted until the new total fits, otherwisesetfails.Eviction order is based on the internal ordering list, which is updated on successful
set. Overwriting an existing key refreshes its recency.on_getRegisters a callback that is automatically executed after a successful call to "get".
This is useful to restore contextual dependencies after deserialisation (e.g. re-attaching a database connection object).
The callback can be provided as:
a code reference
on_get => sub { ... }an array reference of callback + arguments
on_get => [ sub { my( $value, @args ) = @_; ... }, [ 'arg1', 'arg2' ], ];or:
on_get => [ sub { my( $value, @args ) = @_; ... }, 'arg1', 'arg2', ];
When the callback is executed,
$_is locally set to the current repository object instance.This allows the callback to access the repository without altering the callback argument list.
Example:
on_get => sub { my( $value ) = @_; my $repo = $_; return( $value ); };You may override the callback per-call by passing
on_getto "get".refcountWhen the
refcountoption is provided (boolean0/1/empty string orundef), the repository enables reference counting for automatic cleanup.Module::Generic::Globalwill count increment on creation and decrement on destroy—entries remove when hitting0. Implicitly enabled for class-level contexts (string controller, no key) to manage shared lifetime. For keyed or object-level, opt-in viarefcount => 1.serialiserorserializerSpecify a serialiser to use instead of the default value set in the global variable
$DEFAULT_SERIALISER, which is, by default, set toStorable::ImprovedThe serialiser will be used to freeze and thaw the data.
Supported serialiser are:
CBORorCBOR::XSCBOR::FreeSerealStorableorStorable::ImprovedA CODE reference, called as
$serialiser->( $data, 'freeze' )or$serialiser->( $data, 'thaw' )
When a code reference is provided, the following arguments will be provided:
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.
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.
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 the preferred serialiser if it was serialised, and return it.
If an error occurs, it returns undef in scalar context, or an empty list in list context.
The callback registered via the on_get option (constructor) is executed after deserialisation. You may also override the callback on a per-call basis by passing on_get to get().
Examples
Basic usage
my $repo = Module::Generic::Global->new( table_cache => 'system' );
my $cache = $repo->get;
Using an on_get callback declared once at construction time
This is useful when your cached objects require contextual restoration after deserialisation.
my $repo = Module::Generic::Global->new(
table_cache => 'system',
on_get => sub
{
my( $value ) = @_;
my $repo = $_;
# Example of a post-thaw adjustment
# $value->some_init_method if( ref( $value ) );
return( $value );
}
);
my $cache = $repo->get;
Passing extra arguments to the callback
my $repo = Module::Generic::Global->new(
table_cache => 'system',
on_get => [
sub
{
my( $value, $prefix ) = @_;
my $repo = $_;
# Do something with $value and $prefix
return( $value );
},
"tables-cache: ",
],
);
my $cache = $repo->get;
Overriding the callback explicitly at get() time
This overrides the callback that was optionally provided at construction.
my $cache = $repo->get(
on_get => sub
{
my( $value ) = @_;
my $repo = $_;
return( $value );
}
);
Re-attaching a database connection after deserialisation
This pattern is typical when serialising objects that should not store runtime resources such as DBI handles.
my $repo = Module::Generic::Global->new(
table_cache => 'system',
on_get => sub
{
my( $tables ) = @_;
my $repo = $_;
return if( !ref( $tables ) );
# Example: rebind a connection object after thaw
# $tables->dbo( $dbo );
return( $tables );
}
);
my $tables = $repo->get;
key
Returns the computed or explicit key used by this instance.
This is read-only.
length
my $repo = Module::Generic::Global->new( 'my_repo' => 'My::Module' );
say $repo->length;
Returns the number of keys currently present in the namespace (as seen in the repository).
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.
namespace
Returns the current namespace used in this instance. This is read-only.
remove
Removes the stored value for the current namespace and key, updates ordering and size accounting, and removes per-key stats.
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 the preferred serialiser before it is stored in the global repository.
On success, updates:
per-namespace total bytes accounting
per-key stored byte stats
ordering list (the key is moved to the end on every successful set, including overwrites)
If limits are configured (max_store_bytes, max_total_bytes, max_size), older entries may be evicted on set.
Returns true upon success, and upon error, return undef in scalar context, or an empty list in list context.
stat
Returns an array reference of hash references describing the largest entries in $STATS (sorted by stored bytes), limited by the provided limit (default 20).
It contains the following properties:
bytesThe size in bytes for the current value.
keyThe repository key.
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 can be imported into your namespace with:
use Module::Generic::Global ':const';
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_THREADSis true) and the threads module is loaded (HAS_THREADSis true), global repositories ($REPO,$ERRORS,$LOCKS) are marked:sharedusing 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_THREADSis 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), whererefaddris the object’s reference address from "refaddr" in Scalar::Util. - - Threaded:
<refaddr>;<pid>;<tid>(e.g.,1234567;1234;1), wheretidis the thread ID from "tid" in threads.
The inclusion of
tidwhenHAS_THREADSis 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
:sharedwhenCAN_THREADSis 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.Reference Counting
When
refcountis enabled upon instantiation, repositories use internal counts to auto-remove entries on last reference destroy, preventing leaks in shared contexts. Counts are per-namespace/key, thread-safe via locking.
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.