blodsband 0.0.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/lib/blodsband.rb +27 -0
- data/lib/blodsband/error.rb +18 -0
- data/lib/blodsband/future.rb +31 -0
- data/lib/blodsband/multi.rb +37 -0
- data/lib/blodsband/riak.rb +48 -0
- data/lib/blodsband/riak/bucket.rb +1169 -0
- data/lib/blodsband/riak/counter.rb +77 -0
- data/lib/blodsband/riak/list.rb +880 -0
- data/lib/blodsband/riak/lock.rb +68 -0
- data/lib/blodsband/riak/map.rb +143 -0
- data/lib/blodsband/riak/mr.rb +103 -0
- data/lib/blodsband/riak/response.rb +208 -0
- data/lib/blodsband/riak/search.rb +233 -0
- data/lib/blodsband/riak/sset.rb +104 -0
- metadata +107 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
|
2
|
+
module Blodsband
|
3
|
+
|
4
|
+
class Riak
|
5
|
+
|
6
|
+
class Counter
|
7
|
+
|
8
|
+
#
|
9
|
+
# Create a concurrent counter.
|
10
|
+
#
|
11
|
+
# @param [Blodsband::Riak::Bucket] bucket the bucket this counter lives in.
|
12
|
+
# @param [String] key the key for this counter.
|
13
|
+
#
|
14
|
+
def initialize(bucket, key)
|
15
|
+
@bucket = bucket
|
16
|
+
@key = key
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Increment this counter.
|
21
|
+
#
|
22
|
+
# @param [Integer] step the amount to increment the counter.
|
23
|
+
#
|
24
|
+
# @return [Integer] the new value for this counter. Note that there is no guarantee whatsoever as to at what point this value is true. Only that it was true at some point.
|
25
|
+
#
|
26
|
+
# @raise [RuntimerError] if step is < 0
|
27
|
+
#
|
28
|
+
def inc(step = 1)
|
29
|
+
raise RuntimeError.new("#{self}.inc(..) only allows positive values") if step < 0
|
30
|
+
current = get_resolved
|
31
|
+
current["last"] = current["last"] + current["step"]
|
32
|
+
current["step"] = step
|
33
|
+
val(get_resolved(@bucket.put(@key, current, :riak_params => {:readbody => true})))
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Get the value of this counter.
|
38
|
+
#
|
39
|
+
# @param [Blodsband::Riak::Result] current a {Blodsband::Riak::Response} defining the last known value (possibly with a gazillion siblings).
|
40
|
+
#
|
41
|
+
# @return [Integer] the current value of the counter. Note that there is no guarantee whatsoever as to when this value was true. Only that it was true at some point.
|
42
|
+
#
|
43
|
+
def val(current = nil)
|
44
|
+
current = get_resolved if current.nil?
|
45
|
+
return current["last"] + current["step"]
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def get_resolved(current = nil)
|
51
|
+
current = @bucket.get(@key, :ary => true) if current.nil?
|
52
|
+
if current.empty?
|
53
|
+
current = {
|
54
|
+
"step" => 0,
|
55
|
+
"last" => 0
|
56
|
+
}
|
57
|
+
return get_resolved(@bucket.put(@key, current, :riak_params => {:readbody => true}))
|
58
|
+
else
|
59
|
+
last = 1 << 256
|
60
|
+
step = 0
|
61
|
+
current.each do |v|
|
62
|
+
step += v["step"]
|
63
|
+
last = v["last"] if v["last"] < last
|
64
|
+
end
|
65
|
+
sum = {
|
66
|
+
"step" => step,
|
67
|
+
"last" => last
|
68
|
+
}
|
69
|
+
return current.copy_to(sum)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,880 @@
|
|
1
|
+
|
2
|
+
module Blodsband
|
3
|
+
|
4
|
+
class Riak
|
5
|
+
|
6
|
+
#
|
7
|
+
# A concurrent linked list.
|
8
|
+
#
|
9
|
+
class List
|
10
|
+
|
11
|
+
#
|
12
|
+
# An error signaling that saving a piece of data failed because it has
|
13
|
+
# been concurrently updated elsewhere.
|
14
|
+
#
|
15
|
+
class ConcurrentUpdateError < RuntimeError
|
16
|
+
#
|
17
|
+
# [Object] The actor that was concurrently updated.
|
18
|
+
#
|
19
|
+
attr_reader :actor
|
20
|
+
def initialize(actor, message)
|
21
|
+
super(message)
|
22
|
+
@actor = actor
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# An error signaling that performing an operation failed because one or more
|
28
|
+
# of the actors in the operation have been deleted.
|
29
|
+
#
|
30
|
+
class ActorDeletedError < RuntimeError
|
31
|
+
#
|
32
|
+
# [Object] The actor that was deleted.
|
33
|
+
#
|
34
|
+
attr_reader :actor
|
35
|
+
def initialize(actor, message)
|
36
|
+
super(message)
|
37
|
+
@actor = actor
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# The list element.
|
43
|
+
#
|
44
|
+
class Element
|
45
|
+
|
46
|
+
#
|
47
|
+
# The key in Riak for this element.
|
48
|
+
#
|
49
|
+
attr_reader :key
|
50
|
+
#
|
51
|
+
# The {Blodsband::Riak::List} this element belongs to.
|
52
|
+
#
|
53
|
+
attr_reader :list
|
54
|
+
|
55
|
+
#
|
56
|
+
# Find an element.
|
57
|
+
#
|
58
|
+
# @param [Blodsband::Riak::List] list the list the element belongs to.
|
59
|
+
# @param [String] key the key for this element.
|
60
|
+
#
|
61
|
+
# @return [Blodsband::Riak::Element] the element at the given key.
|
62
|
+
#
|
63
|
+
def self.find(list, key)
|
64
|
+
rval = Element.new
|
65
|
+
rval.instance_eval do
|
66
|
+
@list = list
|
67
|
+
@key = key
|
68
|
+
end
|
69
|
+
rval
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# @private
|
74
|
+
#
|
75
|
+
# Create an element.
|
76
|
+
#
|
77
|
+
# @param [Blodsband::Riak::List] list the list the element shall belong to.
|
78
|
+
# @param [Object] value the value to put in the element.
|
79
|
+
#
|
80
|
+
# @return [Blodsband::Riak::Element] a new (unsaved) element with the given value.
|
81
|
+
#
|
82
|
+
def self.create(list, value)
|
83
|
+
rval = Element.new
|
84
|
+
rval.instance_eval do
|
85
|
+
@list = list
|
86
|
+
@key = rand(1 << 256).to_s(36)
|
87
|
+
@value = value
|
88
|
+
class << @value
|
89
|
+
include Response
|
90
|
+
end
|
91
|
+
@value.instance_eval do
|
92
|
+
@meta = (@meta || {}).merge("list-key" => list.key)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
rval.save
|
96
|
+
rval
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# @return [true, false] whether the element exists.
|
101
|
+
#
|
102
|
+
def exists?
|
103
|
+
reload
|
104
|
+
!key.nil? && !value.nil?
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# @return [String] a {::String} representation of the element.
|
109
|
+
#
|
110
|
+
def to_s
|
111
|
+
if exists?
|
112
|
+
"#{self.previous} => #{self.class}:#{key}@#{list.key} #{value.inspect} => #{self.next}"
|
113
|
+
else
|
114
|
+
"#{self.class}:#{key}@#{list.key}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# @param [Object] o the new value to set in this element.
|
120
|
+
#
|
121
|
+
# @note Does not lock the parent list, since changing the element value is independent
|
122
|
+
# of structural list changes. Instead both this and structural list changes just retry
|
123
|
+
# on {Blodsband::Riak::List::ConcurrentUpdateError}.
|
124
|
+
#
|
125
|
+
# @return [Object] the new value.
|
126
|
+
#
|
127
|
+
def value=(o)
|
128
|
+
begin
|
129
|
+
old_value = value
|
130
|
+
@value = value.copy_to(o)
|
131
|
+
value.instance_eval do
|
132
|
+
@meta = old_value.meta
|
133
|
+
end
|
134
|
+
save
|
135
|
+
value
|
136
|
+
rescue ConcurrentUpdateError => e
|
137
|
+
if !exists?
|
138
|
+
raise ActorDeletedError.new(self, to_s)
|
139
|
+
else
|
140
|
+
retry
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# @return [Object] the value of this element.
|
147
|
+
#
|
148
|
+
def value
|
149
|
+
@value ||= list.bucket.get(key, :unique => true)
|
150
|
+
end
|
151
|
+
|
152
|
+
#
|
153
|
+
# Prepend a value before this element in the list.
|
154
|
+
#
|
155
|
+
# @param [Object] value the value to prepend.
|
156
|
+
#
|
157
|
+
# @return [Object] the value inserted.
|
158
|
+
#
|
159
|
+
def prepend(value)
|
160
|
+
if exists?
|
161
|
+
while !list.prepend_element_before(self, Element.create(list, value))
|
162
|
+
list.reload
|
163
|
+
reload
|
164
|
+
raise ActorDeletedError.new(self, to_s) unless exists?
|
165
|
+
raise ActorDeletedError.new(list, list.to_s) unless list.exists?
|
166
|
+
end
|
167
|
+
else
|
168
|
+
raise ActorDeletedError.new(self, to_s)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
#
|
173
|
+
# Delete this element.
|
174
|
+
#
|
175
|
+
def delete
|
176
|
+
if exists?
|
177
|
+
while !list.delete_element(self)
|
178
|
+
list.reload
|
179
|
+
reload
|
180
|
+
break unless exists?
|
181
|
+
raise ActorDeletedError.new(list, list.to_s) unless list.exists?
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# Append a value after this element in the list.
|
188
|
+
#
|
189
|
+
# @param [Object] value the value to append.
|
190
|
+
#
|
191
|
+
# @return [Object] the value appended.
|
192
|
+
#
|
193
|
+
def append(value)
|
194
|
+
if exists?
|
195
|
+
while !list.append_element_after(self, Element.create(list, value))
|
196
|
+
list.reload
|
197
|
+
reload
|
198
|
+
raise ActorDeletedError.new(self, to_s) unless exists?
|
199
|
+
raise ActorDeletedError.new(list, list.to_s) unless list.exists?
|
200
|
+
end
|
201
|
+
else
|
202
|
+
raise ActorDeletedError.new(self, to_s)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
#
|
207
|
+
# @private
|
208
|
+
#
|
209
|
+
# Save this element.
|
210
|
+
#
|
211
|
+
# @raise [Blodsband::Riak::List::ConcurrentUpdateError] if the element has already been updated elsewhere.
|
212
|
+
#
|
213
|
+
def save
|
214
|
+
curr = @value
|
215
|
+
@value = list.bucket.cas(key, value, value.vclock)
|
216
|
+
if @value.nil?
|
217
|
+
raise ConcurrentUpdateError.new(self, to_s)
|
218
|
+
else
|
219
|
+
#STDERR.puts("#{$$} succeeded saving #{curr} (#{curr.meta})")
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
#
|
224
|
+
# @private
|
225
|
+
#
|
226
|
+
# Make sure this element is reloaded from Riak.
|
227
|
+
#
|
228
|
+
def reload
|
229
|
+
@value = nil
|
230
|
+
end
|
231
|
+
|
232
|
+
#
|
233
|
+
# @private
|
234
|
+
#
|
235
|
+
# @return [String] the key to the next element in the list.
|
236
|
+
#
|
237
|
+
def next
|
238
|
+
value.meta["list-next"]
|
239
|
+
end
|
240
|
+
|
241
|
+
#
|
242
|
+
# @private
|
243
|
+
#
|
244
|
+
# @return [Blodsband::Riak::List::Element] the next element or nil.
|
245
|
+
#
|
246
|
+
def next_element
|
247
|
+
Element.find(list, self.next)
|
248
|
+
end
|
249
|
+
|
250
|
+
#
|
251
|
+
# @private
|
252
|
+
#
|
253
|
+
# @return [String] the key to the previous element in the list.
|
254
|
+
#
|
255
|
+
def previous
|
256
|
+
value.meta["list-previous"]
|
257
|
+
end
|
258
|
+
|
259
|
+
#
|
260
|
+
# @private
|
261
|
+
#
|
262
|
+
# @return [Blodsband::Riak::List::Element] the previous element or nil.
|
263
|
+
#
|
264
|
+
def previous_element
|
265
|
+
Element.find(list, self.previous)
|
266
|
+
end
|
267
|
+
|
268
|
+
#
|
269
|
+
# @private
|
270
|
+
#
|
271
|
+
def set_pointers(list_version, previous_key, next_key)
|
272
|
+
begin
|
273
|
+
list_doc = list.bucket.get(list.key)
|
274
|
+
if list_doc.nil? || list_doc["version"] == list_version
|
275
|
+
if previous_key.nil?
|
276
|
+
value.meta.delete("list-previous")
|
277
|
+
elsif previous_key != ":unchanged"
|
278
|
+
value.meta["list-previous"] = previous_key
|
279
|
+
end
|
280
|
+
if next_key.nil?
|
281
|
+
value.meta.delete("list-next")
|
282
|
+
elsif next_key != ":unchanged"
|
283
|
+
value.meta["list-next"] = next_key
|
284
|
+
end
|
285
|
+
save
|
286
|
+
end
|
287
|
+
rescue ConcurrentUpdateError => e
|
288
|
+
retry if exists?
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
end
|
293
|
+
|
294
|
+
#
|
295
|
+
# The key of this list in Riak.
|
296
|
+
#
|
297
|
+
attr_reader :key
|
298
|
+
#
|
299
|
+
# The {Blodsband::Riak::Bucket} this list lives in.
|
300
|
+
#
|
301
|
+
attr_reader :bucket
|
302
|
+
|
303
|
+
#
|
304
|
+
# Access a list in a bucket.
|
305
|
+
#
|
306
|
+
# @param [Blodsband::Riak::Bucket] the bucket the list lives in.
|
307
|
+
# @param [String] key the key of the list.
|
308
|
+
#
|
309
|
+
def initialize(bucket, key)
|
310
|
+
@bucket = bucket
|
311
|
+
@key = key
|
312
|
+
end
|
313
|
+
|
314
|
+
#
|
315
|
+
# @return [String] a string representation of the list.
|
316
|
+
#
|
317
|
+
def to_s
|
318
|
+
"#{self.class}:#{key}"
|
319
|
+
end
|
320
|
+
|
321
|
+
#
|
322
|
+
# @return [true, false] whether this list is empty.
|
323
|
+
#
|
324
|
+
def empty?
|
325
|
+
size == 0
|
326
|
+
end
|
327
|
+
|
328
|
+
#
|
329
|
+
# @return [true, false] whether this list exists in Riak.
|
330
|
+
#
|
331
|
+
def exists?
|
332
|
+
!key.nil? && !bucket.get(key).nil?
|
333
|
+
end
|
334
|
+
|
335
|
+
#
|
336
|
+
# @return [Integer] the size of this list.
|
337
|
+
#
|
338
|
+
def size
|
339
|
+
document["size"] ||= 0
|
340
|
+
end
|
341
|
+
|
342
|
+
#
|
343
|
+
# @return [Array] this list as a ruby {::Array}.
|
344
|
+
#
|
345
|
+
def to_a
|
346
|
+
rval = []
|
347
|
+
each do |e|
|
348
|
+
rval << e
|
349
|
+
end
|
350
|
+
rval
|
351
|
+
end
|
352
|
+
|
353
|
+
#
|
354
|
+
# @return [Object] the last element of this list after having removed it.
|
355
|
+
#
|
356
|
+
def pop
|
357
|
+
if empty?
|
358
|
+
nil
|
359
|
+
else
|
360
|
+
e = Element.find(self, document["tail"])
|
361
|
+
rval = e.value
|
362
|
+
e.delete
|
363
|
+
rval
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
#
|
368
|
+
# @return [Object] the first element of this list after having removed it.
|
369
|
+
#
|
370
|
+
def shift
|
371
|
+
if empty?
|
372
|
+
nil
|
373
|
+
else
|
374
|
+
e = Element.find(self, document["head"])
|
375
|
+
rval = e.value
|
376
|
+
e.delete
|
377
|
+
rval
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
#
|
382
|
+
# @return [Object] the first element of this list.
|
383
|
+
#
|
384
|
+
def first
|
385
|
+
if empty?
|
386
|
+
nil
|
387
|
+
else
|
388
|
+
Element.find(self, document["head"]).value
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
#
|
393
|
+
# @return [Object] the last element of this list.
|
394
|
+
#
|
395
|
+
def last
|
396
|
+
if empty?
|
397
|
+
nil
|
398
|
+
else
|
399
|
+
Element.find(self, document["tail"]).value
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
#
|
404
|
+
# @private
|
405
|
+
#
|
406
|
+
# Save this list.
|
407
|
+
#
|
408
|
+
# @raise [Blodsband::Riak::ConcurrentUpdateError] if this list has already been updated elswhere.
|
409
|
+
#
|
410
|
+
def save
|
411
|
+
curr = document
|
412
|
+
@document = bucket.cas(key, document, document.vclock)
|
413
|
+
if @document.nil?
|
414
|
+
raise ConcurrentUpdateError.new(self, to_s)
|
415
|
+
else
|
416
|
+
#STDERR.puts("#{$$} succeeded saving #{@document.inspect}")
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
def stack_each(current, &block)
|
421
|
+
stack = []
|
422
|
+
while current
|
423
|
+
if current.exists?
|
424
|
+
block.call(current.value, current)
|
425
|
+
if current.next
|
426
|
+
stack << current
|
427
|
+
if stack.include?(current.next_element)
|
428
|
+
raise "Loop detected! Wtf? #{stack.inspect}"
|
429
|
+
else
|
430
|
+
current = current.next_element
|
431
|
+
end
|
432
|
+
else
|
433
|
+
current = nil
|
434
|
+
end
|
435
|
+
else
|
436
|
+
if stack.empty?
|
437
|
+
return
|
438
|
+
else
|
439
|
+
while !stack.empty? && !stack.last.next
|
440
|
+
stack.pop
|
441
|
+
end
|
442
|
+
if stack.empty?
|
443
|
+
current = nil
|
444
|
+
else
|
445
|
+
current = stack.last.next_element
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
#
|
453
|
+
# Execute code on each element in the list.
|
454
|
+
#
|
455
|
+
# @param [Block |value, element|] block the code to execute.
|
456
|
+
#
|
457
|
+
def each(&block)
|
458
|
+
begin
|
459
|
+
element = nil
|
460
|
+
while size > 0 && !(element = Element.find(self, document["head"])).exists?
|
461
|
+
reload
|
462
|
+
end
|
463
|
+
stack_each(element, &block) if size > 0
|
464
|
+
rescue ActorDeletedError => e
|
465
|
+
if e.actor == self
|
466
|
+
raise e
|
467
|
+
else
|
468
|
+
retry
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
#
|
474
|
+
# Append to the list.
|
475
|
+
#
|
476
|
+
# @param [Object] value the value to append.
|
477
|
+
#
|
478
|
+
# @return [Object] the appended value.
|
479
|
+
#
|
480
|
+
def append(value)
|
481
|
+
append_and_get_element(value).value
|
482
|
+
end
|
483
|
+
|
484
|
+
alias :<< :append
|
485
|
+
|
486
|
+
#
|
487
|
+
# @private
|
488
|
+
#
|
489
|
+
# Append to the list.
|
490
|
+
#
|
491
|
+
# @param (see #append)
|
492
|
+
#
|
493
|
+
# @return [Blodsband::Riak::List::Element] the appended element.
|
494
|
+
#
|
495
|
+
def append_and_get_element(value)
|
496
|
+
new_node = Element.create(self, value)
|
497
|
+
append_element(new_node)
|
498
|
+
new_node
|
499
|
+
end
|
500
|
+
|
501
|
+
#
|
502
|
+
# Prepend to the list.
|
503
|
+
#
|
504
|
+
# @param [Object] value the value to prepend.
|
505
|
+
#
|
506
|
+
# @return [Object] the prepended value.
|
507
|
+
#
|
508
|
+
def prepend(value)
|
509
|
+
new_node = Element.create(self, value)
|
510
|
+
prepend_element(new_node)
|
511
|
+
value
|
512
|
+
end
|
513
|
+
|
514
|
+
#
|
515
|
+
# @private
|
516
|
+
#
|
517
|
+
def prepend_element_before(element, new_node)
|
518
|
+
add_element(element, new_node, :execute_prepend_element_before, "prepend #{new_node.value} before #{element.value}")
|
519
|
+
end
|
520
|
+
|
521
|
+
#
|
522
|
+
# @private
|
523
|
+
#
|
524
|
+
def append_element_after(element, new_node)
|
525
|
+
add_element(element, new_node, :execute_append_element_after, "append #{new_node.value} after #{element.value}")
|
526
|
+
end
|
527
|
+
|
528
|
+
#
|
529
|
+
# @private
|
530
|
+
#
|
531
|
+
# Delete an element.
|
532
|
+
#
|
533
|
+
# @param [Blodsband::Riak::List::Element] element the element to delete.
|
534
|
+
#
|
535
|
+
def delete_element(element)
|
536
|
+
begin
|
537
|
+
backlog << [:execute_delete_element,
|
538
|
+
element.key,
|
539
|
+
"delete #{element.value}"]
|
540
|
+
|
541
|
+
save
|
542
|
+
|
543
|
+
begin
|
544
|
+
rollforward
|
545
|
+
rescue ConcurrentUpdateError => e
|
546
|
+
ensure
|
547
|
+
return true
|
548
|
+
end
|
549
|
+
rescue ConcurrentUpdateError => e
|
550
|
+
if !exists?
|
551
|
+
raise ActorDeletedError.new(self, to_s)
|
552
|
+
elsif element.exists?
|
553
|
+
false
|
554
|
+
end
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
#
|
559
|
+
# @private
|
560
|
+
#
|
561
|
+
# Make sure this list gets reloaded from Riak.
|
562
|
+
#
|
563
|
+
def reload
|
564
|
+
@document = nil
|
565
|
+
end
|
566
|
+
|
567
|
+
#
|
568
|
+
# @private
|
569
|
+
#
|
570
|
+
# Validate that this list is consistent.
|
571
|
+
#
|
572
|
+
def validate
|
573
|
+
d = bucket.get(key)
|
574
|
+
s = 0
|
575
|
+
p = nil
|
576
|
+
pe = nil
|
577
|
+
c = d["head"]
|
578
|
+
seen = []
|
579
|
+
while c
|
580
|
+
seen << c
|
581
|
+
e = bucket.get(c)
|
582
|
+
raise "#{e} (#{e.meta.inspect}) doesn't point back to #{pe} (#{pe.meta.inspect})" if p && e.meta["list-previous"] != p
|
583
|
+
p = c
|
584
|
+
pe = e
|
585
|
+
s += 1
|
586
|
+
c = e.meta["list-next"]
|
587
|
+
raise "#{e} (#{e.meta.inspect}) is the the beginning of a loop: #{seen.inspect}" if seen.include?(c)
|
588
|
+
seen << c
|
589
|
+
end
|
590
|
+
raise "#{d.inspect}['tail'] doesn't point to the de facto tail #{pe}" unless d["tail"] == p
|
591
|
+
raise "#{d.inspect}['size'] doesn't show the de facto size #{s}" unless s == d["size"]
|
592
|
+
end
|
593
|
+
|
594
|
+
protected
|
595
|
+
|
596
|
+
def ok_to_add?(element)
|
597
|
+
true
|
598
|
+
end
|
599
|
+
|
600
|
+
def backlog_add(element)
|
601
|
+
end
|
602
|
+
|
603
|
+
def backlog_delete(element)
|
604
|
+
end
|
605
|
+
|
606
|
+
def backlog
|
607
|
+
document["backlog"] ||= []
|
608
|
+
document["backlog"]
|
609
|
+
end
|
610
|
+
|
611
|
+
private
|
612
|
+
|
613
|
+
def add_element(element, new_node, method, desc)
|
614
|
+
begin
|
615
|
+
backlog << [method,
|
616
|
+
element.key,
|
617
|
+
new_node.key,
|
618
|
+
desc]
|
619
|
+
|
620
|
+
save
|
621
|
+
|
622
|
+
begin
|
623
|
+
rollforward
|
624
|
+
rescue ConcurrentUpdateError => e
|
625
|
+
ensure
|
626
|
+
return true
|
627
|
+
end
|
628
|
+
rescue ConcurrentUpdateError => e
|
629
|
+
if !exists?
|
630
|
+
raise ActorDeletedError.new(self, to_s)
|
631
|
+
elsif !element.exists?
|
632
|
+
raise ActorDeletedError.new(element, element.to_s)
|
633
|
+
else
|
634
|
+
return false
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
def set_element_pointers(v, element_key, previous_key, next_key, desc)
|
640
|
+
Element.find(self, element_key).set_pointers(v, previous_key, next_key)
|
641
|
+
end
|
642
|
+
|
643
|
+
def actually_delete_element(element_key, desc)
|
644
|
+
bucket.delete(element_key)
|
645
|
+
end
|
646
|
+
|
647
|
+
def execute_delete_element(element_key, desc)
|
648
|
+
element = Element.find(self, element_key)
|
649
|
+
if element.exists?
|
650
|
+
previous_element = element.previous_element
|
651
|
+
next_element = element.next_element
|
652
|
+
#STDERR.puts("#{$$} chain:\n #{previous_element}\n #{element}\n #{next_element}")
|
653
|
+
|
654
|
+
document["version"] = version + 1
|
655
|
+
|
656
|
+
backlog << [:set_element_pointers,
|
657
|
+
version,
|
658
|
+
next_element.key,
|
659
|
+
element.previous,
|
660
|
+
":unchanged",
|
661
|
+
desc] if next_element.exists?
|
662
|
+
|
663
|
+
backlog << [:set_element_pointers,
|
664
|
+
version,
|
665
|
+
previous_element.key,
|
666
|
+
":unchanged",
|
667
|
+
next_element.key,
|
668
|
+
desc] if previous_element.exists?
|
669
|
+
|
670
|
+
backlog << [:actually_delete_element,
|
671
|
+
element.key,
|
672
|
+
desc]
|
673
|
+
|
674
|
+
backlog_delete(element)
|
675
|
+
|
676
|
+
document["head"] = element.next if element.previous.nil?
|
677
|
+
document["tail"] = element.previous if element.next.nil?
|
678
|
+
document["size"] -= 1
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
def execute_prepend_element_before(element_key, new_key, desc)
|
683
|
+
element = Element.find(self, element_key)
|
684
|
+
if element.exists?
|
685
|
+
new_element = Element.find(self, new_key)
|
686
|
+
if ok_to_add?(new_element)
|
687
|
+
previous_element = element.previous_element
|
688
|
+
#STDERR.puts("#{$$} chain:\n #{previous_element}\n #{new_element}\n #{element}")
|
689
|
+
|
690
|
+
document["version"] = version + 1
|
691
|
+
|
692
|
+
backlog << [:set_element_pointers,
|
693
|
+
version,
|
694
|
+
new_element.key,
|
695
|
+
previous_element.key,
|
696
|
+
element.key,
|
697
|
+
desc]
|
698
|
+
backlog << [:set_element_pointers,
|
699
|
+
version,
|
700
|
+
element.key,
|
701
|
+
new_element.key,
|
702
|
+
":unchanged",
|
703
|
+
desc]
|
704
|
+
backlog << [:set_element_pointers,
|
705
|
+
version,
|
706
|
+
previous_element.key,
|
707
|
+
":unchanged",
|
708
|
+
new_element.key,
|
709
|
+
desc] if previous_element.exists?
|
710
|
+
|
711
|
+
backlog_add(new_element)
|
712
|
+
|
713
|
+
document["head"] = new_key if element.previous.nil?
|
714
|
+
document["size"] = size + 1
|
715
|
+
else
|
716
|
+
bucket.delete(new_element.key)
|
717
|
+
end
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
def execute_append_element_after(element_key, new_key, desc)
|
722
|
+
element = Element.find(self, element_key)
|
723
|
+
if element.exists?
|
724
|
+
new_element = Element.find(self, new_key)
|
725
|
+
if ok_to_add?(new_element)
|
726
|
+
next_element = element.next_element
|
727
|
+
#STDERR.puts("#{$$} chain:\n #{element}\n #{new_element}\n #{next_element}")
|
728
|
+
document["version"] = version + 1
|
729
|
+
|
730
|
+
backlog << [:set_element_pointers,
|
731
|
+
version,
|
732
|
+
new_element.key,
|
733
|
+
element.key,
|
734
|
+
next_element.key,
|
735
|
+
desc]
|
736
|
+
backlog << [:set_element_pointers,
|
737
|
+
version,
|
738
|
+
element.key,
|
739
|
+
":unchanged",
|
740
|
+
new_element.key,
|
741
|
+
desc]
|
742
|
+
backlog << [:set_element_pointers,
|
743
|
+
version,
|
744
|
+
next_element.key,
|
745
|
+
new_element.key,
|
746
|
+
":unchanged",
|
747
|
+
desc] if next_element.exists?
|
748
|
+
|
749
|
+
backlog_add(new_element)
|
750
|
+
|
751
|
+
document["tail"] = new_key if element.next.nil?
|
752
|
+
document["size"] = size + 1
|
753
|
+
else
|
754
|
+
bucket.delete(new_element.key)
|
755
|
+
end
|
756
|
+
end
|
757
|
+
end
|
758
|
+
|
759
|
+
def prepend_element(new_node)
|
760
|
+
done = false
|
761
|
+
while !done
|
762
|
+
if empty?
|
763
|
+
done = set_first_element(new_node)
|
764
|
+
else
|
765
|
+
first_element = Element.find(self, document["head"])
|
766
|
+
if first_element.exists?
|
767
|
+
begin
|
768
|
+
done = prepend_element_before(first_element, new_node)
|
769
|
+
rescue ActorDeletedError => e
|
770
|
+
if e.actor != first_element
|
771
|
+
raise e
|
772
|
+
else
|
773
|
+
reload
|
774
|
+
end
|
775
|
+
end
|
776
|
+
else
|
777
|
+
reload
|
778
|
+
end
|
779
|
+
end
|
780
|
+
end
|
781
|
+
end
|
782
|
+
|
783
|
+
def append_element(new_node)
|
784
|
+
done = false
|
785
|
+
while !done
|
786
|
+
if empty?
|
787
|
+
done = set_first_element(new_node)
|
788
|
+
else
|
789
|
+
last_element = Element.find(self, document["tail"])
|
790
|
+
if last_element.exists?
|
791
|
+
begin
|
792
|
+
done = append_element_after(last_element, new_node)
|
793
|
+
rescue ActorDeletedError => e
|
794
|
+
if e.actor != last_element
|
795
|
+
raise e
|
796
|
+
else
|
797
|
+
reload
|
798
|
+
end
|
799
|
+
end
|
800
|
+
else
|
801
|
+
reload
|
802
|
+
end
|
803
|
+
end
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
def set_first_element(new_node)
|
808
|
+
begin
|
809
|
+
document["version"] = version + 1
|
810
|
+
new_node.set_pointers(version, nil, nil)
|
811
|
+
new_node.save
|
812
|
+
document["head"] = new_node.key
|
813
|
+
document["tail"] = new_node.key
|
814
|
+
document["size"] = 1
|
815
|
+
backlog_add(new_node)
|
816
|
+
save
|
817
|
+
begin
|
818
|
+
rollforward
|
819
|
+
rescue ConcurrentUpdateError => e
|
820
|
+
ensure
|
821
|
+
return true
|
822
|
+
end
|
823
|
+
rescue ConcurrentUpdateError => e
|
824
|
+
if !exists?
|
825
|
+
raise ActorDeletedError.new(self, to_s)
|
826
|
+
else
|
827
|
+
return false
|
828
|
+
end
|
829
|
+
end
|
830
|
+
end
|
831
|
+
|
832
|
+
def rollforward
|
833
|
+
while !backlog.empty?
|
834
|
+
begin
|
835
|
+
todo = backlog.clone
|
836
|
+
#STDERR.puts("#{$$} going to roll #{todo.inspect} forward")
|
837
|
+
copy = todo.clone
|
838
|
+
backlog.clear
|
839
|
+
while !todo.empty?
|
840
|
+
self.send(*(todo.shift))
|
841
|
+
end
|
842
|
+
save
|
843
|
+
#STDERR.puts("#{$$} rolled #{copy.inspect} forward")
|
844
|
+
rescue ConcurrentUpdateError => e
|
845
|
+
reload
|
846
|
+
retry unless (_document["backlog"] || []).empty?
|
847
|
+
end
|
848
|
+
end
|
849
|
+
end
|
850
|
+
|
851
|
+
def version
|
852
|
+
document["version"] || 0
|
853
|
+
end
|
854
|
+
|
855
|
+
def _document
|
856
|
+
if @document.nil?
|
857
|
+
@document = bucket.get(key, :unique => true)
|
858
|
+
if @document.nil?
|
859
|
+
@document = {}
|
860
|
+
class << @document
|
861
|
+
include Response
|
862
|
+
end
|
863
|
+
end
|
864
|
+
end
|
865
|
+
@document
|
866
|
+
end
|
867
|
+
|
868
|
+
def document
|
869
|
+
if @document.nil?
|
870
|
+
_document
|
871
|
+
rollforward
|
872
|
+
end
|
873
|
+
@document
|
874
|
+
end
|
875
|
+
|
876
|
+
end
|
877
|
+
|
878
|
+
end
|
879
|
+
|
880
|
+
end
|