1 # gitweb - simple web interface to track changes in git repositories
3 # (C) 2006, John 'Warthog9' Hawley <warthog19@eaglescrag.net>
4 # (C) 2010, Jakub Narebski <jnareb@gmail.com>
6 # This program is licensed under the GPLv2
9 # Gitweb caching engine, simple file-based cache, with locking
12 # Based on GitwebCache::SimpleFileCache, minimalistic cache that
13 # stores data in the filesystem, without serialization.
15 # It uses file locks (flock) to have only one process generating data
16 # and writing to cache, when using CHI interface ->compute() method.
18 package GitwebCache
::FileCacheWithLocking
;
19 use base
qw(GitwebCache::SimpleFileCache);
24 use File
::Path
qw(mkpath);
27 # ......................................................................
28 # constructor is inherited from GitwebCache::SimpleFileCache
30 # ----------------------------------------------------------------------
31 # utility functions and methods
33 # Take an human readable key, and return path to be used for lockfile
34 # Ensures that file can be created, if needed.
36 my ($self, $key) = @_;
38 my $lockfile = $self->path_to_key($key, \
my $dir) . '.lock';
40 # ensure that directory leading to lockfile exists
42 eval { mkpath
($dir, 0, 0777); 1 }
43 or die "Couldn't mkpath '$dir' for lockfile: $!";
49 # ----------------------------------------------------------------------
50 # "private" utility functions and methods
52 # take a file path to cache entry, and its directory
53 # return filehandle and filename of open temporary file,
54 # like File::Temp::tempfile
55 sub _tempfile_to_path
{
56 my ($self, $file, $dir) = @_;
58 my $tempname = "$file.tmp";
59 open my $temp_fh, '>', $tempname
60 or die "Couldn't open temporary file '$tempname' for writing: $!";
62 return ($temp_fh, $tempname);
65 # ......................................................................
68 sub _compute_generic
{
70 $get_code, $set_code, $get_locked) = @_;
72 my @result = $get_code->();
73 return @result if @result;
75 my $lockfile = $self->get_lockname($key);
77 # this loop is to protect against situation where process that
78 # acquired exclusive lock (writer) dies or exits (die_error)
79 # before writing data to cache
80 my $lock_state; # needed for loop condition
82 open my $lock_fh, '+>', $lockfile
83 or die "Could't open lockfile '$lockfile': $!";
84 $lock_state = flock($lock_fh, LOCK_EX
| LOCK_NB
);
86 # acquired writers lock
87 @result = $set_code->();
89 # closing lockfile releases lock
91 or die "Could't close lockfile '$lockfile': $!";
94 # get readers lock (wait for writer)
95 flock($lock_fh, LOCK_SH
);
96 # closing lockfile releases lock
98 @result = $get_code->();
100 or die "Could't close lockfile '$lockfile': $!";
103 or die "Could't close lockfile '$lockfile': $!";
104 @result = $get_code->();
107 } until (@result || $lock_state);
108 # repeat until we have data, or we tried generating data oneself and failed
112 # $data = $cache->compute($key, $code);
114 # Combines the get and set operations in a single call. Attempts to
115 # get $key; if successful, returns the value. Otherwise, calls $code
116 # and uses the return value as the new value for $key, which is then
119 # Uses file locking to have only one process updating value for $key
120 # to avoid 'cache miss stampede' (aka 'stampeding herd') problem.
122 my ($self, $key, $code) = @_;
124 return ($self->_compute_generic($key,
126 return $self->get($key);
129 my $data = $code->();
130 $self->set($key, $data);
133 0 # $self->get($key); is outside LOCK_SH critical section
134 ))[0]; # return single value: $data
137 # ($fh, $filename) = $cache->compute_fh($key, $code);
139 # Combines the get and set operations in a single call. Attempts to
140 # get $key; if successful, returns the filehandle it can be read from.
141 # Otherwise, calls $code passing filehandle to write to as a
142 # parameter; contents of this file is then used as the new value for
143 # $key; returns filehandle from which one can read newly generated data.
145 # Uses file locking to have only one process updating value for $key
146 # to avoid 'cache miss stampede' (aka 'stampeding herd') problem.
148 my ($self, $key, $code_fh) = @_;
150 return $self->_compute_generic($key,
152 return $self->get_fh($key);
155 return $self->set_coderef_fh($key, $code_fh);
157 1 # $self->get_fh($key); just opens file
163 # end of package GitwebCache::FileCacheWithLocking