higgs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/ChangeLog +208 -0
  2. data/LICENSE +26 -0
  3. data/README +2 -0
  4. data/Rakefile +75 -0
  5. data/bin/higgs_backup +67 -0
  6. data/bin/higgs_dump_index +43 -0
  7. data/bin/higgs_dump_jlog +42 -0
  8. data/bin/higgs_verify +37 -0
  9. data/lib/cgi/session/higgs.rb +72 -0
  10. data/lib/higgs/block.rb +192 -0
  11. data/lib/higgs/cache.rb +117 -0
  12. data/lib/higgs/dbm.rb +55 -0
  13. data/lib/higgs/exceptions.rb +31 -0
  14. data/lib/higgs/flock.rb +77 -0
  15. data/lib/higgs/index.rb +164 -0
  16. data/lib/higgs/jlog.rb +159 -0
  17. data/lib/higgs/lock.rb +189 -0
  18. data/lib/higgs/storage.rb +1086 -0
  19. data/lib/higgs/store.rb +228 -0
  20. data/lib/higgs/tar.rb +390 -0
  21. data/lib/higgs/thread.rb +370 -0
  22. data/lib/higgs/tman.rb +513 -0
  23. data/lib/higgs/utils/bman.rb +285 -0
  24. data/lib/higgs/utils.rb +22 -0
  25. data/lib/higgs/version.rb +21 -0
  26. data/lib/higgs.rb +59 -0
  27. data/misc/cache_bench/cache_bench.rb +43 -0
  28. data/misc/dbm_bench/.strc +8 -0
  29. data/misc/dbm_bench/Rakefile +78 -0
  30. data/misc/dbm_bench/dbm_multi_thread.rb +199 -0
  31. data/misc/dbm_bench/dbm_rnd_delete.rb +43 -0
  32. data/misc/dbm_bench/dbm_rnd_read.rb +44 -0
  33. data/misc/dbm_bench/dbm_rnd_update.rb +44 -0
  34. data/misc/dbm_bench/dbm_seq_read.rb +45 -0
  35. data/misc/dbm_bench/dbm_seq_write.rb +44 -0
  36. data/misc/dbm_bench/st_verify.rb +28 -0
  37. data/misc/io_bench/cksum_bench.rb +48 -0
  38. data/misc/io_bench/jlog_bench.rb +71 -0
  39. data/misc/io_bench/write_bench.rb +128 -0
  40. data/misc/thread_bench/lock_bench.rb +132 -0
  41. data/mkrdoc.rb +8 -0
  42. data/rdoc.yml +13 -0
  43. data/sample/count.rb +60 -0
  44. data/sample/dbmtest.rb +38 -0
  45. data/test/Rakefile +45 -0
  46. data/test/run.rb +32 -0
  47. data/test/test_block.rb +163 -0
  48. data/test/test_cache.rb +214 -0
  49. data/test/test_cgi_session.rb +142 -0
  50. data/test/test_flock.rb +162 -0
  51. data/test/test_index.rb +258 -0
  52. data/test/test_jlog.rb +180 -0
  53. data/test/test_lock.rb +320 -0
  54. data/test/test_online_backup.rb +169 -0
  55. data/test/test_storage.rb +439 -0
  56. data/test/test_storage_conf.rb +202 -0
  57. data/test/test_storage_init_opts.rb +89 -0
  58. data/test/test_store.rb +211 -0
  59. data/test/test_tar.rb +432 -0
  60. data/test/test_thread.rb +541 -0
  61. data/test/test_tman.rb +875 -0
  62. data/test/test_tman_init_opts.rb +56 -0
  63. data/test/test_utils_bman.rb +234 -0
  64. metadata +115 -0
@@ -0,0 +1,192 @@
1
+ # = block read/write
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ require 'higgs/exceptions'
12
+
13
+ module Higgs
14
+ # block header format
15
+ #
16
+ # 0..15 : Z16 : magic symbol
17
+ # 16..19 : V : body length
18
+ # 20..31 : x12 : (reserved)
19
+ # 32..33 : v : format version
20
+ # 34..35 : x2 : (reserved)
21
+ # 36..37 : v : head cksum
22
+ # 38..39 : x2 : (reserved)
23
+ # 40..55 : Z16 : body hash type
24
+ # 56..57 : v : body hash length
25
+ # 58..71 : x14 : (reserved)
26
+ # 73..511 : a440 : body hash binary
27
+ #
28
+ module Block
29
+ # for ident(1)
30
+ CVS_ID = '$Id: block.rb 559 2007-09-25 15:20:20Z toki $'
31
+
32
+ include Exceptions
33
+
34
+ class BrokenError < HiggsError
35
+ end
36
+
37
+ BLOCK_SIZE = 512
38
+
39
+ FMT_VERSION = 0x00_00
40
+ HEAD_CKSUM_BITS = 16
41
+ HEAD_CKSUM_POS = 36..37
42
+ HEAD_CKSUM_FMT = 'v'
43
+
44
+ BODY_HASH = {}
45
+ [ [ :SUM16, proc{|s| s.sum(16).to_s }, nil ],
46
+ [ :MD5, proc{|s| Digest::MD5.digest(s) }, 'digest/md5' ],
47
+ [ :RMD160, proc{|s| Digest::RMD160.digest(s) }, 'digest/rmd160' ],
48
+ [ :SHA1, proc{|s| Digest::SHA1.digest(s) }, 'digest/sha1' ],
49
+ [ :SHA256, proc{|s| Digest::SHA256.digest(s) }, 'digest/sha2' ],
50
+ [ :SHA384, proc{|s| Digest::SHA384.digest(s) }, 'digest/sha2' ],
51
+ [ :SHA512, proc{|s| Digest::SHA512.digest(s) }, 'digest/sha2' ]
52
+ ].each do |hash_symbol, hash_proc, hash_lib|
53
+ if (hash_lib) then
54
+ begin
55
+ require(hash_lib)
56
+ rescue LoadError
57
+ next
58
+ end
59
+ end
60
+ BODY_HASH[hash_symbol] = hash_proc
61
+ end
62
+
63
+ BODY_HASH_BIN = {}
64
+ BODY_HASH.each do |hash_symbol, hash_proc|
65
+ BODY_HASH_BIN[hash_symbol.to_s] = hash_proc
66
+ end
67
+
68
+ HEAD_FMT = [
69
+ 'Z16', # magic symbol
70
+ 'V', # body length
71
+ 'x12', # (reserved)
72
+ 'v', # format version
73
+ 'x2', # (reserved)
74
+ HEAD_CKSUM_FMT, # head cksum
75
+ 'x2', # (reserved)
76
+ 'Z16', # body hash type
77
+ 'v', # body hash length
78
+ 'x14', # (reserved)
79
+ 'a440' # body hash binary
80
+ ].join('')
81
+
82
+ def padding_size(bytes)
83
+ r = bytes % BLOCK_SIZE
84
+ (r > 0) ? BLOCK_SIZE - r : 0
85
+ end
86
+ module_function :padding_size
87
+
88
+ def head_read(io, magic_symbol)
89
+ head_block = io.read(BLOCK_SIZE) or return
90
+ if (head_block.size != BLOCK_SIZE) then
91
+ raise BrokenError, 'short read'
92
+ end
93
+
94
+ _magic_symbol, body_len, fmt_version, head_cksum,
95
+ body_hash_type, body_hash_len, body_hash_bucket = head_block.unpack(HEAD_FMT)
96
+
97
+ head_block[HEAD_CKSUM_POS] = "\000\000"
98
+ if (head_block.sum(HEAD_CKSUM_BITS) != head_cksum) then
99
+ raise BrokenError, 'broken head block'
100
+ end
101
+
102
+ if (_magic_symbol != magic_symbol) then
103
+ raise BrokenError, "unknown magic symbol: #{_magic_symbol}"
104
+ end
105
+
106
+ if (fmt_version != FMT_VERSION) then
107
+ raise BrokenError, format('unknown format version: 0x%04F', fmt_version)
108
+ end
109
+
110
+ body_hash_bin = body_hash_bucket[0, body_hash_len]
111
+
112
+ return body_len, body_hash_type, body_hash_bin
113
+ end
114
+ module_function :head_read
115
+
116
+ def head_write(io, magic_symbol, body_len, body_hash_type, body_hash_bin)
117
+ head_block = [
118
+ magic_symbol,
119
+ body_len,
120
+ FMT_VERSION,
121
+ 0,
122
+ body_hash_type,
123
+ body_hash_bin.length,
124
+ body_hash_bin
125
+ ].pack(HEAD_FMT)
126
+
127
+ head_cksum = head_block.sum(HEAD_CKSUM_BITS)
128
+ head_block[HEAD_CKSUM_POS] = [ head_cksum ].pack(HEAD_CKSUM_FMT)
129
+
130
+ bytes = io.write(head_block)
131
+ if (bytes != head_block.size) then
132
+ raise BrokenError, 'short write'
133
+ end
134
+ bytes
135
+ end
136
+ module_function :head_write
137
+
138
+ def block_read(io, magic_symbol)
139
+ body_len, body_hash_type, body_hash_bin = head_read(io, magic_symbol)
140
+ unless (body_len) then
141
+ return
142
+ end
143
+
144
+ body = io.read(body_len) or raise BrokenError, 'unexpected EOF'
145
+ if (body.length != body_len) then
146
+ raise BrokenError, 'short read'
147
+ end
148
+
149
+ unless (hash_proc = BODY_HASH_BIN[body_hash_type]) then
150
+ raise BrokenError, "unknown body hash type: #{body_hash_type}"
151
+ end
152
+
153
+ if (hash_proc.call(body) != body_hash_bin) then
154
+ raise BrokenError, 'body hash error'
155
+ end
156
+
157
+ padding_size = padding_size(body_len)
158
+ padding = io.read(padding_size) or raise BrokenError, 'unexpected EOF'
159
+ if (padding.size != padding_size) then
160
+ raise BrokenError, 'short read'
161
+ end
162
+
163
+ body
164
+ end
165
+ module_function :block_read
166
+
167
+ def block_write(io, magic_symbol, body, body_hash_type=:MD5)
168
+ hash_proc = BODY_HASH[body_hash_type.to_sym] or "unknown body hash type: #{body_hash_type}"
169
+ body_hash = hash_proc.call(body)
170
+ head_write(io, magic_symbol, body.length, body_hash_type.to_s, body_hash)
171
+
172
+ bytes = io.write(body)
173
+ if (bytes != body.size) then
174
+ raise BrokenError, 'short write'
175
+ end
176
+
177
+ padding_size = padding_size(body.length)
178
+ bytes = io.write("\0" * padding_size)
179
+ if (bytes != padding_size) then
180
+ raise BrokenError, 'short write'
181
+ end
182
+
183
+ body.size + padding_size
184
+ end
185
+ module_function :block_write
186
+ end
187
+ end
188
+
189
+ # Local Variables:
190
+ # mode: Ruby
191
+ # indent-tabs-mode: nil
192
+ # End:
@@ -0,0 +1,117 @@
1
+ # = cache utilities
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ require 'forwardable'
12
+ require 'higgs/thread'
13
+ require 'thread'
14
+
15
+ module Higgs
16
+ # = cache by Least Recently Used strategy
17
+ class LRUCache
18
+ # for ident(1)
19
+ CVS_ID = '$Id: cache.rb 559 2007-09-25 15:20:20Z toki $'
20
+
21
+ extend Forwardable
22
+
23
+ def initialize(limit_size=1000)
24
+ @limit_size = limit_size
25
+ @count = 0
26
+ @cache = {}
27
+ end
28
+
29
+ attr_reader :limit_size
30
+ def_delegator :@cache, :keys
31
+ def_delegator :@cache, :key?
32
+ alias has_key? key?
33
+ alias include? key?
34
+
35
+ def [](key)
36
+ if (cached_pair = @cache[key]) then
37
+ cached_pair[1] = @count
38
+ @count = @count.succ
39
+ return cached_pair[0]
40
+ end
41
+ nil
42
+ end
43
+
44
+ def []=(key, value)
45
+ if (cached_pair = @cache[key]) then
46
+ cached_pair[1] = @count
47
+ else
48
+ @cache[key] = [ value, @count ]
49
+ end
50
+ @count = @count.succ
51
+ if (@cache.size > @limit_size) then
52
+ purge_old_cache
53
+ end
54
+ value
55
+ end
56
+
57
+ def purge_old_cache
58
+ c_list = @cache.map{|key, (value, c)| c }
59
+ c_list.sort!
60
+ threshold = c_list[c_list.size / 2]
61
+ @cache.delete_if{|key, (value, c)| c < threshold }
62
+ end
63
+ private :purge_old_cache
64
+
65
+ def delete(key)
66
+ if (cached_pair = @cache.delete(key)) then
67
+ return cached_pair[0]
68
+ end
69
+ nil
70
+ end
71
+ end
72
+
73
+ # = multi-thread safe cache
74
+ class SharedWorkCache
75
+ # for ident(1)
76
+ CVS_ID = '$Id: cache.rb 559 2007-09-25 15:20:20Z toki $'
77
+
78
+ def initialize(cache={}, &work)
79
+ unless (work) then
80
+ raise 'required work block'
81
+ end
82
+ @work = work
83
+ @lock = Mutex.new
84
+ @cache = cache
85
+ end
86
+
87
+ def fetch_work(key)
88
+ @lock.synchronize{
89
+ if (@cache.key? key) then
90
+ @cache[key]
91
+ else
92
+ @cache[key] = SharedWork.new{ @work.call(key) }
93
+ end
94
+ }
95
+ end
96
+ private :fetch_work
97
+
98
+ def [](key)
99
+ fetch_work(key).result
100
+ end
101
+
102
+ def []=(key, value)
103
+ work = fetch_work(key)
104
+ work.result = value
105
+ end
106
+
107
+ def delete(key)
108
+ r = @lock.synchronize{ @cache.delete(key) }
109
+ r ? true : false
110
+ end
111
+ end
112
+ end
113
+
114
+ # Local Variables:
115
+ # mode: Ruby
116
+ # indent-tabs-mode: nil
117
+ # End:
data/lib/higgs/dbm.rb ADDED
@@ -0,0 +1,55 @@
1
+ # = storage like dbm
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ require 'higgs/storage'
12
+ require 'higgs/tman'
13
+
14
+ module Higgs
15
+ # = storage like dbm
16
+ # == sample script
17
+ # sample/dbmtest.rb
18
+ # :include: sample/dbmtest.rb
19
+ #
20
+ class DBM
21
+ # for ident(1)
22
+ CVS_ID = '$Id: dbm.rb 559 2007-09-25 15:20:20Z toki $'
23
+
24
+ include Storage::Export
25
+ include TransactionManager::Export
26
+
27
+ DECODE = proc{|r| r }
28
+ ENCODE = proc{|w| w }
29
+
30
+ # <tt>name</tt> is a storage name and see Higgs::Storage.new for
31
+ # detail. see Higgs::Storage::InitOptions and
32
+ # Higgs::TransactionManager::InitOptions for <tt>options</tt>.
33
+ def initialize(name, options={})
34
+ @storage = Storage.new(name, options)
35
+ options[:decode] = DECODE
36
+ options[:encode] = ENCODE
37
+ @tman = TransactionManager.new(@storage, options)
38
+ end
39
+
40
+ def self.open(*args)
41
+ dbm = new(*args)
42
+ begin
43
+ r = yield(dbm)
44
+ ensure
45
+ dbm.shutdown
46
+ end
47
+ r
48
+ end
49
+ end
50
+ end
51
+
52
+ # Local Variables:
53
+ # mode: Ruby
54
+ # indent-tabs-mode: nil
55
+ # End:
@@ -0,0 +1,31 @@
1
+ # = exceptions
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ module Higgs
12
+ module Exceptions
13
+ # for ident(1)
14
+ CVS_ID = '$Id: exceptions.rb 559 2007-09-25 15:20:20Z toki $'
15
+
16
+ class HiggsError < StandardError
17
+ end
18
+
19
+ class HiggsException < Exception
20
+ end
21
+
22
+ class ShutdownException < HiggsException
23
+ end
24
+ end
25
+ include Exceptions
26
+ end
27
+
28
+ # Local Variables:
29
+ # mode: Ruby
30
+ # indent-tabs-mode: nil
31
+ # End:
@@ -0,0 +1,77 @@
1
+ # = file lock
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ module Higgs
12
+ class FileLock
13
+ # for ident(1)
14
+ CVS_ID = '$Id: flock.rb 559 2007-09-25 15:20:20Z toki $'
15
+
16
+ def initialize(path, read_only=false)
17
+ @path = path
18
+ @read_only = read_only
19
+ rdwr_mode = (@read_only) ? File::RDONLY : File::RDWR
20
+ begin
21
+ @f = File.open(path, rdwr_mode | File::CREAT | File::EXCL, 0660)
22
+ rescue Errno::EEXIST
23
+ @f = File.open(path, rdwr_mode, 0660)
24
+ end
25
+ end
26
+
27
+ attr_reader :read_only
28
+
29
+ def read_lock
30
+ @f.flock(File::LOCK_SH)
31
+ nil
32
+ end
33
+
34
+ def write_lock
35
+ if (@read_only) then
36
+ raise 'read only'
37
+ end
38
+ @f.flock(File::LOCK_EX)
39
+ nil
40
+ end
41
+
42
+ def unlock
43
+ @f.flock(File::LOCK_UN)
44
+ nil
45
+ end
46
+
47
+ def close
48
+ unlock
49
+ @f.close
50
+ nil
51
+ end
52
+
53
+ def synchronize(mode=:EX)
54
+ case (mode)
55
+ when :SH
56
+ read_lock
57
+ when :EX
58
+ write_lock
59
+ else
60
+ raise ArgumentError, "unknown lock mode: #{mode}"
61
+ end
62
+
63
+ begin
64
+ r = yield
65
+ ensure
66
+ unlock
67
+ end
68
+
69
+ r
70
+ end
71
+ end
72
+ end
73
+
74
+ # Local Variables:
75
+ # mode: Ruby
76
+ # indent-tabs-mode: nil
77
+ # End:
@@ -0,0 +1,164 @@
1
+ # = storage index
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ require 'forwardable'
12
+ require 'higgs/block'
13
+
14
+ module Higgs
15
+ # = storage index
16
+ class Index
17
+ # for ident(1)
18
+ CVS_ID = '$Id: index.rb 559 2007-09-25 15:20:20Z toki $'
19
+
20
+ extend Forwardable
21
+ include Block
22
+
23
+ MAGIC_SYMBOL = 'HIGGS_INDEX'
24
+ MAJOR_VERSION = 0
25
+ MINOR_VERSION = 1
26
+
27
+ def initialize
28
+ @change_number = 0
29
+ @eoa = 0
30
+ @free_lists = {}
31
+ @index = {}
32
+ @identities = {}
33
+ end
34
+
35
+ attr_reader :change_number
36
+ attr_accessor :eoa
37
+
38
+ def succ!
39
+ @change_number = @change_number.succ
40
+ self
41
+ end
42
+
43
+ def free_fetch(size)
44
+ @free_lists[size].shift if (@free_lists.key? size)
45
+ end
46
+
47
+ def free_fetch_at(pos, size)
48
+ @free_lists[size].delete(pos) or raise "not found free region: (#{pos},#{size})"
49
+ end
50
+
51
+ def free_store(pos, size)
52
+ @free_lists[size] = [] unless (@free_lists.key? size)
53
+ @free_lists[size] << pos
54
+ nil
55
+ end
56
+
57
+ def_delegator :@index, :key?
58
+ def_delegator :@index, :keys
59
+
60
+ def identity(key)
61
+ i = @index[key] and i[0]
62
+ end
63
+
64
+ def [](key)
65
+ i = @index[key] and i[1]
66
+ end
67
+
68
+ def self.create_id(key, identities)
69
+ id = key.to_s
70
+ if (identities.key? id) then
71
+ id += '.a'
72
+ id.succ! while (identities.key? id)
73
+ end
74
+ id
75
+ end
76
+
77
+ def []=(key, value)
78
+ delete(key)
79
+ id = Index.create_id(key, @identities)
80
+ @identities[id] = key
81
+ @index[key] = [ id, value ]
82
+ value
83
+ end
84
+
85
+ def delete(key)
86
+ id, value = @index.delete(key)
87
+ @identities.delete(id) if id
88
+ value
89
+ end
90
+
91
+ def each_key
92
+ @index.each_key do |key|
93
+ yield(key)
94
+ end
95
+ self
96
+ end
97
+
98
+ def to_h
99
+ { :version => [ MAJOR_VERSION, MINOR_VERSION ],
100
+ :change_number => @change_number,
101
+ :eoa => @eoa,
102
+ :free_lists => @free_lists,
103
+ :index => @index,
104
+ :identities => @identities
105
+ }
106
+ end
107
+
108
+ def migration_0_0_to_0_1(index_data)
109
+ if ((index_data[:version] <=> [ 0, 0 ]) > 0) then
110
+ return
111
+ end
112
+ if ((index_data[:version] <=> [ 0, 0 ]) < 0) then
113
+ raise "unexpected index format version: #{index_data[:version].join('.')}"
114
+ end
115
+
116
+ index = index_data[:index]
117
+ identities = index_data[:identities] = {}
118
+ for key in index.keys
119
+ id = Index.create_id(key, identities)
120
+ identities[id] = key
121
+ value = index[key]
122
+ index[key] = [ id, value ]
123
+ end
124
+ index_data[:version] = [ 0, 1 ]
125
+
126
+ index_data
127
+ end
128
+ private :migration_0_0_to_0_1
129
+
130
+ def save(path)
131
+ tmp_path = "#{path}.tmp.#{$$}"
132
+ File.open(tmp_path, File::WRONLY | File::CREAT | File::TRUNC, 0660) {|f|
133
+ f.binmode
134
+ index_data = self.to_h
135
+ block_write(f, MAGIC_SYMBOL, Marshal.dump(index_data))
136
+ f.fsync
137
+ }
138
+ File.rename(tmp_path, path)
139
+ self
140
+ end
141
+
142
+ def load(path)
143
+ File.open(path, 'r') {|f|
144
+ f.binmode
145
+ index_data = Marshal.load(block_read(f, MAGIC_SYMBOL))
146
+ migration_0_0_to_0_1(index_data)
147
+ if ((index_data[:version] <=> [ MAJOR_VERSION, MINOR_VERSION ]) > 0) then
148
+ raise "unsupported version: #{index_data[:version].join('.')}"
149
+ end
150
+ @change_number = index_data[:change_number]
151
+ @eoa = index_data[:eoa]
152
+ @free_lists = index_data[:free_lists]
153
+ @index = index_data[:index]
154
+ @identities = index_data[:identities]
155
+ }
156
+ self
157
+ end
158
+ end
159
+ end
160
+
161
+ # Local Variables:
162
+ # mode: Ruby
163
+ # indent-tabs-mode: nil
164
+ # End: