When rand isn't random
In spite of having programming Perl mostly full time for the last 12 years, I still find myself learning new things about how Perl works (like the time I discovered the arcane apostrophe package separator when trying to add a possessive 's' on the end of a variable in an interpolated string).
Yesterday yielded a similar epiphany when I realized how rand, srand and fork were interacting in our e-commerce application at $work. Consider the following one-liner:
perl -le 'for(1..2) { fork or do { print int rand 100; exit } }'
This prints two random numbers between 0 and 99. Simple. Now, consider this slightly different example:
perl -le 'rand; for(1..2) { fork or do { print int rand 100; exit } }'
This prints the same random number between 0 and 99 twice. The reasoning is quite simple but was not obvious to me at first. When rand
is first called in a Perl application, srand
is implicitly called to seed rand
with a different starting number. If this happens prior to a process forking, the same seed is shared by all of the child processes and non-randomness ensues.
The tricky bit to this issue is that since the rand
seed is effectively global, a pre-fork rand
call can be anywhere in your large-ish legacy mod_perl application and it won't be apparent for quite some time why the "random" numbers being generated by the mod_perl children seem to be colliding at an alarming rate! This issue was further occluded by the fact that the srand perldoc assures the reader (I'm a very trusting reader) that most applications never need to call srand
explicitly.
In my opinion, one of two things should be changed about how this works. Either:
- The global has-rand-been-seeded flag should be per-process, or
- A warning should be added to the srand perldoc page
In our case, simply adding a PerlChildInitHandler
that re-calls srand
proved to be a very simple fix. This solution comes courtesy of bugzilla which was quite google-able once one gets past the denial and realizes that fork
and srand
interact differently than one thought:
# In your Apache configuration
PerlChildInitHandler "sub { srand }"
What do you think? Is this worthy of an srand
doc patch or a functionality change or am I the last Perl developer to uncover this behavior?
Submit a patch.
It's really subtle and it actually *is* a specific case where an application should use srand.
See the POD for File::Temp for some documentation on this behavior.
Am I missing something or should that instead be:
PerlChildInitHandler "sub { srand }"
Notice the "srand" instead of "rand" call. Otherwise if some library you're using during startup (or preload) makes a call to rand() then you're still sharing the same seed, right?
@mpeters - yes, that was my intent. I've updated the post to reflect the correct srand call in the init handler. Thanks
And now I'm a perl5 core contributor!
http://www.nntp.perl.org/group/perl.perl5.changes/2010/07/msg26793.html
Ack, bit by this in mod_perl 2.0.8! The PerlChildInitHandler workaround seems to solve my problem.