syncache 1.0.0 → 1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +676 -0
- data/ChangeLog.mtn +183 -0
- data/README.rdoc +21 -14
- data/bin/syncache-drb +204 -0
- data/lib/syncache/remote.rb +89 -0
- data/lib/syncache/syncache.rb +283 -0
- data/lib/syncache.rb +3 -248
- data/man/syncache-drb.1 +44 -0
- data/setup.rb +1360 -0
- data/syncache.gemspec +24 -62
- data/test/tc_remote.rb +86 -0
- data/test/{test_syncache.rb → tc_syncache.rb} +5 -8
- data/test/ts_syncache.rb +15 -0
- metadata +49 -57
- data/.document +0 -5
- data/.gitignore +0 -21
- data/LICENSE +0 -20
- data/Rakefile +0 -63
- data/VERSION +0 -1
- data/lib/syncache_sync_patch.rb +0 -36
- data/test/helper.rb +0 -9
data/ChangeLog.mtn
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
-----------------------------------------------------------------
|
2
|
+
Revision: 8bc8efa1310743cf39e14bdf69d4429cadebcb61
|
3
|
+
Ancestor: 6f9a39297f610f7095473ac34f68674963561d9c
|
4
|
+
Author: angdraug@debian.org
|
5
|
+
Date: 25/12/09 19:56:53
|
6
|
+
Branch: syncache-head
|
7
|
+
|
8
|
+
Modified files:
|
9
|
+
bin/syncache-drb man/syncache-drb.1
|
10
|
+
|
11
|
+
ChangeLog:
|
12
|
+
|
13
|
+
man page updated for new options
|
14
|
+
|
15
|
+
-----------------------------------------------------------------
|
16
|
+
Revision: 6f9a39297f610f7095473ac34f68674963561d9c
|
17
|
+
Ancestor: 57f2c6cd4fbe8a04750aebeff72b094dc4d24476
|
18
|
+
Author: angdraug@debian.org
|
19
|
+
Date: 25/12/09 19:46:42
|
20
|
+
Branch: syncache-head
|
21
|
+
|
22
|
+
Modified files:
|
23
|
+
bin/syncache-drb
|
24
|
+
|
25
|
+
ChangeLog:
|
26
|
+
|
27
|
+
New options: --user, --error-log
|
28
|
+
|
29
|
+
* Redirect STDERR to an error log when specified.
|
30
|
+
* Allow to specify any to run as user instead of just nobody.
|
31
|
+
* Create the pidfile under /var/run/syncache-drb to solve the
|
32
|
+
undeletable pidfile problem.
|
33
|
+
|
34
|
+
-----------------------------------------------------------------
|
35
|
+
Revision: 57f2c6cd4fbe8a04750aebeff72b094dc4d24476
|
36
|
+
Ancestor: 0b3852cfbf276589043a4bb87c37ab024df694cc
|
37
|
+
Author: angdraug@debian.org
|
38
|
+
Date: 26/10/09 20:08:45
|
39
|
+
Branch: syncache-head
|
40
|
+
|
41
|
+
Modified files:
|
42
|
+
bin/syncache-drb
|
43
|
+
|
44
|
+
ChangeLog:
|
45
|
+
|
46
|
+
untaint URI supplied on command line; report exception backtraces to syslog
|
47
|
+
|
48
|
+
-----------------------------------------------------------------
|
49
|
+
Revision: 0b3852cfbf276589043a4bb87c37ab024df694cc
|
50
|
+
Ancestor: 69d9bb2d250a211e52baab031e5f840032fdee74
|
51
|
+
Author: angdraug@debian.org
|
52
|
+
Date: 07/08/09 14:08:50
|
53
|
+
Branch: syncache-head
|
54
|
+
|
55
|
+
Modified files:
|
56
|
+
lib/syncache.rb lib/syncache_sync_patch.rb
|
57
|
+
|
58
|
+
ChangeLog:
|
59
|
+
|
60
|
+
expand syncache_sync_patch to fix the same bug in Ruby 1.9.0, move RUBY_VERSION condition into the patch file
|
61
|
+
|
62
|
+
-----------------------------------------------------------------
|
63
|
+
Revision: 69d9bb2d250a211e52baab031e5f840032fdee74
|
64
|
+
Ancestor: de7c67553a07c221dc2064cc19a64c32286c72ae
|
65
|
+
Author: angdraug@debian.org
|
66
|
+
Date: 28/07/09 17:45:55
|
67
|
+
Branch: syncache-head
|
68
|
+
|
69
|
+
Added files:
|
70
|
+
test/ts_syncache.rb
|
71
|
+
Added directories:
|
72
|
+
test
|
73
|
+
|
74
|
+
ChangeLog:
|
75
|
+
|
76
|
+
unit test re-added from Samizdat::Cache
|
77
|
+
|
78
|
+
-----------------------------------------------------------------
|
79
|
+
Revision: de7c67553a07c221dc2064cc19a64c32286c72ae
|
80
|
+
Ancestor: f95d143aed0ae773800a96cafb4e1b9eb87cc407
|
81
|
+
Author: angdraug@debian.org
|
82
|
+
Date: 28/07/09 17:29:16
|
83
|
+
Branch: syncache-head
|
84
|
+
|
85
|
+
Modified files:
|
86
|
+
bin/syncache-drb
|
87
|
+
|
88
|
+
ChangeLog:
|
89
|
+
|
90
|
+
fixes in handling of syncache-drb options
|
91
|
+
|
92
|
+
-----------------------------------------------------------------
|
93
|
+
Revision: f95d143aed0ae773800a96cafb4e1b9eb87cc407
|
94
|
+
Ancestor: ed2cc574a6324a95a69f81c4a3ed3e04bd26ade4
|
95
|
+
Author: angdraug@debian.org
|
96
|
+
Date: 27/07/09 17:50:10
|
97
|
+
Branch: syncache-head
|
98
|
+
|
99
|
+
Modified files:
|
100
|
+
README.rdoc
|
101
|
+
|
102
|
+
ChangeLog:
|
103
|
+
|
104
|
+
updated README.rdoc to reflect renamed DRb server script
|
105
|
+
|
106
|
+
-----------------------------------------------------------------
|
107
|
+
Revision: ed2cc574a6324a95a69f81c4a3ed3e04bd26ade4
|
108
|
+
Ancestor: e9c480978fdb812474c40a785d7b4cae8aa77c58
|
109
|
+
Author: angdraug@debian.org
|
110
|
+
Date: 27/07/09 17:32:17
|
111
|
+
Branch: syncache-head
|
112
|
+
|
113
|
+
Renamed entries:
|
114
|
+
man/syncache-drb-server.1 to man/syncache-drb.1
|
115
|
+
Modified files:
|
116
|
+
man/syncache-drb.1
|
117
|
+
|
118
|
+
ChangeLog:
|
119
|
+
|
120
|
+
renamed the samizdat-drb-server.1 manpage along with the script
|
121
|
+
|
122
|
+
-----------------------------------------------------------------
|
123
|
+
Revision: e9c480978fdb812474c40a785d7b4cae8aa77c58
|
124
|
+
Ancestor: e6c0480854ee7af7f7f9956d9c6489452ec99a65
|
125
|
+
Author: angdraug@debian.org
|
126
|
+
Date: 27/07/09 17:16:05
|
127
|
+
Branch: syncache-head
|
128
|
+
|
129
|
+
Renamed entries:
|
130
|
+
bin/syncache-drb-server to bin/syncache-drb
|
131
|
+
Modified files:
|
132
|
+
bin/syncache-drb
|
133
|
+
|
134
|
+
ChangeLog:
|
135
|
+
|
136
|
+
renamed syncache-drb-server to syncache-drb to make it fit within 15 character limit
|
137
|
+
|
138
|
+
-----------------------------------------------------------------
|
139
|
+
Revision: e6c0480854ee7af7f7f9956d9c6489452ec99a65
|
140
|
+
Ancestor: 6818ae15675fa101beb33c7fc21635e404a09fbe
|
141
|
+
Author: angdraug@debian.org
|
142
|
+
Date: 27/07/09 16:37:53
|
143
|
+
Branch: syncache-head
|
144
|
+
|
145
|
+
Modified files:
|
146
|
+
lib/syncache.rb
|
147
|
+
|
148
|
+
ChangeLog:
|
149
|
+
|
150
|
+
conditionally load monkey patch for the sync.rb bug only for Ruby versions < 1.8.7p173
|
151
|
+
|
152
|
+
-----------------------------------------------------------------
|
153
|
+
Revision: 6818ae15675fa101beb33c7fc21635e404a09fbe
|
154
|
+
Ancestor: 56a8b045280179815127c312f703c55bc63d7081
|
155
|
+
Author: angdraug@debian.org
|
156
|
+
Date: 27/07/09 15:57:09
|
157
|
+
Branch: syncache-head
|
158
|
+
|
159
|
+
Modified files:
|
160
|
+
bin/syncache-drb-server
|
161
|
+
|
162
|
+
ChangeLog:
|
163
|
+
|
164
|
+
fixed Samizdat legacy: cache -> @cache
|
165
|
+
|
166
|
+
-----------------------------------------------------------------
|
167
|
+
Revision: 56a8b045280179815127c312f703c55bc63d7081
|
168
|
+
Ancestor:
|
169
|
+
Author: angdraug@debian.org
|
170
|
+
Date: 27/07/09 15:40:15
|
171
|
+
Branch: syncache-head
|
172
|
+
|
173
|
+
Added files:
|
174
|
+
COPYING README.rdoc bin/syncache-drb-server lib/syncache.rb
|
175
|
+
lib/syncache_sync_patch.rb man/syncache-drb-server.1
|
176
|
+
setup.rb
|
177
|
+
Added directories:
|
178
|
+
. bin lib man
|
179
|
+
|
180
|
+
ChangeLog:
|
181
|
+
|
182
|
+
initial checkin: SynCache is a spin-off of cache.rb from Samizdat project
|
183
|
+
|
data/README.rdoc
CHANGED
@@ -42,8 +42,8 @@ overriding its #replacement_index method.
|
|
42
42
|
Cache entries are automatically invalidated when their +ttl+ (time to
|
43
43
|
live) is exceeded. Entries can be explicitly invalidated by #flush
|
44
44
|
method. The method can use <tt>===</tt> operator to compare cache keys
|
45
|
-
against flush base (so that base can be e.g. a Regexp)
|
46
|
-
|
45
|
+
against flush base (so that base can be e.g. a Regexp). When invoked
|
46
|
+
without the +base+ parameter, it invalidates all entries.
|
47
47
|
|
48
48
|
The +flush_delay+ initialization option allows to limit cache's flush
|
49
49
|
rate. When this option is set, SynCache will make sure that at least
|
@@ -70,22 +70,29 @@ To access a remote cache, you will need to use DRb library:
|
|
70
70
|
# allow remote cache to access local objects from fetch_or_add blocks
|
71
71
|
DRb.start_service('druby://localhost:0')
|
72
72
|
|
73
|
+
For the above to work properly, all your fetch_or_add blocks should be
|
74
|
+
thread-safe, because DRb will run them in their own threads. Also, if a
|
75
|
+
Ruby process crashes is the middle of such block, the key will remain
|
76
|
+
locked in the remote cache until its +ttl+ runs out.
|
77
|
+
|
78
|
+
To work around these limitations, you can wrap access to a remote cache
|
79
|
+
using a SynCache::RemoteCache object:
|
80
|
+
|
81
|
+
require 'syncache'
|
82
|
+
|
83
|
+
# connect to the remote cache
|
84
|
+
@cache = SynCache::RemoteCache.new('druby://localhost:9000')
|
85
|
+
|
86
|
+
SynCache::RemoteCache implements its own version of fetch_or_add that
|
87
|
+
runs the supplied block locally in the current thread and would give up
|
88
|
+
and take over a locked key if the client that originally locked it takes
|
89
|
+
too long to free it up.
|
90
|
+
|
73
91
|
|
74
92
|
== Copying
|
75
93
|
|
76
|
-
Copyright (c) 2002-
|
94
|
+
Copyright (c) 2002-2011 Dmitry Borodaenko <angdraug@debian.org>
|
77
95
|
|
78
96
|
This program is free software.
|
79
97
|
You can distribute/modify this program under the terms of the GNU
|
80
98
|
General Public License version 3 or later.
|
81
|
-
|
82
|
-
|
83
|
-
== Note on Patches/Pull Requests
|
84
|
-
|
85
|
-
* Fork the project.
|
86
|
-
* Make your feature addition or bug fix.
|
87
|
-
* Add tests for it. This is important so I don't break it in a
|
88
|
-
future version unintentionally.
|
89
|
-
* Commit, do not mess with rakefile, version, or history.
|
90
|
-
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
91
|
-
* Send me a pull request. Bonus points for topic branches.
|
data/bin/syncache-drb
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# SynCache dRuby object cache server
|
4
|
+
# (originally written for Samizdat project)
|
5
|
+
#
|
6
|
+
# Copyright (c) 2002-2011 Dmitry Borodaenko <angdraug@debian.org>
|
7
|
+
#
|
8
|
+
# This program is free software.
|
9
|
+
# You can distribute/modify this program under the terms of
|
10
|
+
# the GNU General Public License version 3 or later.
|
11
|
+
#
|
12
|
+
# vim: et sw=2 sts=2 ts=8 tw=0
|
13
|
+
|
14
|
+
require 'getoptlong'
|
15
|
+
require 'etc'
|
16
|
+
require 'syslog'
|
17
|
+
require 'drb'
|
18
|
+
require 'syncache'
|
19
|
+
|
20
|
+
class SynCacheDRb
|
21
|
+
PNAME = 'syncache-drb'
|
22
|
+
|
23
|
+
def usage
|
24
|
+
puts %{
|
25
|
+
Usage: #{PNAME} [ options ] [ URI ]
|
26
|
+
|
27
|
+
Options:
|
28
|
+
|
29
|
+
URI
|
30
|
+
URI with druby: schema that DRb server binds to, default is
|
31
|
+
druby://localhost:9000
|
32
|
+
|
33
|
+
--help
|
34
|
+
display this message and quit
|
35
|
+
|
36
|
+
--ttl SECONDS
|
37
|
+
time-to-live value for cache entries, default is 24 hours
|
38
|
+
|
39
|
+
--size ENTRIES
|
40
|
+
maximum number of objects in cache, default is 10000
|
41
|
+
|
42
|
+
--flush-delay SECONDS
|
43
|
+
rate-limit flush operations: if less than that number of seconds
|
44
|
+
has passed since last flush, next flush will be delayed; default
|
45
|
+
is no rate limit
|
46
|
+
|
47
|
+
--user USER
|
48
|
+
Run as USER if started as root. Default is nobody.
|
49
|
+
|
50
|
+
--error-log ERROR_LOG_PATH
|
51
|
+
File to write errors to. Default is /dev/null. When run as root,
|
52
|
+
the file is chowned to USER:adm.
|
53
|
+
|
54
|
+
--debug
|
55
|
+
Enable debug mode. If an error log is specified with --error-log,
|
56
|
+
all messages will be sent there instead of syslog.
|
57
|
+
|
58
|
+
--pidfile PATH
|
59
|
+
Path to the pidfile. By default, pidfile is created under
|
60
|
+
/var/run/#{PNAME}/ when run as root, or under $TMPDIR
|
61
|
+
otherwise. Location should be writeable by USER.
|
62
|
+
|
63
|
+
}
|
64
|
+
exit
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_options
|
68
|
+
opts = GetoptLong.new(
|
69
|
+
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
70
|
+
[ '--ttl', '-t', GetoptLong::REQUIRED_ARGUMENT ],
|
71
|
+
[ '--size', '-s', GetoptLong::REQUIRED_ARGUMENT ],
|
72
|
+
[ '--flush-delay', '-f', GetoptLong::REQUIRED_ARGUMENT ],
|
73
|
+
[ '--user', '-u', GetoptLong::REQUIRED_ARGUMENT ],
|
74
|
+
[ '--error-log', '-e', GetoptLong::REQUIRED_ARGUMENT ],
|
75
|
+
[ '--debug', '-d', GetoptLong::NO_ARGUMENT ],
|
76
|
+
[ '--pidfile', '-p', GetoptLong::REQUIRED_ARGUMENT ]
|
77
|
+
)
|
78
|
+
|
79
|
+
@uri = 'druby://localhost:9000'
|
80
|
+
@ttl = 24*60*60
|
81
|
+
@size = 10000
|
82
|
+
@flush_delay = nil
|
83
|
+
@user = 'nobody'
|
84
|
+
@error_log = '/dev/null'
|
85
|
+
@debug = false
|
86
|
+
@pidfile = (0 == Process.uid) ?
|
87
|
+
"/var/run/#{PNAME}/#{PNAME}.pid" :
|
88
|
+
(ENV.has_key?('TMPDIR') ? ENV['TMPDIR'] : '/tmp') + "/#{PNAME}.pid"
|
89
|
+
|
90
|
+
opts.each do |opt, arg|
|
91
|
+
case opt
|
92
|
+
when '--help'
|
93
|
+
usage
|
94
|
+
when '--ttl'
|
95
|
+
@ttl = arg.to_i
|
96
|
+
when '--size'
|
97
|
+
@size = arg.to_i
|
98
|
+
when '--flush-delay'
|
99
|
+
@flush_delay = arg.to_i
|
100
|
+
when '--user'
|
101
|
+
@user = arg.dup.untaint
|
102
|
+
when '--error-log'
|
103
|
+
@error_log = arg.dup.untaint
|
104
|
+
when '--debug'
|
105
|
+
@debug = true
|
106
|
+
when '--pidfile'
|
107
|
+
@pidfile = arg.dup.untaint
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
@uri = ARGV[0].dup.untaint if ARGV[0]
|
112
|
+
@user = Etc.getpwnam(@user)
|
113
|
+
end
|
114
|
+
|
115
|
+
def log(message, level = :info)
|
116
|
+
if @log.respond_to? level
|
117
|
+
@log.send(level, message)
|
118
|
+
else
|
119
|
+
STDERR << 'syncache: ' + message + "\n"
|
120
|
+
STDERR.flush
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def daemonize
|
125
|
+
$0 = PNAME
|
126
|
+
ENV.clear
|
127
|
+
exit if fork
|
128
|
+
Process.setsid
|
129
|
+
exit if fork
|
130
|
+
Dir.chdir '/'
|
131
|
+
File.umask 0022
|
132
|
+
|
133
|
+
STDIN.reopen '/dev/null'
|
134
|
+
STDOUT.reopen '/dev/null', 'a'
|
135
|
+
STDERR.reopen @error_log, 'a'
|
136
|
+
|
137
|
+
if 0 == Process.uid
|
138
|
+
if @error_log != '/dev/null'
|
139
|
+
File.chown(@user.uid, Etc.getgrnam('adm').gid, @error_log)
|
140
|
+
File.chmod(0640, @error_log)
|
141
|
+
end
|
142
|
+
|
143
|
+
# drop root priviledge
|
144
|
+
Process::Sys.setgid(@user.gid)
|
145
|
+
Process::Sys.setuid(@user.uid)
|
146
|
+
end
|
147
|
+
|
148
|
+
File.open(@pidfile, 'w') {|f| f.puts Process.pid }
|
149
|
+
end
|
150
|
+
|
151
|
+
def open_syslog
|
152
|
+
@log = Syslog.open PNAME
|
153
|
+
end
|
154
|
+
|
155
|
+
def ready
|
156
|
+
@cache = SynCache::Cache.new(@ttl, @size, @flush_delay) # initialize cache
|
157
|
+
@cache.debug = true if @debug
|
158
|
+
end
|
159
|
+
|
160
|
+
def trap_signals
|
161
|
+
shutdown = lambda do |sig|
|
162
|
+
Thread.new do
|
163
|
+
sleep 0.1
|
164
|
+
DRb.stop_service
|
165
|
+
end
|
166
|
+
end
|
167
|
+
Signal.trap('INT', shutdown)
|
168
|
+
Signal.trap('TERM', shutdown)
|
169
|
+
Signal.trap('HUP') do |sig| # kill -HUP to flush cache
|
170
|
+
@cache.flush
|
171
|
+
log 'cache flushed'
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def start_service
|
176
|
+
$SAFE = 1
|
177
|
+
DRb.start_service(@uri, @cache)
|
178
|
+
log 'started'
|
179
|
+
end
|
180
|
+
|
181
|
+
def wait_for_shutdown
|
182
|
+
DRb.thread.join
|
183
|
+
File.delete @pidfile
|
184
|
+
log 'shut down'
|
185
|
+
end
|
186
|
+
|
187
|
+
def run
|
188
|
+
set_options
|
189
|
+
daemonize
|
190
|
+
open_syslog unless @debug and @error_log
|
191
|
+
begin
|
192
|
+
ready
|
193
|
+
trap_signals
|
194
|
+
start_service
|
195
|
+
wait_for_shutdown
|
196
|
+
rescue => error
|
197
|
+
log "#{error.class.to_s}: #{error.to_s}", :err
|
198
|
+
error.backtrace.each {|line| log(' ' + line, :err) }
|
199
|
+
raise
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
SynCacheDRb.new.run
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# SynCache: thread-safe time-limited cache with flexible replacement policy
|
2
|
+
# (originally written for Samizdat project)
|
3
|
+
#
|
4
|
+
# Copyright (c) 2002-2011 Dmitry Borodaenko <angdraug@debian.org>
|
5
|
+
#
|
6
|
+
# This program is free software.
|
7
|
+
# You can distribute/modify this program under the terms of
|
8
|
+
# the GNU General Public License version 3 or later.
|
9
|
+
#
|
10
|
+
# vim: et sw=2 sts=2 ts=8 tw=0
|
11
|
+
|
12
|
+
require 'drb'
|
13
|
+
require 'delegate'
|
14
|
+
|
15
|
+
module SynCache
|
16
|
+
|
17
|
+
REMOTE_TIMEOUT = 60 * 5 # 5 minutes
|
18
|
+
REMOTE_FIRST_DELAY = 0.1 # 100 ms
|
19
|
+
|
20
|
+
class Placeholder
|
21
|
+
def initialize
|
22
|
+
@id = rand(9223372036854775808)
|
23
|
+
@timestamp = Time.now
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :id, :timestamp
|
27
|
+
|
28
|
+
def ===(other)
|
29
|
+
other.kind_of?(Placeholder) and other.id == @id
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Connects to a remote SynCache instance over DRb at the provided URI and
|
34
|
+
# replaces the remote fetch_or_add method with a slightly less bullet-proof
|
35
|
+
# version that invokes the supplied block locally (instead of sending it over
|
36
|
+
# DRb to the cache and then back to a different local thread via a local DRb
|
37
|
+
# service).
|
38
|
+
#
|
39
|
+
# If another RemoteCache client is already working on the same key, this client
|
40
|
+
# will wait, using randomly increasing intervals. When a configured timeout
|
41
|
+
# runs out (counting from the time the other client has put a placeholder in
|
42
|
+
# the cache), the client will give up, discard the other client's placeholder
|
43
|
+
# and start working on the key itself.
|
44
|
+
#
|
45
|
+
# Mixing access to the same cache entries from direct and RemoteCache clients
|
46
|
+
# is not recommended.
|
47
|
+
#
|
48
|
+
class RemoteCache
|
49
|
+
def initialize(uri, timeout=REMOTE_TIMEOUT, first_delay=REMOTE_FIRST_DELAY)
|
50
|
+
@timeout = timeout
|
51
|
+
@first_delay = first_delay
|
52
|
+
@cache = DRbObject.new_with_uri(uri)
|
53
|
+
end
|
54
|
+
|
55
|
+
def method_missing(method, *args)
|
56
|
+
@cache.send(method, *args)
|
57
|
+
end
|
58
|
+
|
59
|
+
def fetch_or_add(key)
|
60
|
+
placeholder = Placeholder.new
|
61
|
+
value = @cache.fetch_or_add(key) { placeholder }
|
62
|
+
|
63
|
+
case value
|
64
|
+
when placeholder
|
65
|
+
# our placeholder
|
66
|
+
value = @cache[key] = yield
|
67
|
+
|
68
|
+
when Placeholder
|
69
|
+
# someone else's placeholder
|
70
|
+
delay = @first_delay
|
71
|
+
while value.kind_of?(Placeholder) and Time.now < value.timestamp + @timeout
|
72
|
+
sleep(delay)
|
73
|
+
delay *= 1 + rand
|
74
|
+
value = @cache[key]
|
75
|
+
end
|
76
|
+
|
77
|
+
if value.kind_of?(Placeholder)
|
78
|
+
# ran out of time: give up and do it ourselves
|
79
|
+
@cache.delete(key)
|
80
|
+
@cache[key] = Placeholder.new
|
81
|
+
value = @cache[key] = yield
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end # module SynCache
|