higgs 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +82 -0
- data/README +3 -0
- data/lib/higgs/cache.rb +161 -4
- data/lib/higgs/lock.rb +72 -129
- data/lib/higgs/storage.rb +20 -19
- data/lib/higgs/thread.rb +14 -17
- data/lib/higgs/tman.rb +111 -55
- data/lib/higgs/version.rb +4 -4
- data/test/test_cache.rb +5 -3
- data/test/test_cache_mvcc.rb +444 -0
- data/test/test_lock.rb +126 -187
- data/test/test_replication.rb +2 -0
- data/test/test_storage.rb +4 -11
- data/test/test_tman.rb +22 -38
- data/test/test_tman_mvcc.rb +265 -0
- data/test/test_utils_bman.rb +3 -1
- metadata +4 -2
data/ChangeLog
CHANGED
@@ -1,3 +1,85 @@
|
|
1
|
+
2008-01-20 TOKI Yoshinori <toki@freedom.ne.jp>
|
2
|
+
|
3
|
+
* lib/higgs/version.rb: version 0.1.5.
|
4
|
+
|
5
|
+
* lib/higgs/tman.rb (Higgs::TransactionManager class): uncheck
|
6
|
+
nested transaction.
|
7
|
+
|
8
|
+
2008-01-14 TOKI Yoshinori <toki@freedom.ne.jp>
|
9
|
+
|
10
|
+
* merge from development branch of MVCC (higgs_develop_mvcc).
|
11
|
+
|
12
|
+
2008-01-11 TOKI Yoshinori <toki@freedom.ne.jp>
|
13
|
+
|
14
|
+
* lib/higgs/cache.rb (Higgs::MVCCCache class): inner class name is
|
15
|
+
changed.
|
16
|
+
|
17
|
+
2008-01-09 TOKI Yoshinori <toki@freedom.ne.jp>
|
18
|
+
|
19
|
+
* lib/higgs/cache.rb (Higgs::MVCCCache class): lock collision
|
20
|
+
reduced by read-write lock.
|
21
|
+
|
22
|
+
* lib/higgs/storage.rb (Higgs::Storage class): change number lock
|
23
|
+
for MVCC-cache access.
|
24
|
+
|
25
|
+
2008-01-05 TOKI Yoshinori <toki@freedom.ne.jp>
|
26
|
+
|
27
|
+
* lib/higgs/storage.rb (Higgs::Storage class): backing store on
|
28
|
+
update is obsolete. string_only method is obsolete.
|
29
|
+
|
30
|
+
* lib/higgs/tman.rb (Higgs::TransactionManager class): MVCC-transaction.
|
31
|
+
|
32
|
+
2008-01-04 TOKI Yoshinori <toki@freedom.ne.jp>
|
33
|
+
|
34
|
+
* lib/higgs/lock.rb: critical section in transaction.
|
35
|
+
|
36
|
+
* lib/higgs/lock.rb: Higgs::FineGrainLockManager class is
|
37
|
+
obsolete. Higgs::OptimisticLockManager class is new. lock of each
|
38
|
+
type. check_collision method is new. unlock method is obsolete.
|
39
|
+
|
40
|
+
2008-01-01 TOKI Yoshinori <toki@freedom.ne.jp>
|
41
|
+
|
42
|
+
* lib/higgs/cache.rb (Higgs::MVCCCache class): resource of cache
|
43
|
+
accessor is releaseable in transaction.
|
44
|
+
|
45
|
+
2007-12-30 TOKI Yoshinori <toki@freedom.ne.jp>
|
46
|
+
|
47
|
+
* lib/higgs/cache.rb (Higgs::MVCCCache class): added
|
48
|
+
write_old_values method to cache accessor. fixed a bug of each_key
|
49
|
+
method.
|
50
|
+
|
51
|
+
2007-12-24 TOKI Yoshinori <toki@freedom.ne.jp>
|
52
|
+
|
53
|
+
* lib/higgs/cache.rb (Higgs::MVCCCache class): cache of each type.
|
54
|
+
|
55
|
+
* lib/higgs/cache.rb (Higgs::MVCCCache class): MVCC-cache access
|
56
|
+
logic is implemented.
|
57
|
+
|
58
|
+
* lib/higgs/cache.rb (Higgs::MVCCCache class):
|
59
|
+
NOTE: MVCC-cache access order.
|
60
|
+
read access: 1. source storage. -> 2. cache entry.
|
61
|
+
write access: 1. cache entry. -> 2. source storage.
|
62
|
+
(it is assumed that multithreaded read-write access of ruby's Hash
|
63
|
+
is atomic)
|
64
|
+
|
65
|
+
* test/test_cache_mvcc.rb:
|
66
|
+
|
67
|
+
2007-12-05 TOKI Yoshinori <toki@freedom.ne.jp>
|
68
|
+
|
69
|
+
* lib/higgs/storage.rb (Higgs::Storage class): backing store on
|
70
|
+
update.
|
71
|
+
|
72
|
+
2007-12-01 TOKI Yoshinori <toki@freedom.ne.jp>
|
73
|
+
|
74
|
+
* lib/higgs/cache.rb (Higgs::MVCCCache class): cache for
|
75
|
+
Multi-Version Concurrency Control
|
76
|
+
|
77
|
+
* test/test_tman_mvcc.rb: test of MVCC for
|
78
|
+
Higgs::TransactionManager class.
|
79
|
+
|
80
|
+
* development branch of MVCC (higgs_develop_mvcc).
|
81
|
+
branched from trunk:r695.
|
82
|
+
|
1
83
|
2007-11-28 TOKI Yoshinori <toki@freedom.ne.jp>
|
2
84
|
|
3
85
|
* lib/higgs/version.rb: version 0.1.4.
|
data/README
CHANGED
data/lib/higgs/cache.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# = cache utilities
|
2
2
|
#
|
3
3
|
# Author:: $Author: toki $
|
4
|
-
# Date:: $Date:
|
5
|
-
# Revision:: $Revision:
|
4
|
+
# Date:: $Date: 2008-01-14 00:55:53 +0900 (Mon, 14 Jan 2008) $
|
5
|
+
# Revision:: $Revision: 742 $
|
6
6
|
#
|
7
7
|
# == license
|
8
8
|
# :include:LICENSE
|
@@ -16,7 +16,7 @@ module Higgs
|
|
16
16
|
# = cache by Least Recently Used strategy
|
17
17
|
class LRUCache
|
18
18
|
# for ident(1)
|
19
|
-
CVS_ID = '$Id: cache.rb
|
19
|
+
CVS_ID = '$Id: cache.rb 742 2008-01-13 15:55:53Z toki $'
|
20
20
|
|
21
21
|
extend Forwardable
|
22
22
|
|
@@ -73,7 +73,7 @@ module Higgs
|
|
73
73
|
# = multi-thread safe cache
|
74
74
|
class SharedWorkCache
|
75
75
|
# for ident(1)
|
76
|
-
CVS_ID = '$Id: cache.rb
|
76
|
+
CVS_ID = '$Id: cache.rb 742 2008-01-13 15:55:53Z toki $'
|
77
77
|
|
78
78
|
def initialize(cache={}, &work)
|
79
79
|
unless (work) then
|
@@ -109,6 +109,163 @@ module Higgs
|
|
109
109
|
r ? true : false
|
110
110
|
end
|
111
111
|
end
|
112
|
+
|
113
|
+
# = cache for Multi-Version Concurrency Control
|
114
|
+
class MVCCCache
|
115
|
+
extend Forwardable
|
116
|
+
|
117
|
+
def initialize
|
118
|
+
@lock = Mutex.new
|
119
|
+
@snapshots = {}
|
120
|
+
end
|
121
|
+
|
122
|
+
# for debug
|
123
|
+
def_delegator :@snapshots, :empty?
|
124
|
+
|
125
|
+
def write_old_values(cnum, write_list)
|
126
|
+
@lock.synchronize{
|
127
|
+
@snapshots.each_value do |snapshot|
|
128
|
+
lock = snapshot[:lock].write_lock
|
129
|
+
cache = snapshot[:cache]
|
130
|
+
lock.synchronize{
|
131
|
+
for ope, key, type, value in write_list
|
132
|
+
case (ope)
|
133
|
+
when :value
|
134
|
+
if (cache.key? key) then
|
135
|
+
if (cache[key] != :none && ! (cache[key].key? type)) then
|
136
|
+
cache[key][type] = { :value => value, :cnum => cnum }
|
137
|
+
end
|
138
|
+
else
|
139
|
+
cache[key] = { type => { :value => value, :cnum => cnum } }
|
140
|
+
end
|
141
|
+
when :none
|
142
|
+
unless (cache.key? key) then
|
143
|
+
cache[key] = :none
|
144
|
+
end
|
145
|
+
else
|
146
|
+
raise "unknown operation: #{ope}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
}
|
150
|
+
end
|
151
|
+
}
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
|
155
|
+
def ref_count_up(cnum)
|
156
|
+
@lock.synchronize{
|
157
|
+
@snapshots[cnum] = { :lock => ReadWriteLock.new, :cache => {}, :ref_count => 0 } unless (@snapshots.key? cnum)
|
158
|
+
@snapshots[cnum][:ref_count] += 1
|
159
|
+
return @snapshots[cnum][:lock].read_lock, @snapshots[cnum][:cache]
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
def ref_count_down(cnum)
|
164
|
+
@lock.synchronize{
|
165
|
+
@snapshots[cnum][:ref_count] -= 1
|
166
|
+
@snapshots.delete(cnum) if (@snapshots[cnum][:ref_count] == 0)
|
167
|
+
}
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
|
171
|
+
class Snapshot
|
172
|
+
def initialize(parent)
|
173
|
+
@parent = parent
|
174
|
+
end
|
175
|
+
|
176
|
+
def ref_count_up(cnum)
|
177
|
+
@cnum = cnum
|
178
|
+
@lock, @cache = @parent.ref_count_up(@cnum)
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
|
182
|
+
def ref_count_down
|
183
|
+
@cnum or raise 'not initialized'
|
184
|
+
@parent.ref_count_down(@cnum)
|
185
|
+
@cnum = @lock = @cache = nil
|
186
|
+
end
|
187
|
+
|
188
|
+
def change_number
|
189
|
+
@cnum
|
190
|
+
end
|
191
|
+
|
192
|
+
def cached?(key)
|
193
|
+
@cache.key? key
|
194
|
+
end
|
195
|
+
|
196
|
+
def cache_key?(key)
|
197
|
+
(@cache.key? key) && (@cache[key] != :none)
|
198
|
+
end
|
199
|
+
|
200
|
+
def each_cache_key
|
201
|
+
@cache.each_key do |key|
|
202
|
+
if (@cache[key] != :none) then
|
203
|
+
yield(key)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
|
209
|
+
def key?(store, key)
|
210
|
+
(store.key? key) && ! (cached? key) || (cache_key? key)
|
211
|
+
end
|
212
|
+
|
213
|
+
def keys(store)
|
214
|
+
key_set = {}
|
215
|
+
store.each_key do |key|
|
216
|
+
unless (cached? key) then
|
217
|
+
key_set[key] = true
|
218
|
+
end
|
219
|
+
end
|
220
|
+
each_cache_key do |key|
|
221
|
+
key_set[key] = true
|
222
|
+
end
|
223
|
+
key_set.keys
|
224
|
+
end
|
225
|
+
|
226
|
+
def each_key(store)
|
227
|
+
for key in keys(store)
|
228
|
+
yield(key)
|
229
|
+
end
|
230
|
+
nil
|
231
|
+
end
|
232
|
+
|
233
|
+
def fetch(key, type)
|
234
|
+
@lock.synchronize{
|
235
|
+
if (@cache.key? key) then
|
236
|
+
if (@cache[key] != :none) then
|
237
|
+
if (cache_entry = @cache[key][type]) then
|
238
|
+
cache_entry[:value]
|
239
|
+
else
|
240
|
+
yield
|
241
|
+
end
|
242
|
+
else
|
243
|
+
nil
|
244
|
+
end
|
245
|
+
else
|
246
|
+
yield
|
247
|
+
end
|
248
|
+
}
|
249
|
+
end
|
250
|
+
|
251
|
+
def write_old_values(write_list)
|
252
|
+
@parent.write_old_values(@cnum, write_list)
|
253
|
+
nil
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def transaction(cnum)
|
258
|
+
r = nil
|
259
|
+
snapshot = Snapshot.new(self)
|
260
|
+
snapshot.ref_count_up(cnum)
|
261
|
+
begin
|
262
|
+
r = yield(snapshot)
|
263
|
+
ensure
|
264
|
+
snapshot.ref_count_down
|
265
|
+
end
|
266
|
+
r
|
267
|
+
end
|
268
|
+
end
|
112
269
|
end
|
113
270
|
|
114
271
|
# Local Variables:
|
data/lib/higgs/lock.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
# = multi-thread lock manager
|
2
2
|
#
|
3
3
|
# Author:: $Author: toki $
|
4
|
-
# Date:: $Date:
|
5
|
-
# Revision:: $Revision:
|
4
|
+
# Date:: $Date: 2008-01-14 00:55:53 +0900 (Mon, 14 Jan 2008) $
|
5
|
+
# Revision:: $Revision: 742 $
|
6
6
|
#
|
7
7
|
# == license
|
8
8
|
# :include:LICENSE
|
9
9
|
#
|
10
10
|
|
11
|
-
require 'forwardable'
|
12
11
|
require 'higgs/cache'
|
13
12
|
require 'higgs/exceptions'
|
14
13
|
require 'singleton'
|
@@ -18,187 +17,131 @@ module Higgs
|
|
18
17
|
# = multi-thread lock manager
|
19
18
|
class LockManager
|
20
19
|
# for ident(1)
|
21
|
-
CVS_ID = '$Id: lock.rb
|
20
|
+
CVS_ID = '$Id: lock.rb 742 2008-01-13 15:55:53Z toki $'
|
22
21
|
|
23
22
|
include Exceptions
|
24
23
|
|
25
24
|
class Error < HiggsError
|
26
25
|
end
|
27
26
|
|
28
|
-
class
|
27
|
+
class CollisionError < Error
|
29
28
|
end
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
TRY_LOCK_INTERVAL = 0.1
|
34
|
-
|
35
|
-
RAND_GEN = proc{|seed|
|
36
|
-
n = seed
|
37
|
-
cycle = 0xFFFF
|
38
|
-
proc{
|
39
|
-
n = (n * 37549 + 12345) % cycle
|
40
|
-
n.to_f / cycle
|
41
|
-
}
|
42
|
-
}
|
30
|
+
class NoWorkLockHandler
|
31
|
+
include Singleton
|
43
32
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
@try_lock_interval = options[:try_lock_interval] || TRY_LOCK_INTERVAL
|
48
|
-
@rand_gen = options[:random_number_generator] || RAND_GEN
|
49
|
-
end
|
33
|
+
def lock(key, type, cnum)
|
34
|
+
self
|
35
|
+
end
|
50
36
|
|
51
|
-
|
52
|
-
|
53
|
-
|
37
|
+
def check_collision # :yields: key
|
38
|
+
self
|
39
|
+
end
|
54
40
|
|
55
|
-
|
56
|
-
|
41
|
+
def critical
|
42
|
+
yield
|
43
|
+
end
|
57
44
|
end
|
58
45
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
while (c > 0)
|
63
|
-
if (lock.try_lock) then
|
64
|
-
return
|
65
|
-
end
|
66
|
-
c -= 1
|
46
|
+
class CriticalRegionLockHandler
|
47
|
+
def initialize(critical_lock)
|
48
|
+
@critical_lock = critical_lock
|
67
49
|
end
|
68
50
|
|
69
|
-
|
70
|
-
|
71
|
-
while (Time.now - t0 < attrs.try_lock_limit)
|
72
|
-
if (lock.try_lock) then
|
73
|
-
return
|
74
|
-
end
|
75
|
-
sleep(attrs.try_lock_interval * rand.call)
|
76
|
-
end
|
77
|
-
raise TryLockTimeoutError, 'expired'
|
78
|
-
else
|
79
|
-
# brave man who doesn't fear deadlock.
|
80
|
-
lock.lock
|
51
|
+
def lock(key, type, cnum)
|
52
|
+
self
|
81
53
|
end
|
82
|
-
end
|
83
|
-
end
|
84
54
|
|
85
|
-
|
86
|
-
|
87
|
-
|
55
|
+
def check_collision # :yields: key
|
56
|
+
self
|
57
|
+
end
|
88
58
|
|
89
|
-
|
90
|
-
|
91
|
-
|
59
|
+
def critical
|
60
|
+
@critical_lock.synchronize{ yield }
|
61
|
+
end
|
92
62
|
end
|
93
63
|
|
94
|
-
class
|
95
|
-
|
64
|
+
class CollisionCheckLockHandler < CriticalRegionLockHandler
|
65
|
+
def initialize(*args)
|
66
|
+
super
|
67
|
+
@cnum_map = {}
|
68
|
+
end
|
96
69
|
|
97
|
-
def lock(key)
|
70
|
+
def lock(key, type, cnum)
|
71
|
+
key_pair = [ key, type ]
|
72
|
+
if (@cnum_map[key_pair] && @cnum_map[key_pair] != cnum) then
|
73
|
+
raise "unexpected changed cnum at`#{key}(#{type})': #{@cnum_map[key].inspect} -> #{cnum.inspect}"
|
74
|
+
end
|
75
|
+
@cnum_map[key_pair] = cnum
|
98
76
|
self
|
99
77
|
end
|
100
78
|
|
101
|
-
def
|
79
|
+
def check_collision
|
80
|
+
for (key, type), cnum in @cnum_map
|
81
|
+
last_cnum = yield(key, type)
|
82
|
+
if (cnum != last_cnum) then
|
83
|
+
raise CollisionError, "`#{key}(#{type})' is changed (cnum: #{cnum.inspect} -> #{last_cnum.inspect}) by other transaction and this transaction may be retried."
|
84
|
+
end
|
85
|
+
end
|
102
86
|
self
|
103
87
|
end
|
104
88
|
end
|
105
89
|
|
106
|
-
def
|
107
|
-
|
108
|
-
lock = @rw_lock.read_lock
|
109
|
-
else
|
110
|
-
lock = @rw_lock.write_lock
|
111
|
-
end
|
112
|
-
r = nil
|
113
|
-
lock.synchronize{
|
114
|
-
r = yield(NoWorkLockHandler.instance)
|
115
|
-
}
|
116
|
-
r
|
90
|
+
def initialize
|
91
|
+
@tx_lock = ReadWriteLock.new
|
117
92
|
end
|
118
93
|
|
119
94
|
def exclusive
|
120
95
|
r = nil
|
121
|
-
@
|
96
|
+
@tx_lock.write_lock.synchronize{
|
122
97
|
r = yield
|
123
98
|
}
|
124
99
|
r
|
125
100
|
end
|
126
101
|
end
|
127
102
|
|
128
|
-
class
|
103
|
+
class GiantLockManager < LockManager
|
129
104
|
# for ident(1)
|
130
|
-
CVS_ID = '$Id: lock.rb
|
105
|
+
CVS_ID = '$Id: lock.rb 742 2008-01-13 15:55:53Z toki $'
|
131
106
|
|
132
107
|
def initialize(*args)
|
133
108
|
super
|
134
|
-
@
|
135
|
-
@cache = SharedWorkCache.new{|key| ReadWriteLock.new }
|
109
|
+
@write_lock = Mutex.new
|
136
110
|
end
|
137
111
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
end
|
144
|
-
|
145
|
-
def lock_list
|
146
|
-
@lock_map.values
|
147
|
-
end
|
148
|
-
|
149
|
-
def unlock(key)
|
150
|
-
if (lock = @lock_map.delete(key)) then
|
151
|
-
lock.unlock
|
112
|
+
def transaction(read_only=false)
|
113
|
+
r = nil
|
114
|
+
@tx_lock.read_lock.synchronize{
|
115
|
+
if (read_only) then
|
116
|
+
r = yield(NoWorkLockHandler.instance)
|
152
117
|
else
|
153
|
-
|
118
|
+
@write_lock.synchronize{
|
119
|
+
r = yield(NoWorkLockHandler.instance)
|
120
|
+
}
|
154
121
|
end
|
155
|
-
|
156
|
-
|
122
|
+
}
|
123
|
+
r
|
157
124
|
end
|
125
|
+
end
|
158
126
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
LockManager.try_lock(r_lock, @attrs)
|
163
|
-
@lock_map[key] = r_lock
|
164
|
-
self
|
165
|
-
end
|
166
|
-
end
|
127
|
+
class OptimisticLockManager < LockManager
|
128
|
+
# for ident(1)
|
129
|
+
CVS_ID = '$Id: lock.rb 742 2008-01-13 15:55:53Z toki $'
|
167
130
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
LockManager.try_lock(w_lock, @attrs)
|
172
|
-
@lock_map[key] = w_lock
|
173
|
-
self
|
174
|
-
end
|
131
|
+
def initialize(*args)
|
132
|
+
super
|
133
|
+
@critical_lock = Mutex.new
|
175
134
|
end
|
176
135
|
|
177
136
|
def transaction(read_only=false)
|
178
|
-
if (read_only) then
|
179
|
-
lock_handler = ReadOnlyLockHandler.new(self, @cache)
|
180
|
-
else
|
181
|
-
lock_handler = ReadWriteLockHandler.new(self, @cache)
|
182
|
-
end
|
183
|
-
|
184
137
|
r = nil
|
185
138
|
@tx_lock.read_lock.synchronize{
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
lock.unlock
|
191
|
-
end
|
139
|
+
if (read_only) then
|
140
|
+
lock_handler = CriticalRegionLockHandler.new(@critical_lock)
|
141
|
+
else
|
142
|
+
lock_handler = CollisionCheckLockHandler.new(@critical_lock)
|
192
143
|
end
|
193
|
-
|
194
|
-
|
195
|
-
r
|
196
|
-
end
|
197
|
-
|
198
|
-
def exclusive
|
199
|
-
r = nil
|
200
|
-
@tx_lock.write_lock.synchronize{
|
201
|
-
r = yield
|
144
|
+
r = yield(lock_handler)
|
202
145
|
}
|
203
146
|
r
|
204
147
|
end
|