daybreak 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - ruby-head
6
+ - jruby-18mode
7
+ - jruby-19mode
8
+ - rbx-18mode
9
+ - rbx-19mode
data/README CHANGED
@@ -5,10 +5,10 @@
5
5
 
6
6
  Daybreak is a simple key value store for ruby. It has user defined persistence,
7
7
  and all data is stored in a table in memory so ruby niceties are available.
8
- Daybreak is faster than other options ruby options like pstore and dbm.
8
+ Daybreak is faster than other ruby options like pstore and dbm.
9
9
 
10
10
  $ gem install daybreak
11
11
 
12
12
  Docs: http://propublica.github.com/daybreak/
13
13
  Issue Tracker: http://propublica.github.com/daybreak/issues
14
- Benchmarks: https://gist.github.com/4146590
14
+ Benchmarks: https://gist.github.com/4146590
data/daybreak.gemspec CHANGED
@@ -14,7 +14,6 @@ Gem::Specification.new do |gem|
14
14
  gem.require_paths = ["lib"]
15
15
  gem.licenses = ["MIT"]
16
16
  gem.version = Daybreak::VERSION
17
+ gem.add_development_dependency 'rake'
17
18
  gem.add_development_dependency 'minitest'
18
- gem.add_development_dependency 'simplecov'
19
- gem.add_development_dependency 'ruby-prof'
20
19
  end
data/lib/daybreak.rb CHANGED
@@ -1,17 +1,11 @@
1
- # Daybreak, a simple dimple key value store for ruby.
2
- module Daybreak
3
- # The root path for Daybreak
4
- ROOT = File.expand_path(File.dirname(__FILE__))
5
- end
6
-
7
1
  require 'tempfile'
8
2
  require 'thread'
9
- require 'zlib'
10
3
  require 'fcntl'
4
+ require 'zlib'
11
5
 
12
- require "#{Daybreak::ROOT}/daybreak/version"
13
- require "#{Daybreak::ROOT}/daybreak/locking"
14
- require "#{Daybreak::ROOT}/daybreak/record"
15
- require "#{Daybreak::ROOT}/daybreak/writer"
16
- require "#{Daybreak::ROOT}/daybreak/reader"
17
- require "#{Daybreak::ROOT}/daybreak/db"
6
+ require 'daybreak/version'
7
+ require 'daybreak/locking'
8
+ require 'daybreak/record'
9
+ require 'daybreak/writer'
10
+ require 'daybreak/reader'
11
+ require 'daybreak/db'
data/lib/daybreak/db.rb CHANGED
@@ -14,10 +14,11 @@ module Daybreak
14
14
  # @yield [key] a block that will return the default value to store.
15
15
  # @yieldparam [String] key the key to be stored.
16
16
  def initialize(file, default=nil, &blk)
17
+ @table = {}
17
18
  @file_name = file
18
- reset!
19
- @default = default
20
- @default = blk if block_given?
19
+ @reader = Reader.new(@file_name)
20
+ @writer = Writer.new(@file_name)
21
+ @default = block_given? ? blk : default
21
22
  read!
22
23
  end
23
24
 
@@ -64,12 +65,7 @@ module Daybreak
64
65
  if @table.has_key? key
65
66
  @table[key]
66
67
  elsif default?
67
- if @default.is_a? Proc
68
- value = @default.call(key)
69
- else
70
- value = @default
71
- end
72
- set key, value
68
+ set key, Proc === @default ? @default.call(key) : @default
73
69
  end
74
70
  end
75
71
  alias_method :get, :"[]"
@@ -78,8 +74,8 @@ module Daybreak
78
74
  # @yield [key, value] blk the iterator for each key value pair.
79
75
  # @yieldparam [String] key the key.
80
76
  # @yieldparam value the value from the database.
81
- def each(&blk)
82
- keys.each { |k| blk.call(k, get(k)) }
77
+ def each
78
+ keys.each { |k| yield(k, get(k)) }
83
79
  end
84
80
 
85
81
  # Does this db have a default value.
@@ -104,6 +100,7 @@ module Daybreak
104
100
  def length
105
101
  @table.keys.length
106
102
  end
103
+ alias_method :size, :length
107
104
 
108
105
  # Serialize the data for writing to disk, if you don't want to use <tt>Marshal</tt>
109
106
  # overwrite this method.
@@ -121,11 +118,10 @@ module Daybreak
121
118
  Marshal.load(value)
122
119
  end
123
120
 
124
- # Reset and empty the database file.
121
+ # Empty the database file.
125
122
  def empty!
126
123
  @writer.truncate!
127
- close!
128
- reset!
124
+ @table.clear
129
125
  read!
130
126
  end
131
127
  alias_method :clear, :empty!
@@ -135,53 +131,42 @@ module Daybreak
135
131
  @writer.flush!
136
132
  end
137
133
 
138
- # Reset the state of the database, you should call <tt>read!</tt> after calling this.
139
- def reset!
140
- @table = {}
141
- @writer = Daybreak::Writer.new(@file_name)
142
- @reader = Daybreak::Reader.new(@file_name)
143
- end
144
-
145
134
  # Close the database for reading and writing.
146
135
  def close!
147
136
  @writer.close!
148
- @reader.close!
149
137
  end
150
138
 
151
139
  # Compact the database to remove stale commits and reduce the file size.
152
140
  def compact!
153
- # Create a new temporary file
154
- tmp_file = Tempfile.new File.basename(@file_name)
155
- copy_db = self.class.new tmp_file.path
141
+ # Create a new temporary database
142
+ tmp_file = @file_name + "-#{$$}-#{Thread.current.object_id}"
143
+ copy_db = self.class.new tmp_file
156
144
 
157
145
  # Copy the database key by key into the temporary table
158
- each do |key|
146
+ each do |key, value|
159
147
  copy_db.set(key, get(key))
160
148
  end
161
149
  copy_db.close!
162
150
 
163
- # Empty this database
164
- empty!
165
151
  close!
166
152
 
167
153
  # Move the copy into place
168
- tmp_file.close
169
- FileUtils.mv tmp_file.path, @file_name
170
- tmp_file.unlink
154
+ File.rename tmp_file, @file_name
171
155
 
172
- # Reset this database
173
- reset!
156
+ # Reopen this database
157
+ @writer = Writer.new(@file_name)
158
+ @table.clear
174
159
  read!
175
160
  end
176
161
 
177
162
  # Read all values from the log file. If you want to check for changed data
178
163
  # call this again.
179
164
  def read!
180
- @reader.read do |record|
181
- if record.deleted?
182
- @table.delete record.key
165
+ @reader.read do |(key, data, deleted)|
166
+ if deleted
167
+ @table.delete key
183
168
  else
184
- @table[record.key] = parse(record.data)
169
+ @table[key] = parse(data)
185
170
  end
186
171
  end
187
172
  end
@@ -189,7 +174,7 @@ module Daybreak
189
174
  private
190
175
 
191
176
  def write(key, value, sync = false, delete = false)
192
- @writer.write(Record.new(key, serialize(value), delete))
177
+ @writer.write([key, serialize(value), delete])
193
178
  flush! if sync
194
179
  end
195
180
  end
@@ -8,30 +8,19 @@ module Daybreak
8
8
  @file_name = file
9
9
  end
10
10
 
11
- # Close the Reader's file descriptor.
12
- def close!
13
- @fd.close unless @fd.nil? || !@fd.closed?
14
- end
15
-
16
11
  # Read all values from the aof file.
17
12
  #
18
13
  # Right now this is really expensive, every call to read will
19
14
  # close and reread the whole db file, but since cross process
20
15
  # consistency is handled by the user, this should be fair warning.
21
- def read(&blk)
22
- open!
23
- while !@fd.eof?
24
- blk.call(Record.read(@fd))
16
+ def read
17
+ File.open(@file_name, 'r') do |fd|
18
+ fd.binmode
19
+ fd.advise(:sequential) if fd.respond_to? :advise
20
+ while !fd.eof?
21
+ yield Record.read(fd)
22
+ end
25
23
  end
26
- close!
27
- end
28
-
29
- private
30
-
31
- def open!
32
- @fd = File.open @file_name, 'r'
33
- @fd.binmode
34
- @fd.advise(:sequential) if @fd.respond_to? :advise
35
24
  end
36
25
  end
37
26
  end
@@ -1,87 +1,67 @@
1
1
  module Daybreak
2
2
  # Records define how data is serialized and read from disk.
3
- class Record
3
+ module Record
4
4
  # Thrown when either key or data is missing
5
5
  class UnnacceptableDataError < Exception; end
6
6
 
7
7
  # Thrown when there is a CRC mismatch between the data from the disk
8
8
  # and what was written to disk previously.
9
9
  class CorruptDataError < Exception; end
10
- include Locking
11
10
 
12
- # The mask a record uses to check for deletion.
13
- DELETION_MASK = (1 << 31)
14
-
15
- attr_accessor :key, :data
11
+ extend self
12
+ extend Locking
16
13
 
17
- def initialize(key = nil, data = nil, deleted = false)
18
- @key = key
19
- @data = data
20
- if deleted
21
- @deleted = DELETION_MASK
22
- else
23
- @deleted = 0
24
- end
25
- end
14
+ # The mask a record uses to check for deletion.
15
+ DELETION_MASK = 1 << 31
26
16
 
27
17
  # Read a record from an open io source, check the CRC, and set <tt>@key</tt>
28
18
  # and <tt>@data</tt>.
29
19
  # @param [#read] io an IO instance to read from
30
- def read(io)
31
- lock io do
32
- @key = read_key(io)
33
- @data = read_data(io)
34
- crc = io.read(4)
35
- raise CorruptDataError, "CRC mismatch #{crc} should be #{crc_string}" unless crc == crc_string
36
- end
37
- self
38
- end
39
20
 
40
21
  # The serialized representation of the key value pair plus the CRC.
41
22
  # @return [String]
42
- def representation
43
- raise UnnacceptableDataError, "key and data must be defined" if @key.nil? || @data.nil?
44
- byte_string + crc_string
23
+ def serialize(record)
24
+ raise UnnacceptableDataError, 'key and data must be defined' unless record[0] && record[1]
25
+ s = key_data_string(record)
26
+ s << crc_string(s)
45
27
  end
46
28
 
47
29
  # Create a new record to read from IO.
48
30
  # @param [#read] io an IO instance to read from
49
- def self.read(io)
50
- new.read(io)
51
- end
52
-
53
- def deleted?
54
- @deleted > 0
31
+ def read(io)
32
+ lock io do
33
+ record = []
34
+ masked = read32(io)
35
+ # Read the record's key bytes
36
+ record << io.read(masked & (DELETION_MASK - 1)) <<
37
+ # Read the record's value bytes
38
+ io.read(read32(io)) <<
39
+ # Set the deletion flag
40
+ ((masked & DELETION_MASK) != 0)
41
+ crc = io.read(4)
42
+ raise CorruptDataError, 'CRC mismatch' unless crc == crc_string(key_data_string(record))
43
+ record
44
+ end
55
45
  end
56
46
 
57
47
  private
58
48
 
59
- def byte_string
60
- @byte_string ||= part(@key, @key.bytesize + @deleted) + part(@data, @data.bytesize)
61
- end
62
-
63
- def crc_string
64
- [Zlib.crc32(byte_string, 0)].pack('N')
49
+ # Return the deletion flag plus two length prefixed cells
50
+ def key_data_string(record)
51
+ part(record[0], record[0].bytesize + (record[2] ? DELETION_MASK : 0)) << part(record[1], record[1].bytesize)
65
52
  end
66
53
 
67
- def read_data(io)
68
- io.read read32(io)
54
+ def crc_string(s)
55
+ [Zlib.crc32(s, 0)].pack('N')
69
56
  end
70
57
 
71
- def read_key(io)
72
- masked = read32 io
73
- @deleted = masked & DELETION_MASK
74
- length = masked & (DELETION_MASK - 1)
75
- io.read length
58
+ def part(data, length)
59
+ [length].pack('N') << data
76
60
  end
77
61
 
78
62
  def read32(io)
79
63
  raw = io.read(4)
80
64
  raw.unpack('N')[0]
81
65
  end
82
-
83
- def part(data, length)
84
- [length].pack('N') + data
85
- end
86
66
  end
87
67
  end
@@ -1,4 +1,4 @@
1
1
  module Daybreak
2
2
  # Updated using SemVer
3
- VERSION = "0.1.1"
3
+ VERSION = "0.1.2"
4
4
  end
@@ -5,9 +5,7 @@ module Daybreak
5
5
  # Open up the file, ready it for binary and nonblocking writing.
6
6
  def initialize(file)
7
7
  @file = file
8
-
9
8
  open!
10
-
11
9
  @worker = Worker.new(@fd)
12
10
  end
13
11
 
@@ -65,20 +63,20 @@ module Daybreak
65
63
 
66
64
  # Queue up a write to be committed later.
67
65
  def enqueue(record)
68
- @queue << record.representation
66
+ @queue << record
69
67
  end
70
68
 
71
69
  # Loop and block if we don't have work to do or if
72
70
  # the file isn't ready for another write just yet.
73
71
  def work
74
- buf = ""
72
+ buf = ''
75
73
  loop do
76
- str = @queue.pop
77
- if str.nil?
74
+ record = @queue.pop
75
+ unless record
78
76
  @fd.flush
79
77
  break
80
78
  end
81
- buf << str
79
+ buf << Record.serialize(record)
82
80
  read, write = IO.select [], [@fd]
83
81
  if write and fd = write.first
84
82
  lock(fd, File::LOCK_EX) { buf = try_write fd, buf }
@@ -98,7 +96,7 @@ module Daybreak
98
96
  if s < buf.length
99
97
  buf = buf[s..-1] # didn't finish
100
98
  else
101
- buf = ""
99
+ buf = ''
102
100
  end
103
101
  rescue Errno::EAGAIN
104
102
  buf = buf # try this again
data/test/test.rb CHANGED
@@ -1,8 +1,12 @@
1
- require 'rubygems'
2
- require 'simplecov'
3
1
  require 'set'
4
- SimpleCov.start
5
- SimpleCov.command_name "Unit tests"
2
+
3
+ begin
4
+ require 'simplecov'
5
+ SimpleCov.start
6
+ SimpleCov.command_name "Unit tests"
7
+ rescue Exception => ex
8
+ puts "No coverage report generated: #{ex.message}"
9
+ end
6
10
 
7
11
  require File.expand_path(File.dirname(__FILE__)) + '/test_helper.rb'
8
12
 
data/test/test_helper.rb CHANGED
@@ -4,4 +4,5 @@ require 'minitest/benchmark'
4
4
  HERE = File.expand_path(File.dirname(__FILE__))
5
5
  DB_PATH = File.join HERE, "test.db"
6
6
 
7
- require File.join HERE, '..', 'lib', 'daybreak'
7
+ $: << File.join(HERE, '..', 'lib')
8
+ require 'daybreak'
metadata CHANGED
@@ -1,72 +1,63 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: daybreak
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
4
5
  prerelease:
5
- version: 0.1.1
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 2
10
+ version: 0.1.2
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Jeff Larson
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2013-01-07 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- version_requirements: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ! '>='
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- none: false
21
- name: minitest
22
- type: :development
17
+
18
+ date: 2013-01-07 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rake
23
23
  prerelease: false
24
- requirement: !ruby/object:Gem::Requirement
25
- requirements:
26
- - - ! '>='
27
- - !ruby/object:Gem::Version
28
- version: '0'
29
- none: false
30
- - !ruby/object:Gem::Dependency
31
- version_requirements: !ruby/object:Gem::Requirement
32
- requirements:
33
- - - ! '>='
34
- - !ruby/object:Gem::Version
35
- version: '0'
24
+ requirement: &id001 !ruby/object:Gem::Requirement
36
25
  none: false
37
- name: simplecov
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
38
33
  type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: minitest
39
37
  prerelease: false
40
- requirement: !ruby/object:Gem::Requirement
41
- requirements:
42
- - - ! '>='
43
- - !ruby/object:Gem::Version
44
- version: '0'
45
- none: false
46
- - !ruby/object:Gem::Dependency
47
- version_requirements: !ruby/object:Gem::Requirement
48
- requirements:
49
- - - ! '>='
50
- - !ruby/object:Gem::Version
51
- version: '0'
38
+ requirement: &id002 !ruby/object:Gem::Requirement
52
39
  none: false
53
- name: ruby-prof
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
54
47
  type: :development
55
- prerelease: false
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ! '>='
59
- - !ruby/object:Gem::Version
60
- version: '0'
61
- none: false
48
+ version_requirements: *id002
62
49
  description: A simple dimple key-value store for ruby.
63
- email:
50
+ email:
64
51
  - thejefflarson@gmail.com
65
52
  executables: []
53
+
66
54
  extensions: []
55
+
67
56
  extra_rdoc_files: []
68
- files:
57
+
58
+ files:
69
59
  - .gitignore
60
+ - .travis.yml
70
61
  - .yardopts
71
62
  - EPIGRAPH
72
63
  - Gemfile
@@ -86,36 +77,43 @@ files:
86
77
  - test/prof.rb
87
78
  - test/test.rb
88
79
  - test/test_helper.rb
80
+ has_rdoc: true
89
81
  homepage: http://propublica.github.com/daybreak/
90
- licenses:
82
+ licenses:
91
83
  - MIT
92
84
  post_install_message:
93
85
  rdoc_options: []
94
- require_paths:
86
+
87
+ require_paths:
95
88
  - lib
96
- required_ruby_version: !ruby/object:Gem::Requirement
97
- requirements:
98
- - - ! '>='
99
- - !ruby/object:Gem::Version
100
- version: '0'
89
+ required_ruby_version: !ruby/object:Gem::Requirement
101
90
  none: false
102
- required_rubygems_version: !ruby/object:Gem::Requirement
103
- requirements:
104
- - - ! '>='
105
- - !ruby/object:Gem::Version
106
- version: '0'
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
99
  none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
108
107
  requirements: []
108
+
109
109
  rubyforge_project:
110
- rubygems_version: 1.8.23
110
+ rubygems_version: 1.6.2
111
111
  signing_key:
112
112
  specification_version: 3
113
- summary: Daybreak provides an in memory key-value store that is easily enumerable
114
- in ruby.
115
- test_files:
113
+ summary: Daybreak provides an in memory key-value store that is easily enumerable in ruby.
114
+ test_files:
116
115
  - test/bench.rb
117
116
  - test/compare.rb
118
117
  - test/prof.rb
119
118
  - test/test.rb
120
119
  - test/test_helper.rb
121
- has_rdoc: