# http://rt.cpan.org/Public/Bug/Display.html?id=21952
# Hello Mark & Sherzod
#
#
# I just started yesterday to use CGI::Session (as an embedded component
# of CGI::Forge ), and would like to report a wrong behavior.
#
# -----------------------------------------------------
# My environment :
#
# CGI::Session : Version 4.14
# uname -a : Linux perl.smartech.pf 2.6.16.4 #1 PREEMPT Mon Apr 17
# 15:12:40 TAHT 2006 i686 AMD Athlon(TM) XP 2800+ unknown GNU/Linux
# perl -v : This is perl, v5.8.7 built for i386-linux
#
# -----------------------------------------------------
# My code (summarized) :
#
# 1 my $opt_dsn = ...
# 2 my $cgiquery = ...
# 3 my $s = CGI::Session->load('driver:file;serializer:default',
# $cgiquery, $opt_dsn) or die;
# 4 $s = CGI::Session->new('driver:file;serializer:default', $cgiquery,
# $opt_dsn) if $s->is_empty();
# 5
#
# -----------------------------------------------------
# The suspicious behavior...
#
# - The statement line 3 leads to the creation of a new CGI::Session
# object, say A, and to the creation of a CGI::Session::Sriver::file
# driver object, say B.
#
# - Statement 4 resets $s, so A & B, no longer reachable are DESTROYed and
# garbage collected.The destruction of A is caught in CGI/Session.pm to
# automagically call CGI::Session::flush() which does nothing in this
# case. A new CGI::Session object is created and assigned to $s, say C, which
# shares the driver B (see log below).
#
# - When the end of program is reached (line 5 above), C is caught to be
# flushed by DESTROY. B having already disappeared out the scene, a
# new driver is specially created at this time of death only to allow the
# flushing (DESTROY >> flush() >> CGI::Session::_driver).
# This new driver ignores $opt_dsn (for instance Directory => /my/temp),
# so the flushing creates or updates session files
# at the wrong place...
#
# And it appears that
# C->{_DRIVER_ARGS} is also gone,
#
# -----------------------------------------------------
# My analysis of the problem
#
# Statement line 3 leads CGI::Session::Driver::new() to physically alter
# its argument (here $opt_dsn) by turning it into a driver object (bless
# $opt_dsn, ).
#
# So $opt_dsn data is no longer a private custom data structure : it has
# turned into an object (B) elligible for a premature DESTROY
# when it goes out of reach after statement 4 resets $s.
#
# As such, and unfortunate as it is, the garbage collection of B is also
# that of $opt_dsn, and that of $s->{_DRIVER_ARGS},
# which proves to be unavailable (long gone) when used by
# CGI::Session::_driver() to create a driver for the late flushing
# (->new( $self->{_DRIVER_ARGS} ).
#
# -------------------
# My suggestion
#
# My idea is that the custom data, here $opt_dsn, *should not* be altered
# by the underlying CGI::Session logic.
# My suggestion to restore a good behavior is to prevent
# CGI::Session::Driver to turn its argument into an object.
# This is easily done by patching CGI::Forge::Driver::new() as follows
# ( *bold* shows the suggested patch, /*bold italic*/ shows my other
# "perturbations" ) :
#
# sub new {
# /*my ($class, $args) = @_;
# croak "Invalid argument type passed to driver: " . Dumper($args) if
# $args && ! ref $args;
# $args ||= {};*/
#
# # my $self = bless ($args, $class) # wrong : $args is a custom
# data that shouldn't be altered
# my $self = bless (*{%$args}*, $class); # Instead make it a
# shallow-clone, and only alter the clone !
# return $self if $self->init();
# return $self->set_error( "%s->init() returned false", $class);
# }
#
# I've applied it to my CGI::Session version and the suspicious behavior
# was removed.
#
# Cheers, and good luck.
#
# I hope that CGI::Session stays around up & running : it's a fine suite
# of module. Thanks for contributing it.
#
#
# Franck PORCHER
#
# ======================= LOGS==========================
# /*Statement line 3 ...
# */Oct 7 16:32:21 perl logger: [CGISESSION::LOAD::1] SESSION:
# CGI::Session=HASH(0x87808ac)
# Oct 7 16:32:21 perl logger: [CGISESSION::_driver] SESSION:
# CGI::Session=HASH(0x87808ac) DRIVER: *DRIVERARGS: _HASH(0x87b3a20)_*
# Oct 7 16:32:21 perl logger: [DRIVER::INIT] DRIVER:*
# CGI::Session::Driver::file=_HASH(0x87b3a20)_* DIRECTORY: .
# Oct 7 16:32:21 perl logger: [CGISESSION::LOAD::2] SESSION:
# CGI::Session=HASH(0x87808ac) DRIVER:
# CGI::Session::Driver::file=HASH(0x87b3a20)
#
# ==> The 3 lines above show how $opt_dsn (*_HASH(0x87b3a20)_*) is turned
# into an object (_*CGI::Session::Driver::file=HASH(0x87b3a20)*_)
#
#
# /*Statement lien 4 (rest of $s) ...*/
# Oct 7 16:32:21 perl logger: [*CGISESSION::DESTROY*] SESSION:
# CGI::Session=HASH(0x87808ac) DRIVER:
# Oct 7 16:32:21 perl logger: [*DRIVER::DESTROY*] DRIVER:
# _*CGI::Session::Driver::file=HASH(0x87b3a20)*_
#
# ==> these 2 lines show that (blessed)$opt_dsn is DESTROYED prematurately..
use strict;
use File::Spec;
use Test::More ('no_plan');
BEGIN {
use_ok("CGI");
use_ok('CGI::Session');
use_ok("CGI::Session::Driver");
use_ok("CGI::Session::Driver::file");
}
my $opt_dsn = {Directory=>File::Spec->tmpdir()};
ok(ref($opt_dsn) eq 'HASH', '$opt_dsn is HASH');
ok(my $q = CGI->new());
ok(my $s = CGI::Session->new("driver:file;serializer:default", $q, $opt_dsn));
ok(ref($opt_dsn) eq 'HASH', '$opt_dsn is HASH');
# Clean up /tmp as per RT#29969.
$s -> delete();
undef($s);
ok(!defined($s), "Session object is no longer available");
ok($opt_dsn, "\$opt_dsn still exists");
is(ref($opt_dsn),'HASH', '$opt_dsn is still a hashref');