views:

380

answers:

3

I need to do some simple timezone calculation in mod_perl. DateTime isn't an option. What I need to do is easily accomplished by setting $ENV{TZ} and using localtime and POSIX::mktime, but under a threaded MPM, I'd need to make sure only one thread at a time was mucking with the environment. (I'm not concerned about other uses of localtime, etc.)

How can I use a mutex or other locking strategy to serialize (in the non-marshalling sense) access to the environment? The docs I've looked at don't explain well enough how I would create a mutex for just this use. Maybe there's something I'm just not getting about how you create mutexes in general.

Update: yes, I am aware of the need for using Env::C to set TZ.

+1  A: 

If you're using apache 1.3, then you shouldn't need to resort to mutexes. Apache 1.3 spawns of a number of worker processes, and each worker executes a single thread. In this case, you can write:

{
    local $ENV{TZ} = whatever_I_need_it_to_be();

    # Do calculations here.
}

Changing the variable with local means that it reverts back to the previous value at the end of the block, but is still passed into any subroutine calls made from within that block. It's almost certainly what you want. Since each process has its own independent environment, you won't be changing the environment of other processes using this technique.

For apache 2, I don't know what model it uses with regards to forks and threads. If it keeps the same approach of forking off processes and having a single thread each, you're fine.

If apache 2 uses honest to goodness real threads, then that's outside my area of detailed knowledge, but I hope another lovely stackoverflow person can provide assistance.

All the very best,

Paul
pjf
Apache 2 offers multiple forking/threading models called MPMs. I'm using the worker MPM, which uses multiple processes and multiple threads per process. The MPM that acts like Apache 1 is called prefork.
ysth
+3  A: 

(repeating what I said over at PerlMonks...)

BEGIN {
    my $mutex;

    sub that {
        $mutex ||= APR::ThreadMutex->new( $r->pool() );
        $mutex->lock();

        $ENV{TZ}= ...;
        ...

        $mutex->unlock();
    }
}

But, of course, lock() should happen in a c'tor and unlock() should happen in a d'tor except for one-off hacks.

Update: Note that there is a race condition in how $mutex is initialized in the subroutine (two threads could call that() for the first time nearly simultaneously). You'd most likely want to initialize $mutex before (additional) threads are created but I'm unclear on the details on the 'worker' Apache MPM and how you would accomplish that easily. If there is some code that gets run "early", simply calling that() from there would eliminate the race.

Which all suggests a much safer interface to APR::ThreadMutex:

BEGIN {
    my $mutex;

    sub that {
        my $autoLock= APR::ThreadMutex->autoLock( \$mutex );
        ...
        # Mutex automatically released when $autoLock destroyed
    }
}

Note that autoLock() getting a reference to undef would cause it to use a mutex to prevent a race when it initializes $mutex.

tye
setting $ENV{TZ} in mod_perl won't work in Apache 2. The changes are not reflected back to the C-environment which means they won't affect the output of things like localtime.
Michael Cramer
Thanks. That's why I voted your answer up :) Some discussion/links re why Apache simply chose to "punt" on this might be interesting.
tye
tracked down some interesting comments from the mod_perl source that shed some light on it.
Michael Cramer
+3  A: 

Because of this issue, mod_perl 2 actually deals with the %ENV hash differently than mod_perl 1. In mod_perl 1 %ENV was tied directly to the environ struct, so changing %ENV changed the environment. In mod_perl 2, the %ENV hash is populated from environ, but changes are not passed back.

This means you can no longer muck with $ENV{TZ} to adjust the timezone -- particularly in a threaded environment. The Apache2::Localtime module will make it work for the non-threaded case (by using Env::C) but when running in a threaded MPM that will be bad news.

There are some comments in the mod_perl source (src/modules/perl/modperl_env.c) regarding this issue:

/* * XXX: what we do here might change:
 *      - make it optional for %ENV to be tied to r->subprocess_env
 *      - make it possible to modify environ
 *      - we could allow modification of environ if mpm isn't threaded
 *      - we could allow modification of environ if variable isn't a CGI
 *        variable (still could cause problems)
 */
/*
 * problems we are trying to solve:
 *      - environ is shared between threads
 *          + Perl does not serialize access to environ
 *          + even if it did, CGI variables cannot be shared between threads!
 * problems we create by trying to solve above problems:
 *      - a forked process will not inherit the current %ENV
 *      - C libraries might rely on environ, e.g. DBD::Oracle
 */
Michael Cramer