Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask

Comment on

( #3333=superdoc: print w/replies, xml ) Need Help??
I had been running a Dancer application for several weeks. Everything was fine, with just occasional complaints in the log about invalid combination of form parameters (I dutifully validate both on client and server sides, so probably some users have disabled their JavaScript). But then, suddenly, the log contained several instances of the following error message:
[14166] error @0.102147> [hit #336]request to GET / crashed: YAML Erro +r: Invalid element in map Code: YAML_LOAD_ERR_BAD_MAP_ELEMENT Line: 315 Document: 1 at /opt/perl5.14/lib/site_perl/5.14.2/YAML/ line 352. in /op +t/perl5.14/lib/site_perl/5.14.2/Dancer/ l. 98
The application uses Dancer::Session::YAML to create and store persistent sessions. I easily identified the problematic session file (I add access time to a session) and found the problem at the end of the file:
... question: - "You can select more than one answer.\n" e answer.\n"
Ouch! Seems like the file has been overwritten without being truncated first. The application runs on Starman which forks workers to serve the requests. It seemed to me as if two instances had been trying to write the session file - a race condition. After discussing the issue with a colleague, I inspected the source code of the Dancer::Session::YAML module. The last subroutine was the only one printing anything:
sub flush { my $self = shift; my $session_file = yaml_file( $self->id ); open my $fh, '>', $session_file or die "Can't open '$session_file' +: $!\n"; flock $fh, LOCK_EX or die "Can't lock file '$session_file': $!\n"; set_file_mode($fh); print {$fh} YAML::Dump($self); close $fh or die "Can't close '$session_file': $!\n"; return $self; }
The problem is the open line: it might overwrite (clobber) the file even before the lock is granted. To demonstrate the problem, I wrote the following script:
#!/usr/bin/perl use warnings; use strict; use Fcntl qw(:flock :DEFAULT); my $pid = fork; die "Cannot fork.\n" unless defined $pid; if ($pid) { open my $parent, '>', 'tmp' or die "open parent: $!"; sleep 1; flock $parent, LOCK_EX or die "lock parent: $!"; print {$parent} "Parent\n"; close $parent or die "close parent: $!"; } else { open my $child, '>', 'tmp' or die "open child: $!"; flock $child, LOCK_EX or die "lock child: $!"; # Longer than parent: print {$child} "Child here...\n"; close $child or die "close child: $!"; }
The parent opens the file, but while it sleeps, the child writes something to it. The parent then wakes up, gets the lock and writes to the file - but it just writes over whatever the child has written. If the child's output is longer than the parent's, the invalid session problem turns up.

I have just read the chapter on file-locking in Programming Perl (the 4th edition). To obtain the exclusive lock, the book recommends using sysopen, but after some googling and experimenting I found a simpler solution (only works if the file already exists, though, otherwise sysopen is inevitable — or maybe +>> can help?):

open my $fh, '+<', $file or die "Can't open '$file': $!\n"; flock $fh, LOCK_EX or die "Can't lock file '$file': $!\n"; truncate $fh, 0; print {$fh} $content; close $fh or die "Can't close '$file': $!\n";
The +< mode does not overwrite the file. truncate deletes the previous content of the file (or shortens the file), so the previous content does not matter.

Proud of myself, I opened the Dancer's bug tracker... only to find the problem has already been fixed on github in a completely different way: the session is written through the Dancer::FileUtil module, using its atomic_write:

sub atomic_write { my ($path, $file, $data) = @_; my ($fh, $filename) = tempfile("tmpXXXXXXXXX", DIR => $path); set_file_mode($fh); print $fh $data; close $fh or die "Can't close '$file': $!\n"; rename($filename, $file) or die "Can't move '$filename' to '$file' +"; }
There is more than one way to... you know what. Anyway, I learned a lot.
Update: The note on file existence with +<.
لսႽ ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

In reply to Race condition in Dancer by choroba

Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":

  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?

    What's my password?
    Create A New User
    and all is quiet...

    How do I use this? | Other CB clients
    Other Users?
    Others chilling in the Monastery: (9)
    As of 2018-06-25 20:33 GMT
    Find Nodes?
      Voting Booth?
      Should cpanminus be part of the standard Perl release?

      Results (128 votes). Check out past polls.