Class: Blodsband::Riak::List

Inherits:
Object
  • Object
show all
Defined in:
lib/blodsband/riak/list.rb

Overview

A concurrent linked list.

Direct Known Subclasses

Map, Sset

Defined Under Namespace

Classes: ActorDeletedError, ConcurrentUpdateError, Element

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(bucket, key) ⇒ List

Access a list in a bucket.

Parameters:



309
310
311
312
# File 'lib/blodsband/riak/list.rb', line 309

def initialize(bucket, key)
  @bucket = bucket
  @key = key
end

Instance Attribute Details

#bucketObject (readonly)

The Bucket this list lives in.



301
302
303
# File 'lib/blodsband/riak/list.rb', line 301

def bucket
  @bucket
end

#keyObject (readonly)

The key of this list in Riak.



297
298
299
# File 'lib/blodsband/riak/list.rb', line 297

def key
  @key
end

Instance Method Details

#append(value) ⇒ Object Also known as: <<

Append to the list.

Parameters:

  • value (Object)

    the value to append.

Returns:

  • (Object)

    the appended value.



480
481
482
# File 'lib/blodsband/riak/list.rb', line 480

def append(value)
  append_and_get_element(value).value
end

#append_and_get_element(value) ⇒ Blodsband::Riak::List::Element

Append to the list.

Parameters:

  • value (Object)

    the value to append.

Returns:



495
496
497
498
499
# File 'lib/blodsband/riak/list.rb', line 495

def append_and_get_element(value)
  new_node = Element.create(self, value)
  append_element(new_node)
  new_node
end

#append_element_after(element, new_node) ⇒ Object



524
525
526
# File 'lib/blodsband/riak/list.rb', line 524

def append_element_after(element, new_node)
  add_element(element, new_node, :execute_append_element_after, "append #{new_node.value} after #{element.value}")
end

#delete_element(element) ⇒ Object

Delete an element.

Parameters:



535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
# File 'lib/blodsband/riak/list.rb', line 535

def delete_element(element)
  begin
    backlog << [:execute_delete_element, 
                element.key,
                "delete #{element.value}"]

    save

    begin
      rollforward
    rescue ConcurrentUpdateError => e
    ensure
      return true
    end
  rescue ConcurrentUpdateError => e
    if !exists?
      raise ActorDeletedError.new(self, to_s)
    elsif element.exists?
      false
    end
  end
end

#each(&block) ⇒ Object

Execute code on each element in the list.

Parameters:

  • block (Block |value, element|)

    the code to execute.



457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# File 'lib/blodsband/riak/list.rb', line 457

def each(&block)
  begin
    element = nil
    while size > 0 && !(element = Element.find(self, document["head"])).exists?
      reload
    end
    stack_each(element, &block) if size > 0
  rescue ActorDeletedError => e
    if e.actor == self
      raise e
    else
      retry
    end
  end
end

#empty?true, false

Returns whether this list is empty.

Returns:

  • (true, false)

    whether this list is empty.



324
325
326
# File 'lib/blodsband/riak/list.rb', line 324

def empty?
  size == 0
end

#exists?true, false

Returns whether this list exists in Riak.

Returns:

  • (true, false)

    whether this list exists in Riak.



331
332
333
# File 'lib/blodsband/riak/list.rb', line 331

def exists?
  !key.nil? && !bucket.get(key).nil?
end

#firstObject

Returns the first element of this list.

Returns:

  • (Object)

    the first element of this list.



384
385
386
387
388
389
390
# File 'lib/blodsband/riak/list.rb', line 384

def first
  if empty?
    nil
  else
    Element.find(self, document["head"]).value
  end
end

#lastObject

Returns the last element of this list.

Returns:

  • (Object)

    the last element of this list.



395
396
397
398
399
400
401
# File 'lib/blodsband/riak/list.rb', line 395

def last
  if empty?
    nil
  else
    Element.find(self, document["tail"]).value
  end
end

#popObject

Returns the last element of this list after having removed it.

Returns:

  • (Object)

    the last element of this list after having removed it.



356
357
358
359
360
361
362
363
364
365
# File 'lib/blodsband/riak/list.rb', line 356

def pop
  if empty?
    nil
  else
    e = Element.find(self, document["tail"])
    rval = e.value
    e.delete
    rval
  end
end

#prepend(value) ⇒ Object

Prepend to the list.

Parameters:

  • value (Object)

    the value to prepend.

Returns:

  • (Object)

    the prepended value.



508
509
510
511
512
# File 'lib/blodsband/riak/list.rb', line 508

def prepend(value)
  new_node = Element.create(self, value)
  prepend_element(new_node)
  value
end

#prepend_element_before(element, new_node) ⇒ Object



517
518
519
# File 'lib/blodsband/riak/list.rb', line 517

def prepend_element_before(element, new_node)
  add_element(element, new_node, :execute_prepend_element_before, "prepend #{new_node.value} before #{element.value}")
end

#reloadObject

Make sure this list gets reloaded from Riak.



563
564
565
# File 'lib/blodsband/riak/list.rb', line 563

def reload
  @document = nil
end

#saveObject

Save this list.

Raises:

  • (Blodsband::Riak::ConcurrentUpdateError)

    if this list has already been updated elswhere.



410
411
412
413
414
415
416
417
418
# File 'lib/blodsband/riak/list.rb', line 410

def save
  curr = document
  @document = bucket.cas(key, document, document.vclock)
  if @document.nil?
    raise ConcurrentUpdateError.new(self, to_s) 
  else
    #STDERR.puts("#{$$} succeeded saving #{@document.inspect}")
  end
end

#shiftObject

Returns the first element of this list after having removed it.

Returns:

  • (Object)

    the first element of this list after having removed it.



370
371
372
373
374
375
376
377
378
379
# File 'lib/blodsband/riak/list.rb', line 370

def shift
  if empty?
    nil
  else
    e = Element.find(self, document["head"])
    rval = e.value
    e.delete
    rval
  end
end

#sizeInteger

Returns the size of this list.

Returns:

  • (Integer)

    the size of this list.



338
339
340
# File 'lib/blodsband/riak/list.rb', line 338

def size
  document["size"] ||= 0
end

#stack_each(current, &block) ⇒ Object



420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/blodsband/riak/list.rb', line 420

def stack_each(current, &block)
  stack = []
  while current
    if current.exists?
      block.call(current.value, current)
      if current.next
        stack << current
        if stack.include?(current.next_element)
          raise "Loop detected! Wtf? #{stack.inspect}"
        else
          current = current.next_element
        end
      else
        current = nil
      end
    else
      if stack.empty?
        return
      else
        while !stack.empty? && !stack.last.next
          stack.pop
        end
        if stack.empty?
          current = nil
        else
          current = stack.last.next_element
        end
      end
    end
  end
end

#to_aArray

Returns this list as a ruby Array.

Returns:

  • (Array)

    this list as a ruby Array.



345
346
347
348
349
350
351
# File 'lib/blodsband/riak/list.rb', line 345

def to_a
  rval = []
  each do |e|
    rval << e
  end
  rval
end

#to_sString

Returns a string representation of the list.

Returns:

  • (String)

    a string representation of the list.



317
318
319
# File 'lib/blodsband/riak/list.rb', line 317

def to_s
  "#{self.class}:#{key}"
end

#validateObject

Validate that this list is consistent.



572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
# File 'lib/blodsband/riak/list.rb', line 572

def validate
  d = bucket.get(key)
  s = 0
  p = nil
  pe = nil
  c = d["head"]
  seen = []
  while c
    seen << c
    e = bucket.get(c)
    raise "#{e} (#{e.meta.inspect}) doesn't point back to #{pe} (#{pe.meta.inspect})" if p && e.meta["list-previous"] != p
    p = c
    pe = e
    s += 1
    c = e.meta["list-next"]
    raise "#{e} (#{e.meta.inspect}) is the the beginning of a loop: #{seen.inspect}" if seen.include?(c)
    seen << c
  end
  raise "#{d.inspect}['tail'] doesn't point to the de facto tail #{pe}" unless d["tail"] == p
  raise "#{d.inspect}['size'] doesn't show the de facto size #{s}" unless s == d["size"]
end