Skip to content

Commit

Permalink
fixed simple_skiplist dump/load methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleg Andreev committed Apr 28, 2008
1 parent 61f8140 commit 8d58b04
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 91 deletions.
82 changes: 16 additions & 66 deletions lib/strokedb/data_structures/simple_skiplist.rb
Expand Up @@ -12,36 +12,30 @@ class SimpleSkiplist

attr_accessor :maxlevel, :probability

def initialize(raw_list = nil, options = {})
def initialize(options = {})
options = options.stringify_keys
@maxlevel = options['maxlevel'] || DEFAULT_MAXLEVEL
@probability = options['probability'] || DEFAULT_PROBABILITY
@head, @tail = new_anchors(@maxlevel)
if raw_list
@head, @tail = unserialize_list!(raw_list)
end
@mutex = Mutex.new
end

def inspect
"#<#{self.class}:0x#{object_id.to_s(16)} items: #{to_a.inspect}, maxlevel: #{@maxlevel}, probability: #{@probability}>"
end

# Marshal API
def marshal_dump
raw_list = serialize_list(@head)
{
:options => {
:maxlevel => @maxlevel,
:probability => @probability
},
:raw_list => raw_list
}
end

def marshal_load(dumped)
initialize(dumped[:raw_list], dumped[:options])
self

def dump
Marshal.dump({
:maxlevel => @maxlevel,
:probability => @probability,
:arr => to_a
})
end

def self.load(dumped)
hash = Marshal.load(dumped)
arr = hash.delete(:arr)
from_a(arr, hash)
end

# Tests whether skiplist is empty.
Expand Down Expand Up @@ -214,6 +208,7 @@ def find_nearest_node(key) #:nodoc:
# }
# end
end

declare_optimized_methods(:C) do
end

Expand Down Expand Up @@ -318,7 +313,7 @@ def self.from_hash(hash, options = {})
# Constructs a skiplist from an array of key-value tuples (arrays).
#
def self.from_a(ary, options = {})
sl = new(nil, options)
sl = new(options)
ary.each do |kv|
sl.insert(kv[0], kv[1])
end
Expand All @@ -335,52 +330,7 @@ def to_a
end

private

def serialize_list(head)
head = anchor.dup
head[0] = [ nil ] * node_level(head)
raw_list = [ head ]
prev_by_levels = [ head ] * node_level(head)
x = node_next(head, 0)
i = 1
while x
l = node_level(x)
nx = node_next(x, 0)
x = x.dup # make modification-safe copy of node
forwards = x[0]
while l > 0 # for each node level update forwards
l -= 1
prev_by_levels[l][l] = i # set raw_list's index as a forward ref
forwards[l] = nil # nullify forward pointer (point to tail)
prev_by_levels[l] = x # set in a previous stack
end
raw_list << x # store serialized node in an array
x = nx # step to next node
i += 1 # increment index in a raw_list array
end
raw_list
end

# Returns head & tail of an imported skiplist.
# Caution: raw_list is modified (thus the bang).
# Pass dup-ed value if you need.
#
# TODO: add double-linking!
#
def unserialize_list!(raw_list)
x = raw_list[0]
while x != nil
forwards = x[0]
forwards.each_with_index do |rawindex, i|
forwards[i] = rawindex ? raw_list[rawindex] : nil
end
# go next node
x = forwards[0]
end
# return anchors (head, tail)
[raw_list[0], new_anchor] # <- FIXME: normal tail anchor needed
end

# C-style API for node operations
def anchor(reverse = false)
reverse ? @tail : @head
Expand Down
6 changes: 3 additions & 3 deletions lib/strokedb/volumes/skiplist_volume.rb
Expand Up @@ -85,10 +85,10 @@ def error(*args); end
end

if File.exists?(@list_path)
@list = Marshal.load(File.read(@list_path))
@list = SimpleSkiplist.load(File.read(@list_path))
else
info "List file (#{@list_path}) not found, creating a brand new skiplist."
@list = SimpleSkiplist.new(nil, @params)
@list = SimpleSkiplist.new(@params)
end

if File.exists?(@log_path)
Expand Down Expand Up @@ -146,7 +146,7 @@ def closed?
end

def dump!
dumped_list = Marshal.dump(@list)
dumped_list = @list.dump
f = File.open(@list_tmppath, 'w')
f.sync = true
f.write(dumped_list)
Expand Down
29 changes: 9 additions & 20 deletions spec/lib/strokedb/data_structures/simple_skiplist_spec.rb
Expand Up @@ -3,10 +3,11 @@

describe "Skiplist serialization", :shared => true do
it "should correctly load what it dumped" do
dump1 = @list.marshal_dump
newlist = @list.class.allocate
dump2 = newlist.marshal_load(dump1).marshal_dump
dump1 = @list.dump
newlist = @list.class.load(dump1)
dump2 = newlist.dump
dump1.should == dump2
newlist.to_a.should == @list.to_a
end

it "should correctly load to_a results" do
Expand All @@ -24,25 +25,13 @@
before(:each) do
@maxlevel = 8
@probability = 0.5
@list = SimpleSkiplist.new(nil, :maxlevel => @maxlevel, :probability => @probability)
@list = SimpleSkiplist.new(:maxlevel => @maxlevel, :probability => @probability)
end

it "should be empty" do
@list.should be_empty
end

it "should be serialized with marshal_dump" do
@list.marshal_dump.should == {
:options => {
:probability => @probability,
:maxlevel => @maxlevel
},
:raw_list => [
[[nil]*@maxlevel, nil, nil, [nil]*@maxlevel]
]
}
end

it "should find nil in a empty skiplist" do
@list.find("xx").should == nil
@list.find("").should == nil
Expand All @@ -57,7 +46,7 @@
before(:each) do
@maxlevel = 8
@probability = 0.5
@list = SimpleSkiplist.new(nil, :maxlevel => @maxlevel, :probability => @probability)
@list = SimpleSkiplist.new(:maxlevel => @maxlevel, :probability => @probability)
end

it "should insert empty key in place of default head" do
Expand Down Expand Up @@ -113,7 +102,7 @@
before(:each) do
@maxlevel = 8
@probability = 0.5
@list = SimpleSkiplist.new(nil, :maxlevel => @maxlevel, :probability => @probability)
@list = SimpleSkiplist.new(:maxlevel => @maxlevel, :probability => @probability)
1000.times do
k = rand(2**64).to_s
v = k
Expand Down Expand Up @@ -148,7 +137,7 @@
before(:each) do
@maxlevel = 8
@probability = 0.5
@list = SimpleSkiplist.new(nil, :maxlevel => @maxlevel, :probability => @probability)
@list = SimpleSkiplist.new(:maxlevel => @maxlevel, :probability => @probability)
end
it "should return nil for empty skiplist" do
@list.first_key.should == nil
Expand All @@ -166,7 +155,7 @@
before(:each) do
@maxlevel = 8
@probability = 0.5
@list = SimpleSkiplist.new(nil, :maxlevel => @maxlevel, :probability => @probability)
@list = SimpleSkiplist.new(:maxlevel => @maxlevel, :probability => @probability)
end
it "should find nil in empty skiplist" do
@list.find_nearest("a").should == nil
Expand Down
2 changes: 0 additions & 2 deletions spec/lib/strokedb/volumes/skiplist_volume_spec.rb
Expand Up @@ -72,8 +72,6 @@
}.should raise_error(SkiplistVolume::MessageTooBig)
end



def init_volume
@path = TEMP_STORAGES + '/skiplist_volume_files/volume'
FileUtils.rm_rf(TEMP_STORAGES + '/skiplist_volume_files')
Expand Down

0 comments on commit 8d58b04

Please sign in to comment.