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 # ......................................................................
52 sub _compute_generic
{
54 $get_code, $set_code, $get_locked) = @_;
56 my @result = $get_code->();
57 return @result if @result;
59 my $lockfile = $self->get_lockname($key);
61 # this loop is to protect against situation where process that
62 # acquired exclusive lock (writer) dies or exits (die_error)
63 # before writing data to cache
64 my $lock_state; # needed for loop condition
66 open my $lock_fh, '+>', $lockfile
67 or die "Could't open lockfile '$lockfile': $!";
68 $lock_state = flock($lock_fh, LOCK_EX
| LOCK_NB
);
70 # acquired writers lock
71 @result = $set_code->();
73 # closing lockfile releases lock
75 or die "Could't close lockfile '$lockfile': $!";
78 # get readers lock (wait for writer)
79 flock($lock_fh, LOCK_SH
);
80 # closing lockfile releases lock
82 @result = $get_code->();
84 or die "Could't close lockfile '$lockfile': $!";
87 or die "Could't close lockfile '$lockfile': $!";
88 @result = $get_code->();
91 } until (@result || $lock_state);
92 # repeat until we have data, or we tried generating data oneself and failed
96 # $data = $cache->compute($key, $code);
98 # Combines the get and set operations in a single call. Attempts to
99 # get $key; if successful, returns the value. Otherwise, calls $code
100 # and uses the return value as the new value for $key, which is then
103 # Uses file locking to have only one process updating value for $key
104 # to avoid 'cache miss stampede' (aka 'stampeding herd') problem.
106 my ($self, $key, $code) = @_;
108 return ($self->_compute_generic($key,
110 return $self->get($key);
113 my $data = $code->();
114 $self->set($key, $data);
117 0 # $self->get($key); is outside LOCK_SH critical section
118 ))[0]; # return single value: $data
121 # ($fh, $filename) = $cache->compute_fh($key, $code);
123 # Combines the get and set operations in a single call. Attempts to
124 # get $key; if successful, returns the filehandle it can be read from.
125 # Otherwise, calls $code passing filehandle to write to as a
126 # parameter; contents of this file is then used as the new value for
127 # $key; returns filehandle from which one can read newly generated data.
129 # Uses file locking to have only one process updating value for $key
130 # to avoid 'cache miss stampede' (aka 'stampeding herd') problem.
132 my ($self, $key, $code_fh) = @_;
134 return $self->_compute_generic($key,
136 return $self->get_fh($key);
139 return $self->set_coderef_fh($key, $code_fh);
141 1 # $self->get_fh($key); just opens file
147 # end of package GitwebCache::FileCacheWithLocking