Stenyj has asked for the wisdom of the Perl Monks concerning the following question:

I have a really weird problem that stemmed from the solution to the problem found here:
CGI::Session - non-stop session creation problem
my $sid = cookie('main') || undef; my @sessionInfo; my $session = new CGI::Session(undef, $sid, {Directory=>'c:/apache/ses +sions'}); if ( $session->is_new() ) { $session->delete(); } else { # get values from session }

This code grabs the SID from the cookie (previously stored when the user logged in), and then attempts to validate the session. If the user IS logged in, then it gets the values from the session, if the user is NOT logged in, then it destroys the session (as there's no need for it).

When I use the code above in the body of the script, it runs without errors being generated
BUT
when I put it into a subroutine, ie:
&test; sub test { my $sid = cookie('main') || undef; my @sessionInfo; my $session = new CGI::Session(undef, $sid, {Directory=>'c:/apache +/sessions'}); if ( $session->is_new() ) { $session->delete(); } }

it returns the following error:
"Content-Type: text/html; charset=ISO-8859-1 (in cleanup) could not fl +ush: Couldn't unlink C:\apache\sessions\cgisess_16a6fc56d547a55cb4ec0 +5c183c5c10b: No such file or directory at /apache/htdocs/test.cgi lin +e 58.<br>
Line 58 is the next line after "&test;" where I call another subroutine (that works fine, and has absolutely nothing to do with sessions at all). If I edit out this line, the error simply skips to the next active line in the code... so it's obviously being generated by the &test subroutine.

Can anyone shed some light on what might be causing the code to not work properly in a subroutine, but work fine in the body of the code?


Regards,
Stenyj

20050408 Edit by ysth: use id:// instead of link with domain name

Edit by tye: put long error text in CODE tags

Replies are listed 'Best First'.
Re: Code works fine, but in subroutine does not... help plz!
by ikegami (Patriarch) on Apr 08, 2005 at 04:37 UTC
    You're probably getting the error when $session goes out of scope and its DESTROY method is called. Putting a warn "here!"; at the end of test would confirm that, if the error message appeared after "here!". I don't know why you're getting the error, but then again, I'm not familiar with this module. Is there something that must be done beore the object is destroyed?

      I looked at the source for CGI::Session and CGI::Session::File. The error is originating from CGI::Session::File::remove which is called when a deleted session is DESTROYed. However, the session you just created was never saved, so there's no file to delete yet. So, instead of deleting the new sessions (or trying to), I'd just make sure it never gets saved:

      ... if ( $session->is_new() ) { # Make CGI::Session think no save is needed # by simulating a call to flush. $session->{_STATUS} = &CGI::Session::SYNCED; # Make sure we don't use the session by accident. undef $session; } else { ...

      Other solutions:

        This seems to work beautifully.

        Thx a TON!!!!!

        Stenyj
Re: Code works fine, but in subroutine does not... help plz!
by tlm (Prior) on Apr 08, 2005 at 04:37 UTC

    I think the error is being generated by the destructor ("in cleanup") of an object going out of scope; in this case $session is the only plausible candidate. You can test this hypothesis by taking the code back out of the sub and sticking it in its own block off the top level, like this:

    { my $sid = cookie('main') || undef; my @sessionInfo; my $session = new CGI::Session(undef, $sid, {Directory=>'c:/apache/s +essions'}); if ( $session->is_new() ) { $session->delete(); } else { # get values from session } }
    If the hypothesis is correct, this should also trigger the same error.

    If this is case, then the hard part is figuring out why the destructor is having problems. As usual, I'd fire up the debugger (perldb) and start following stuff around. Or you may use the tried and true print-statement debugging method to probe the status of the object created by the constructor. Are you running with warnings? If not, turn them on, just in case the constructor is emitting warnings about problems when creating the new instance. In particular, does the server have permission to write to directory mentioned in the error message? Does the file mentioned in the error message still exist after the program exits? Can you log the contents of the directory in question before exiting the problematic block, to see if the constructor succeeded in creating the file that the destructor is failing to unlink?

    Update: One thing I should have mentioned from the get go is that if the destructor hypothesis is correct (and ikegami's idea with the warn statement is clearly a simpler way to nail it than what I proposed above of moving the code to a block off the top level yadda-yadda), then you should look for a subroutine called DESTROY in CGI/Session.pm, and just look at what that subroutine is attempting to do (that's the beauty of open source!). That'll give some good clues for why you are getting the error.

    the lowliest monk

Re: Code works fine, but in subroutine does not... help plz!
by Miguel (Friar) on Apr 08, 2005 at 07:08 UTC
    I wrote a small script in reply to your previous post. Perhaps you didn't read it.

    In my opinion you should _not_ create a session or send a cookie to the user's browser if you don't need it. Doing that you're wasting resources on server, bandwidth, filling the browser's cache with junk and wasting your time.

      Yes I'm still confused by how this is supposed to work.

      Here's how it's supposed to be, surely:

      • on every page, you check whether there's a session logged in or not
      • depending whether there is one, you do whatever you need to do
      • only on the login page do you create a session
      • only on the logout page do you delete a session (sessions expire after a certain time anyway)

      I think there's a fundamental problem with the way you're trying to do this.



      ($_='kkvvttuubbooppuuiiffssqqffssmmiibbddllffss')
      =~y~b-v~a-z~s; print
        Here's some sample scripts so it's clearer what I'm attempting to do & how everything works:

        Login script:
        #!c:/apache/perl/bin/perl.exe -wT use CGI; use strict; use CGI::Session; use HTML::Template; use MyAppCommon; my $foo = new CGI; my $alias = $foo->param('alias'); my $userID = 123; #normally would be pulled from DB, but keeping the c +ode as simple as possible for testing purposes my $empty; my $session = new CGI::Session(undef, $empty, {Directory=>"c:/apache/s +essions"}); $session->expire('+18h'); $session->param("id", $userID); $session->param("alias", $alias); my $cookie = $foo->cookie(-name => 'main', -value => $session->id, -expires => '+18h', -path => '/'); print $foo->header(-cookie => $cookie); MyAppCommon::topPublic(); my $template=HTML::Template->new(filename=>"c:/apache/templates/main/l +ogin.tmpl"); $template->param(ALIAS => $alias); print $template->output; MyAppCommon::bottomPublic($alias);

        One of the many pages that contain very similar coding (a page that simply wants to check if the user is logged in or not, and does NOT want to log them in if they're not... ie. does NOT create a session if they're not logged in... it only wants to check if they are & retrieve relevant session variables if they are logged in:

        #!c:/apache/perl/bin/perl.exe -wT use CGI; use strict; use CGI::Session; use HTML::Template; use MyAppCommon; my $foo = new CGI; print $foo->header(); my $sid = cookie('main') || undef; my @sessionInfo; my $session = new CGI::Session(undef, $sid, {Directory=>'c:/apache/ses +sions'}); if ( $session->is_new() ) { $session->delete(); } MyAppCommon::topPublic(); my $template=HTML::Template->new(filename=>"c:/apache/templates/main/h +ome.tmpl"); print $template->output; MyAppCommon::bottomPublic($alias, $userAccess, $membership);

        IF I comment out the following code of the above script:
        my $sid = cookie('main') || undef; my @sessionInfo; my $session = new CGI::Session(undef, $sid, {Directory=>'c:/apache/ses +sions'}); if ( $session->is_new() ) { $session->delete(); }

        and replace it with:
        &test; sub test { my $sid = cookie('main') || undef; my @sessionInfo; my $session = new CGI::Session(undef, $sid, {Directory=>'c:/apache +/sessions'}); if ( $session->is_new() ) { $session->delete(); } return 1; }
        I get the error I mentioned.

        " on every page, you check whether there's a session logged in or not # depending whether there is one, you do whatever you need to do"

        This is exactly what I want to do. Using the sessions ID, I want to determine if one exists with that session ID and to *NOT* create one if it doesn't exist.
      I was reviewing your example. As far as I can read, that's simply a login/logout script. So one of the two actions much take place. What about my situation, where I don't want to login or logout, I simply want to check if they're logged in, and what is displayed depends on whether or not they are logged in or not. If they're not logged in, then a session should no be created, but I believe these lines:
      my $sessionid=$q->cookie("cgisessid")||$session->param("_SESSION_ID"); my $cookie_id = $q->cookie(CGISESSID=>$session->id);

      in your example both creat a session (regardless of whether the person is logged in or not) and then creates a cookie (again, regardless of whether or not they're logged in.

      or am I misreading the code?

      Thx for all your feedback guys, I really appreciate it!

      Stenyj