gitweb/lib - No need for File::Temp when locking
[git/jnareb-git.git] / gitweb / lib / GitwebCache / FileCacheWithLocking.pm
blob4d8114d75f2f27048d0d72d04af33f28bcf0a861
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 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.
35 sub get_lockname {
36 my ($self, $key) = @_;
38 my $lockfile = $self->path_to_key($key, \my $dir) . '.lock';
40 # ensure that directory leading to lockfile exists
41 if (!-d $dir) {
42 eval { mkpath($dir, 0, 0777); 1 }
43 or die "Couldn't mkpath '$dir' for lockfile: $!";
46 return $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 # ......................................................................
66 # interface methods
68 sub _compute_generic {
69 my ($self, $key,
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
81 do {
82 open my $lock_fh, '+>', $lockfile
83 or die "Could't open lockfile '$lockfile': $!";
84 $lock_state = flock($lock_fh, LOCK_EX | LOCK_NB);
85 if ($lock_state) {
86 # acquired writers lock
87 @result = $set_code->();
89 # closing lockfile releases lock
90 close $lock_fh
91 or die "Could't close lockfile '$lockfile': $!";
93 } else {
94 # get readers lock (wait for writer)
95 flock($lock_fh, LOCK_SH);
96 # closing lockfile releases lock
97 if ($get_locked) {
98 @result = $get_code->();
99 close $lock_fh
100 or die "Could't close lockfile '$lockfile': $!";
101 } else {
102 close $lock_fh
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
109 return @result;
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
117 # returned.
119 # Uses file locking to have only one process updating value for $key
120 # to avoid 'cache miss stampede' (aka 'stampeding herd') problem.
121 sub compute {
122 my ($self, $key, $code) = @_;
124 return ($self->_compute_generic($key,
125 sub {
126 return $self->get($key);
128 sub {
129 my $data = $code->();
130 $self->set($key, $data);
131 return $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.
147 sub compute_fh {
148 my ($self, $key, $code_fh) = @_;
150 return $self->_compute_generic($key,
151 sub {
152 return $self->get_fh($key);
154 sub {
155 return $self->set_coderef_fh($key, $code_fh);
157 1 # $self->get_fh($key); just opens file
162 __END__
163 # end of package GitwebCache::FileCacheWithLocking