1d32810e93b705970ad68ac87ac1137fa805cbaa
[git/jnareb-git.git] / gitweb / lib / GitwebCache / FileCacheWithLocking.pm
blob1d32810e93b705970ad68ac87ac1137fa805cbaa
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);
21 use strict;
22 use warnings;
24 use File::Path qw(mkpath);
25 use Fcntl qw(:flock);
27 # ......................................................................
28 # constructor
30 # The options are set by passing in a reference to a hash containing
31 # any of the following keys:
32 # * 'namespace'
33 # The namespace associated with this cache. This allows easy separation of
34 # multiple, distinct caches without worrying about key collision. Defaults
35 # to $DEFAULT_NAMESPACE.
36 # * 'cache_root' (Cache::FileCache compatibile),
37 # 'root_dir' (CHI::Driver::File compatibile),
38 # The location in the filesystem that will hold the root of the cache.
39 # Defaults to $DEFAULT_CACHE_ROOT.
40 # * 'cache_depth' (Cache::FileCache compatibile),
41 # 'depth' (CHI::Driver::File compatibile),
42 # The number of subdirectories deep to cache object item. This should be
43 # large enough that no cache directory has more than a few hundred objects.
44 # Defaults to $DEFAULT_CACHE_DEPTH unless explicitly set.
45 # * 'default_expires_in' (Cache::Cache compatibile),
46 # 'expires_in' (CHI compatibile) [seconds]
47 # The expiration time for objects place in the cache.
48 # Defaults to -1 (never expire) if not explicitly set.
49 # Sets 'expires_min' to given value.
50 # * 'expires_min' [seconds]
51 # The minimum expiration time for objects in cache (e.g. with 0% CPU load).
52 # Used as lower bound in adaptive cache lifetime / expiration.
53 # Defaults to 20 seconds; 'expires_in' sets it also.
54 # * 'expires_max' [seconds]
55 # The maximum expiration time for objects in cache.
56 # Used as upper bound in adaptive cache lifetime / expiration.
57 # Defaults to 1200 seconds, if not set;
58 # defaults to 'expires_min' if 'expires_in' is used.
59 # * 'check_load'
60 # Subroutine (code) used for adaptive cache lifetime / expiration.
61 # If unset, adaptive caching is turned off; defaults to unset.
62 # * 'increase_factor' [seconds / 100% CPU load]
63 # Factor multiplying 'check_load' result when calculating cache lietime.
64 # Defaults to 60 seconds for 100% SPU load ('check_load' returning 1.0).
66 # (all the above are inherited from GitwebCache::SimpleFileCache)
68 # * 'max_lifetime' [seconds]
69 # If it is greater than 0, and cache entry is expired but not older
70 # than it, serve stale data when waiting for cache entry to be
71 # regenerated (refreshed). Non-adaptive.
72 # Defaults to -1 (never expire / always serve stale).
73 sub new {
74 my $class = shift;
75 my %opts = ref $_[0] ? %{ $_[0] } : @_;
77 my $self = $class->SUPER::new(\%opts);
79 my ($max_lifetime);
80 if (%opts) {
81 $max_lifetime =
82 $opts{'max_lifetime'} ||
83 $opts{'max_cache_lifetime'};
85 $max_lifetime = -1 unless defined($max_lifetime);
87 $self->set_max_lifetime($max_lifetime);
89 return $self;
92 # ......................................................................
93 # accessors
95 # http://perldesignpatterns.com/perldesignpatterns.html#AccessorPattern
97 # creates get_depth() and set_depth($depth) etc. methods
98 foreach my $i (qw(max_lifetime)) {
99 my $field = $i;
100 no strict 'refs';
101 *{"get_$field"} = sub {
102 my $self = shift;
103 return $self->{$field};
105 *{"set_$field"} = sub {
106 my ($self, $value) = @_;
107 $self->{$field} = $value;
111 # ----------------------------------------------------------------------
112 # utility functions and methods
114 # Take an human readable key, and return path to be used for lockfile
115 # Ensures that file can be created, if needed.
116 sub get_lockname {
117 my ($self, $key) = @_;
119 my $lockfile = $self->path_to_key($key, \my $dir) . '.lock';
121 # ensure that directory leading to lockfile exists
122 if (!-d $dir) {
123 eval { mkpath($dir, 0, 0777); 1 }
124 or die "Couldn't mkpath '$dir' for lockfile: $!";
127 return $lockfile;
130 # ----------------------------------------------------------------------
131 # "private" utility functions and methods
133 # take a file path to cache entry, and its directory
134 # return filehandle and filename of open temporary file,
135 # like File::Temp::tempfile
136 sub _tempfile_to_path {
137 my ($self, $file, $dir) = @_;
139 my $tempname = "$file.tmp";
140 open my $temp_fh, '>', $tempname
141 or die "Couldn't open temporary file '$tempname' for writing: $!";
143 return ($temp_fh, $tempname);
146 # ......................................................................
147 # interface methods
149 sub _compute_generic {
150 my ($self, $key,
151 $get_code, $fetch_code, $set_code, $fetch_locked) = @_;
153 my @result = $get_code->();
154 return @result if @result;
156 my $lockfile = $self->get_lockname($key);
158 # this loop is to protect against situation where process that
159 # acquired exclusive lock (writer) dies or exits (die_error)
160 # before writing data to cache
161 my $lock_state; # needed for loop condition
162 do {
163 open my $lock_fh, '+>', $lockfile
164 or die "Could't open lockfile '$lockfile': $!";
165 $lock_state = flock($lock_fh, LOCK_EX | LOCK_NB);
166 if ($lock_state) {
167 # acquired writers lock
168 @result = $set_code->();
170 # closing lockfile releases lock
171 close $lock_fh
172 or die "Could't close lockfile '$lockfile': $!";
174 } else {
175 # try to retrieve stale data
176 @result = $fetch_code->()
177 if $self->is_valid($key, $self->get_max_lifetime());
178 return @result if @result;
180 # get readers lock (wait for writer)
181 # if there is no stale data to serve
182 flock($lock_fh, LOCK_SH);
183 # closing lockfile releases lock
184 if ($fetch_locked) {
185 @result = $fetch_code->();
186 close $lock_fh
187 or die "Could't close lockfile '$lockfile': $!";
188 } else {
189 close $lock_fh
190 or die "Could't close lockfile '$lockfile': $!";
191 @result = $fetch_code->();
194 } until (@result || $lock_state);
195 # repeat until we have data, or we tried generating data oneself and failed
196 return @result;
199 # $data = $cache->compute($key, $code);
201 # Combines the get and set operations in a single call. Attempts to
202 # get $key; if successful, returns the value. Otherwise, calls $code
203 # and uses the return value as the new value for $key, which is then
204 # returned.
206 # Uses file locking to have only one process updating value for $key
207 # to avoid 'cache miss stampede' (aka 'stampeding herd') problem.
208 sub compute {
209 my ($self, $key, $code) = @_;
211 return ($self->_compute_generic($key,
212 sub {
213 return $self->get($key);
215 sub {
216 return $self->fetch($key);
218 sub {
219 my $data = $code->();
220 $self->set($key, $data);
221 return $data;
223 0 # $self->get($key); is outside LOCK_SH critical section
224 ))[0]; # return single value: $data
227 # ($fh, $filename) = $cache->compute_fh($key, $code);
229 # Combines the get and set operations in a single call. Attempts to
230 # get $key; if successful, returns the filehandle it can be read from.
231 # Otherwise, calls $code passing filehandle to write to as a
232 # parameter; contents of this file is then used as the new value for
233 # $key; returns filehandle from which one can read newly generated data.
235 # Uses file locking to have only one process updating value for $key
236 # to avoid 'cache miss stampede' (aka 'stampeding herd') problem.
237 sub compute_fh {
238 my ($self, $key, $code_fh) = @_;
240 return $self->_compute_generic($key,
241 sub {
242 return $self->get_fh($key);
244 sub {
245 return $self->fetch_fh($key);
247 sub {
248 return $self->set_coderef_fh($key, $code_fh);
250 1 # $self->fetch_fh($key); just opens file
255 __END__
256 # end of package GitwebCache::FileCacheWithLocking