rubcask 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.standard.yml +1 -1
- data/Gemfile +2 -2
- data/Gemfile.lock +42 -26
- data/lib/rubcask/data_entry.rb +1 -1
- data/lib/rubcask/data_file.rb +69 -26
- data/lib/rubcask/directory.rb +20 -24
- data/lib/rubcask/hinted_file.rb +1 -1
- data/lib/rubcask/marshaled_directory.rb +58 -0
- data/lib/rubcask/protocol.rb +1 -1
- data/lib/rubcask/server/abstract_server.rb +20 -24
- data/lib/rubcask/server/async.rb +35 -3
- data/lib/rubcask/server/client.rb +1 -1
- data/lib/rubcask/server/runner.rb +4 -3
- data/lib/rubcask/server/threaded.rb +24 -3
- data/lib/rubcask/tombstone.rb +5 -14
- data/lib/rubcask/version.rb +1 -1
- data/lib/rubcask.rb +1 -0
- metadata +18 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e54816bf08930641478dc6a0438debb94e82f774b2fcaa857fd0f6786f7adc6b
|
4
|
+
data.tar.gz: be69c187580c84f89572a445296728f5b987a48252c6470cf43853700aa6b1a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 026aaaffc7a9ae96447a21cdd9690fe9bde5b0af59be11e3a04cafea0cce7630916e789baa9fd9f68b6c2082275ca5c096e95441efe35b12df70101a37ae69b8
|
7
|
+
data.tar.gz: 870854934247751b5b0597f09c73f5ae256ea02540b5a93b383071c6c0324f515cb0c56e77eed4d586f6d0ed937fadb0fcb2aa3fc17c0a9b5d189d142637f304
|
data/.standard.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,60 +1,76 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rubcask (0.
|
4
|
+
rubcask (0.2.0)
|
5
5
|
concurrent-ruby (~> 1.1)
|
6
|
+
stringio (~> 3.1)
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: https://rubygems.org/
|
9
10
|
specs:
|
10
11
|
ast (2.4.2)
|
11
12
|
benchmark-ips (2.10.0)
|
12
|
-
concurrent-ruby (1.
|
13
|
+
concurrent-ruby (1.2.0)
|
13
14
|
docile (1.4.0)
|
14
|
-
json (2.
|
15
|
-
json (2.
|
15
|
+
json (2.7.1)
|
16
|
+
json (2.7.1-java)
|
16
17
|
kalibera (0.1.2)
|
17
18
|
memoist (~> 0.16)
|
18
19
|
rbzip2 (~> 0.3)
|
19
|
-
language_server-protocol (3.17.0.
|
20
|
+
language_server-protocol (3.17.0.3)
|
21
|
+
lint_roller (1.1.0)
|
20
22
|
memoist (0.16.2)
|
21
|
-
minitest (5.
|
22
|
-
parallel (1.
|
23
|
-
parser (3.
|
23
|
+
minitest (5.25.4)
|
24
|
+
parallel (1.24.0)
|
25
|
+
parser (3.2.2.4)
|
24
26
|
ast (~> 2.4.1)
|
27
|
+
racc
|
28
|
+
racc (1.7.3)
|
29
|
+
racc (1.7.3-java)
|
25
30
|
rainbow (3.1.1)
|
26
31
|
rake (13.0.6)
|
27
32
|
rbzip2 (0.3.0)
|
28
|
-
regexp_parser (2.
|
29
|
-
rexml (3.2.
|
30
|
-
rubocop (1.
|
33
|
+
regexp_parser (2.8.3)
|
34
|
+
rexml (3.2.6)
|
35
|
+
rubocop (1.59.0)
|
31
36
|
json (~> 2.3)
|
37
|
+
language_server-protocol (>= 3.17.0)
|
32
38
|
parallel (~> 1.10)
|
33
|
-
parser (>= 3.
|
39
|
+
parser (>= 3.2.2.4)
|
34
40
|
rainbow (>= 2.2.2, < 4.0)
|
35
41
|
regexp_parser (>= 1.8, < 3.0)
|
36
42
|
rexml (>= 3.2.5, < 4.0)
|
37
|
-
rubocop-ast (>= 1.
|
43
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
38
44
|
ruby-progressbar (~> 1.7)
|
39
|
-
unicode-display_width (>=
|
40
|
-
rubocop-ast (1.
|
41
|
-
parser (>= 3.
|
42
|
-
rubocop-performance (1.
|
43
|
-
rubocop (>= 1.
|
44
|
-
rubocop-ast (>=
|
45
|
-
ruby-progressbar (1.
|
45
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
46
|
+
rubocop-ast (1.30.0)
|
47
|
+
parser (>= 3.2.1.0)
|
48
|
+
rubocop-performance (1.20.1)
|
49
|
+
rubocop (>= 1.48.1, < 2.0)
|
50
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
51
|
+
ruby-progressbar (1.13.0)
|
46
52
|
simplecov (0.22.0)
|
47
53
|
docile (~> 1.1)
|
48
54
|
simplecov-html (~> 0.11)
|
49
55
|
simplecov_json_formatter (~> 0.1)
|
50
56
|
simplecov-html (0.12.3)
|
51
57
|
simplecov_json_formatter (0.1.4)
|
52
|
-
standard (1.
|
58
|
+
standard (1.33.0)
|
53
59
|
language_server-protocol (~> 3.17.0.2)
|
54
|
-
|
55
|
-
rubocop
|
60
|
+
lint_roller (~> 1.0)
|
61
|
+
rubocop (~> 1.59.0)
|
62
|
+
standard-custom (~> 1.0.0)
|
63
|
+
standard-performance (~> 1.3)
|
64
|
+
standard-custom (1.0.2)
|
65
|
+
lint_roller (~> 1.0)
|
66
|
+
rubocop (~> 1.50)
|
67
|
+
standard-performance (1.3.0)
|
68
|
+
lint_roller (~> 1.1)
|
69
|
+
rubocop-performance (~> 1.20.1)
|
70
|
+
stringio (3.1.0)
|
71
|
+
stringio (3.1.0-java)
|
56
72
|
timecop (0.9.6)
|
57
|
-
unicode-display_width (2.
|
73
|
+
unicode-display_width (2.5.0)
|
58
74
|
|
59
75
|
PLATFORMS
|
60
76
|
ruby
|
@@ -63,11 +79,11 @@ PLATFORMS
|
|
63
79
|
DEPENDENCIES
|
64
80
|
benchmark-ips (~> 2.10)
|
65
81
|
kalibera (~> 0.1.2)
|
66
|
-
minitest (~> 5.
|
82
|
+
minitest (~> 5.25)
|
67
83
|
rake (~> 13.0)
|
68
84
|
rubcask!
|
69
85
|
simplecov (~> 0.22.0)
|
70
|
-
standard (~> 1.
|
86
|
+
standard (~> 1.33)
|
71
87
|
timecop (~> 0.9.6)
|
72
88
|
|
73
89
|
BUNDLED WITH
|
data/lib/rubcask/data_entry.rb
CHANGED
data/lib/rubcask/data_file.rb
CHANGED
@@ -12,8 +12,14 @@ module Rubcask
|
|
12
12
|
|
13
13
|
attr_reader :write_pos
|
14
14
|
|
15
|
-
|
15
|
+
HEADER_SIZE = 4 + 8 + 2 + 4
|
16
|
+
|
16
17
|
HEADER_WITHOUT_CRC_FORMAT = "Q>nN"
|
18
|
+
HEADER_FORMAT = "N#{HEADER_WITHOUT_CRC_FORMAT}"
|
19
|
+
|
20
|
+
EXPIRE_MASK = ~(1 << 63)
|
21
|
+
DELETED_MASK = (1 << 63)
|
22
|
+
MAX_EXPIRE_VALUE = DELETED_MASK - 1
|
17
23
|
|
18
24
|
# @param [File] file File with the data
|
19
25
|
# @param [Integer] file_size Current size of `file` in bytes
|
@@ -22,18 +28,35 @@ module Rubcask
|
|
22
28
|
@write_pos = file_size
|
23
29
|
end
|
24
30
|
|
31
|
+
# @!macro [new] might_change_pos
|
32
|
+
# @note Calling this method might change `pos` of the `file`
|
33
|
+
|
34
|
+
# @!macro [new] no_change_pos
|
35
|
+
# @note Calling this method will not change `pos` of the `file`
|
36
|
+
|
37
|
+
# @!macro [new] read_result_return
|
38
|
+
# @return [DataEntry]
|
39
|
+
# @return [nil] if at the end of file
|
40
|
+
# @raise [ChecksumError] if the entry has an incorrect checksum
|
41
|
+
|
25
42
|
# Fetch entry at given offset.
|
26
|
-
#
|
43
|
+
# With optional size parameter we can do less I/O operations.
|
44
|
+
# @macro might_change_pos
|
27
45
|
# @param [Integer] offset File offset in bytes
|
28
|
-
# @param [Integer, nil] size
|
46
|
+
# @param [Integer, nil] size Entry size in bytes
|
47
|
+
# @macro read_result_return
|
29
48
|
def [](offset, size = nil)
|
30
|
-
|
31
|
-
|
49
|
+
if size.nil?
|
50
|
+
seek(offset)
|
51
|
+
return read
|
52
|
+
end
|
53
|
+
pread(offset, size)
|
32
54
|
end
|
33
55
|
|
34
|
-
# yields each
|
56
|
+
# yields each entry in the file
|
57
|
+
# @macro might_change_pos
|
35
58
|
# @return [Enumerator] if no block given
|
36
|
-
# @yieldparam [DataEntry]
|
59
|
+
# @yieldparam [DataEntry] data_entry Entry from the file
|
37
60
|
def each
|
38
61
|
return to_enum(__method__) unless block_given?
|
39
62
|
|
@@ -46,46 +69,66 @@ module Rubcask
|
|
46
69
|
end
|
47
70
|
end
|
48
71
|
|
49
|
-
# Read entry at the current file position
|
50
|
-
# @
|
51
|
-
# @
|
52
|
-
# @
|
72
|
+
# Read an entry at the current file position
|
73
|
+
# @macro might_change_pos
|
74
|
+
# @param [Integer, nil] size Entry size in bytes
|
75
|
+
# @macro read_result_return
|
53
76
|
def read(size = nil)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
crc, expire_timestamp, key_size, value_size = header.unpack(HEADER_FORMAT)
|
60
|
-
key = io.read(key_size)
|
61
|
-
value = io.read(value_size)
|
77
|
+
read_from_io(
|
78
|
+
size ? StringIO.new(@file.read(size)) : @file
|
79
|
+
)
|
80
|
+
end
|
62
81
|
|
63
|
-
|
64
|
-
|
82
|
+
# Fetch an entry at given offset and with provided size
|
83
|
+
# @macro no_change_pos
|
84
|
+
# @param [Integer] offset File offset in bytes
|
85
|
+
# @param [Integer] size Entry size in bytes
|
86
|
+
# @macro read_result_return
|
87
|
+
def pread(offset, size)
|
88
|
+
read_from_io(StringIO.new(@file.pread(size, offset)))
|
65
89
|
end
|
66
90
|
|
67
91
|
AppendResult = Struct.new(:value_pos, :value_size)
|
68
|
-
# Append
|
92
|
+
# Append an entry at the end of the file
|
93
|
+
# @macro no_change_pos
|
69
94
|
# @param [DataEntry] entry Entry to write to the file
|
70
|
-
# @return [AppendResult] struct containing position and size of the
|
95
|
+
# @return [AppendResult] struct containing position and size of the entry
|
71
96
|
def append(entry)
|
72
97
|
current_pos = @write_pos
|
73
98
|
|
74
99
|
key_size = entry.key.bytesize
|
75
100
|
value_size = entry.value.bytesize
|
76
|
-
|
101
|
+
timestamp_with_deleted = entry.expire_timestamp
|
102
|
+
timestamp_with_deleted |= DELETED_MASK if entry.deleted?
|
77
103
|
crc = Zlib.crc32([
|
78
|
-
|
104
|
+
timestamp_with_deleted,
|
79
105
|
key_size,
|
80
106
|
value_size
|
81
107
|
].pack(HEADER_WITHOUT_CRC_FORMAT) + entry.key + entry.value)
|
82
108
|
@write_pos += @file.write(
|
83
|
-
[crc,
|
109
|
+
[crc, timestamp_with_deleted, key_size, value_size].pack(HEADER_FORMAT),
|
84
110
|
entry.key,
|
85
111
|
entry.value
|
86
112
|
)
|
87
113
|
@file.flush
|
88
114
|
AppendResult.new(current_pos, @write_pos - current_pos)
|
89
115
|
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def read_from_io(io)
|
120
|
+
header = io.read(HEADER_SIZE)
|
121
|
+
|
122
|
+
return nil unless header
|
123
|
+
|
124
|
+
crc, expire_timestamp_with_deleted, key_size, value_size = header.unpack(HEADER_FORMAT)
|
125
|
+
key = io.read(key_size)
|
126
|
+
value = io.read(value_size)
|
127
|
+
expire_timestamp = (expire_timestamp_with_deleted & EXPIRE_MASK)
|
128
|
+
deleted = (expire_timestamp_with_deleted & DELETED_MASK) != 0
|
129
|
+
|
130
|
+
raise ChecksumError, "Checksums do not match" if crc != Zlib.crc32(header[4..] + key + value)
|
131
|
+
DataEntry.new(expire_timestamp, key, value, deleted)
|
132
|
+
end
|
90
133
|
end
|
91
134
|
end
|
data/lib/rubcask/directory.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "concurrent"
|
3
|
+
require "concurrent/atomic/atomic_fixnum"
|
4
|
+
require "concurrent/atomic/reentrant_read_write_lock"
|
4
5
|
|
6
|
+
require "fiber" # rubocop:disable Lint/RedundantRequireStatement It is needed for `Fiber.current`(used by concurrent) in some rubies
|
5
7
|
require "forwardable"
|
6
8
|
require "logger"
|
7
9
|
require "monitor"
|
@@ -59,6 +61,7 @@ module Rubcask
|
|
59
61
|
def initialize(dir, config: Config.new)
|
60
62
|
@dir = dir
|
61
63
|
@config = check_config(config)
|
64
|
+
@active = nil
|
62
65
|
|
63
66
|
max_id = 0
|
64
67
|
files = dir_data_files
|
@@ -105,7 +108,6 @@ module Rubcask
|
|
105
108
|
# @param [String] value
|
106
109
|
# @param [Integer] ttl Time to live
|
107
110
|
# @return [String] the value provided by the user
|
108
|
-
# @return [String] the value provided by the user
|
109
111
|
# @raise [ArgumentError] if ttl is negative
|
110
112
|
def set_with_ttl(key, value, ttl)
|
111
113
|
raise ArgumentError, "Negative ttl" if ttl.negative?
|
@@ -120,8 +122,6 @@ module Rubcask
|
|
120
122
|
# @return [nil] If no value associated with the key
|
121
123
|
def [](key)
|
122
124
|
key = normalize_key(key)
|
123
|
-
entry = nil
|
124
|
-
data_file = nil
|
125
125
|
@lock.with_read_lock do
|
126
126
|
entry = @keydir[key]
|
127
127
|
return nil unless entry
|
@@ -131,11 +131,11 @@ module Rubcask
|
|
131
131
|
end
|
132
132
|
|
133
133
|
data_file = @files[entry.file_id]
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
134
|
+
|
135
|
+
# We are using pread so there's no need to synchronize the read
|
136
|
+
entry = data_file.pread(entry.value_pos, entry.value_size)
|
137
|
+
return nil if entry.deleted?
|
138
|
+
return entry.value
|
139
139
|
end
|
140
140
|
end
|
141
141
|
|
@@ -190,21 +190,16 @@ module Rubcask
|
|
190
190
|
# @yieldparam [String] value
|
191
191
|
# @macro lock_block_for_iteration
|
192
192
|
# @macro key_any_order
|
193
|
-
# @return Enumerator if block
|
193
|
+
# @return [Enumerator<Array(String, String)>] if no block given
|
194
194
|
def each
|
195
195
|
return to_enum(__method__) unless block_given?
|
196
196
|
|
197
197
|
@lock.with_read_lock do
|
198
198
|
@keydir.each do |key, entry|
|
199
199
|
file = @files[entry.file_id]
|
200
|
-
file.
|
201
|
-
|
202
|
-
|
203
|
-
next if Tombstone.is_tombstone?(value)
|
204
|
-
yield [key, value]
|
205
|
-
ensure
|
206
|
-
file.mon_exit
|
207
|
-
end
|
200
|
+
entry = file[entry.value_pos, entry.value_size]
|
201
|
+
next if entry.deleted?
|
202
|
+
yield [key, entry.value]
|
208
203
|
end
|
209
204
|
end
|
210
205
|
end
|
@@ -213,7 +208,7 @@ module Rubcask
|
|
213
208
|
# @macro deleted_keys
|
214
209
|
# @macro key_any_order
|
215
210
|
# @macro lock_block_for_iteration
|
216
|
-
# @return Enumerator if block
|
211
|
+
# @return [Enumerator<String>] if no block given
|
217
212
|
def each_key(&block)
|
218
213
|
return to_enum(__method__) unless block
|
219
214
|
|
@@ -274,10 +269,11 @@ module Rubcask
|
|
274
269
|
attr_reader :config, :active, :worker, :logger
|
275
270
|
|
276
271
|
def put(key, value, expire_timestamp)
|
272
|
+
expire_timestamp = expire_timestamp.clamp(NO_EXPIRE_TIMESTAMP, DataFile::MAX_EXPIRE_VALUE)
|
277
273
|
key = normalize_key(key)
|
278
274
|
@lock.with_write_lock do
|
279
275
|
@keydir[key] = active.append(
|
280
|
-
DataEntry.new(expire_timestamp, key, value)
|
276
|
+
DataEntry.new(expire_timestamp, key, value, false)
|
281
277
|
)
|
282
278
|
if active.write_pos >= @config.max_file_size
|
283
279
|
create_new_file!
|
@@ -289,7 +285,7 @@ module Rubcask
|
|
289
285
|
# @note This method assumes write lock and normalized key
|
290
286
|
def do_delete(key, prev_file_id)
|
291
287
|
active.append(
|
292
|
-
DataEntry.new(NO_EXPIRE_TIMESTAMP, key, Tombstone.new_tombstone(active.id, prev_file_id))
|
288
|
+
DataEntry.new(NO_EXPIRE_TIMESTAMP, key, Tombstone.new_tombstone(active.id, prev_file_id), true)
|
293
289
|
)
|
294
290
|
@keydir.delete(key)
|
295
291
|
if active.write_pos >= @config.max_file_size
|
@@ -332,9 +328,10 @@ module Rubcask
|
|
332
328
|
end
|
333
329
|
end
|
334
330
|
|
331
|
+
merging_paths.each { |_id, path| FileUtils.chmod("+x", path) }
|
332
|
+
|
335
333
|
@lock.with_write_lock do
|
336
334
|
close_not_active
|
337
|
-
merging_paths.each { |_id, path| FileUtils.chmod("+x", path) }
|
338
335
|
reload!
|
339
336
|
end
|
340
337
|
clear_files
|
@@ -348,8 +345,7 @@ module Rubcask
|
|
348
345
|
start_pos = pos
|
349
346
|
pos = file.pos
|
350
347
|
|
351
|
-
next if entry.expired?
|
352
|
-
next if Tombstone.is_tombstone?(entry.value)
|
348
|
+
next if entry.expired? || entry.deleted?
|
353
349
|
|
354
350
|
@lock.acquire_read_lock
|
355
351
|
begin
|
data/lib/rubcask/hinted_file.rb
CHANGED
@@ -13,7 +13,7 @@ module Rubcask
|
|
13
13
|
ID_REGEX = /(\d+)\.data$/
|
14
14
|
HINT_EXTENSION_REGEX = /\.data$/
|
15
15
|
|
16
|
-
def_delegators :@data_file, :seek, :[], :close, :flush, :each, :pos, :write_pos
|
16
|
+
def_delegators :@data_file, :seek, :[], :pread, :close, :flush, :each, :pos, :write_pos
|
17
17
|
|
18
18
|
# @return [String] path of the file
|
19
19
|
attr_reader :path
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require_relative "directory"
|
3
|
+
|
4
|
+
module Rubcask
|
5
|
+
class MarshaledDirectory
|
6
|
+
def initialize(directory)
|
7
|
+
@directory = directory
|
8
|
+
end
|
9
|
+
|
10
|
+
# Set value associated with given key.
|
11
|
+
# @param [Object] key
|
12
|
+
# @param [Object] value
|
13
|
+
# @return [Object] the value provided by the user
|
14
|
+
def []=(key, value)
|
15
|
+
@directory[Marshal.dump(key)] = Marshal.dump(value)
|
16
|
+
value # rubocop:disable Lint/Void
|
17
|
+
end
|
18
|
+
|
19
|
+
# Set value associated with given key with given ttl
|
20
|
+
# @param [Object] key
|
21
|
+
# @param [Object] value
|
22
|
+
# @param [Integer] ttl Time to live
|
23
|
+
# @return [Object] the value provided by the user
|
24
|
+
# @raise [ArgumentError] if ttl is negative
|
25
|
+
def set_with_ttl(key, value, ttl)
|
26
|
+
@directory.set_with_ttl(
|
27
|
+
Marshal.dump(key),
|
28
|
+
Marshal.dump(value),
|
29
|
+
ttl
|
30
|
+
)
|
31
|
+
value
|
32
|
+
end
|
33
|
+
|
34
|
+
# Gets value associated with the key
|
35
|
+
# @param [Object] key
|
36
|
+
# @return [Object] value associatiod with the key
|
37
|
+
# @return [nil] If no value associated with the key
|
38
|
+
def [](key)
|
39
|
+
value = @directory[Marshal.dump(key)]
|
40
|
+
if value.nil?
|
41
|
+
value
|
42
|
+
else
|
43
|
+
Marshal.load(value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Remove entry associated with the key.
|
48
|
+
# @param [Object] key
|
49
|
+
# @return false if the existing value does not exist
|
50
|
+
# @return true if the delete was succesfull
|
51
|
+
def delete(key)
|
52
|
+
@directory.delete(Marshal.dump(key))
|
53
|
+
end
|
54
|
+
|
55
|
+
extend Forwardable
|
56
|
+
def_delegators :@directory, *(Directory.public_instance_methods(false) - MarshaledDirectory.public_instance_methods(false))
|
57
|
+
end
|
58
|
+
end
|
data/lib/rubcask/protocol.rb
CHANGED
@@ -59,7 +59,7 @@ module Rubcask
|
|
59
59
|
# @return [String] Encoded "$1" messege.
|
60
60
|
def generate_cached_message(method)
|
61
61
|
value = encode_message(const_get(method.upcase)).freeze
|
62
|
-
define_method "#{method}_message" do
|
62
|
+
define_method :"#{method}_message" do
|
63
63
|
value
|
64
64
|
end
|
65
65
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "stringio"
|
4
|
+
|
3
5
|
require_relative "../bytes"
|
4
6
|
require_relative "../protocol"
|
5
7
|
require_relative "config"
|
@@ -16,25 +18,29 @@ module Rubcask
|
|
16
18
|
|
17
19
|
private
|
18
20
|
|
19
|
-
def
|
20
|
-
|
21
|
-
length = conn.gets(Protocol::SEPARATOR)
|
21
|
+
def read_command_args(conn)
|
22
|
+
length = conn.gets(SEPARATOR, chomp: true)
|
22
23
|
|
23
|
-
|
24
|
-
|
24
|
+
return nil unless length
|
25
|
+
length = length.to_i
|
25
26
|
|
26
|
-
|
27
|
+
command_body = read_command_body(conn, length)
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
return nil unless command_body
|
30
|
+
return nil if command_body.bytesize != length
|
30
31
|
|
31
|
-
|
32
|
+
reader = StringIO.new(command_body)
|
32
33
|
|
33
|
-
|
34
|
-
command&.chomp!(SEPARATOR)
|
34
|
+
command = reader.gets(SEPARATOR, chomp: true)
|
35
35
|
|
36
|
-
|
36
|
+
args = parse_args(reader)
|
37
|
+
[command, args]
|
38
|
+
end
|
37
39
|
|
40
|
+
def client_loop(conn)
|
41
|
+
while running?
|
42
|
+
command_args = read_command_args(conn)
|
43
|
+
break unless command_args
|
38
44
|
conn.write(execute_command!(command, args))
|
39
45
|
end
|
40
46
|
end
|
@@ -80,23 +86,13 @@ module Rubcask
|
|
80
86
|
end
|
81
87
|
|
82
88
|
def parse_word(reader)
|
83
|
-
length = reader.gets(SEPARATOR).to_i
|
89
|
+
length = reader.gets(SEPARATOR, chomp: true).to_i
|
84
90
|
return nil if length.zero?
|
85
91
|
reader.read(length)
|
86
92
|
end
|
87
93
|
|
88
94
|
def read_command_body(conn, length)
|
89
|
-
|
90
|
-
size = 0
|
91
|
-
|
92
|
-
while size < length
|
93
|
-
val = conn.read([MAX_READ_SIZE, length - size].min)
|
94
|
-
return nil if val.nil?
|
95
|
-
size += val.bytesize
|
96
|
-
command_body << val
|
97
|
-
end
|
98
|
-
|
99
|
-
command_body
|
95
|
+
conn.read(length)
|
100
96
|
end
|
101
97
|
|
102
98
|
def parse_args(reader)
|
data/lib/rubcask/server/async.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "async/io"
|
4
|
-
require "async/
|
4
|
+
require "async/queue"
|
5
5
|
require "async/io/stream"
|
6
6
|
|
7
7
|
require_relative "abstract_server"
|
@@ -18,6 +18,7 @@ module Rubcask
|
|
18
18
|
@port = config.port
|
19
19
|
@logger = Logger.new($stdout)
|
20
20
|
@endpoint = ::Async::IO::Endpoint.tcp(@hostname, @port)
|
21
|
+
@running = false
|
21
22
|
end
|
22
23
|
|
23
24
|
# Shuts down the server
|
@@ -25,6 +26,7 @@ module Rubcask
|
|
25
26
|
def shutdown
|
26
27
|
return unless @task
|
27
28
|
Sync do
|
29
|
+
@running = false
|
28
30
|
@shutdown_condition.signal
|
29
31
|
@task.wait
|
30
32
|
end
|
@@ -33,7 +35,7 @@ module Rubcask
|
|
33
35
|
# Starts the server
|
34
36
|
# @param [::Async::Condition, nil] on_start_condition The condition will be signalled after a successful bind
|
35
37
|
def start(on_start_condition = nil)
|
36
|
-
|
38
|
+
Sync do
|
37
39
|
@shutdown_condition = ::Async::Condition.new
|
38
40
|
|
39
41
|
_, @task = @endpoint.bind do |server, task|
|
@@ -47,10 +49,13 @@ module Rubcask
|
|
47
49
|
|
48
50
|
server.listen(Socket::SOMAXCONN)
|
49
51
|
on_start_condition&.signal
|
52
|
+
@running = true
|
50
53
|
|
51
54
|
server.accept_each do |conn|
|
52
55
|
conn.binmode
|
53
56
|
client_loop(::Async::IO::Stream.new(conn))
|
57
|
+
ensure
|
58
|
+
::Async::Task.current.stop
|
54
59
|
end
|
55
60
|
end
|
56
61
|
end
|
@@ -58,6 +63,27 @@ module Rubcask
|
|
58
63
|
|
59
64
|
private
|
60
65
|
|
66
|
+
def running?
|
67
|
+
@running
|
68
|
+
end
|
69
|
+
|
70
|
+
def client_loop(conn)
|
71
|
+
q = ::Async::Queue.new
|
72
|
+
Async do
|
73
|
+
@shutdown_condition.wait
|
74
|
+
q << nil
|
75
|
+
end
|
76
|
+
while running?
|
77
|
+
Async { q << read_command_args(conn) }
|
78
|
+
command_args = q.dequeue
|
79
|
+
return unless command_args
|
80
|
+
|
81
|
+
conn.write(execute_command!(*command_args))
|
82
|
+
end
|
83
|
+
rescue => error
|
84
|
+
Console::Event::Failure.for(error).emit(server, "Error while handling connection")
|
85
|
+
end
|
86
|
+
|
61
87
|
def define_close_routine(server, task)
|
62
88
|
task.async do |subtask|
|
63
89
|
@shutdown_condition.wait
|
@@ -66,12 +92,18 @@ module Rubcask
|
|
66
92
|
|
67
93
|
server.close
|
68
94
|
|
95
|
+
subtask.with_timeout(30) do
|
96
|
+
task.children.each { |t| t.wait unless t == subtask }
|
97
|
+
rescue ::Async::TimeoutError
|
98
|
+
Console.logger.warn("Could not terminate child connections...")
|
99
|
+
end
|
100
|
+
|
69
101
|
task.stop
|
70
102
|
end
|
71
103
|
end
|
72
104
|
|
73
105
|
def read_command_body(conn, length)
|
74
|
-
conn.read(length)
|
106
|
+
conn.read(length)
|
75
107
|
end
|
76
108
|
end
|
77
109
|
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "concurrent"
|
4
|
-
|
5
|
-
require_relative "
|
3
|
+
require "concurrent/timer_task"
|
4
|
+
|
5
|
+
require_relative "config"
|
6
|
+
require_relative "runner/config"
|
6
7
|
require_relative "../config"
|
7
8
|
|
8
9
|
module Rubcask
|
@@ -3,7 +3,6 @@
|
|
3
3
|
require "logger"
|
4
4
|
require "socket"
|
5
5
|
require "io/wait"
|
6
|
-
require "stringio"
|
7
6
|
|
8
7
|
require_relative "../bytes"
|
9
8
|
require_relative "../protocol"
|
@@ -66,6 +65,10 @@ module Rubcask
|
|
66
65
|
@status = :shutdown
|
67
66
|
cleanup_listeners
|
68
67
|
@threads.list.each(&:kill)
|
68
|
+
@threads.list.each do |t|
|
69
|
+
t.join
|
70
|
+
rescue
|
71
|
+
end
|
69
72
|
@status = :stopped
|
70
73
|
@connected = false
|
71
74
|
logger.info "Closed server"
|
@@ -78,8 +81,12 @@ module Rubcask
|
|
78
81
|
if @status == :running
|
79
82
|
@status = :shutdown
|
80
83
|
end
|
81
|
-
|
82
|
-
|
84
|
+
begin
|
85
|
+
@shutdown_pipe[1].write_nonblock("\0")
|
86
|
+
@shutdown_pipe[1].close
|
87
|
+
rescue
|
88
|
+
# We might have race with cleanup shutdown pipe
|
89
|
+
end
|
83
90
|
end
|
84
91
|
|
85
92
|
# Prepares an IO pipe that is used in shutdown process
|
@@ -94,6 +101,10 @@ module Rubcask
|
|
94
101
|
|
95
102
|
attr_reader :logger
|
96
103
|
|
104
|
+
def running?
|
105
|
+
@status == :running
|
106
|
+
end
|
107
|
+
|
97
108
|
def cleanup_listeners
|
98
109
|
@listeners.each do |listener|
|
99
110
|
listener.shutdown
|
@@ -147,6 +158,16 @@ module Rubcask
|
|
147
158
|
end
|
148
159
|
end
|
149
160
|
|
161
|
+
def client_loop(conn)
|
162
|
+
while running?
|
163
|
+
command_args = read_command_args(conn)
|
164
|
+
return nil unless command_args
|
165
|
+
Thread.handle_interrupt(Exception => :never) do
|
166
|
+
conn.write(execute_command!(*command_args))
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
150
171
|
def client_block(conn)
|
151
172
|
conn.binmode
|
152
173
|
with_interrupt_handle(conn) do |io|
|
data/lib/rubcask/tombstone.rb
CHANGED
@@ -8,25 +8,16 @@ module Rubcask
|
|
8
8
|
module Tombstone
|
9
9
|
extend self
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
FILE_ID_FORMAT = "N"
|
14
|
-
FULL_BYTE_SIZE = PREFIX_SIZE + 4
|
15
|
-
|
16
|
-
# @param [String] value value to check
|
17
|
-
# @return true if value is a tombstone
|
18
|
-
# @return false otherwise
|
19
|
-
def is_tombstone?(value)
|
20
|
-
value.bytesize <= FULL_BYTE_SIZE && value.start_with?(PREFIX)
|
21
|
-
end
|
11
|
+
FILE_ID_FORMAT = "Q>"
|
12
|
+
BYTE_SIZE = 8
|
22
13
|
|
23
14
|
# Creates a new tombstone value
|
24
15
|
# @param [Integer] current_file_id Id of the active file
|
25
16
|
# @param [Integer] prev_file_id Id of the file where the record is currently located
|
26
17
|
# @return [String]
|
27
18
|
def new_tombstone(current_file_id, prev_file_id)
|
28
|
-
return
|
29
|
-
|
19
|
+
return "" if prev_file_id == current_file_id
|
20
|
+
[prev_file_id].pack(FILE_ID_FORMAT)
|
30
21
|
end
|
31
22
|
|
32
23
|
# Gets file id from tombstone value
|
@@ -34,7 +25,7 @@ module Rubcask
|
|
34
25
|
# @return [Integer, nil]
|
35
26
|
def tombstone_file_id(value)
|
36
27
|
return nil if value.bytesize < FULL_BYTE_SIZE
|
37
|
-
value.
|
28
|
+
value.unpack1(FILE_ID_FORMAT)
|
38
29
|
end
|
39
30
|
end
|
40
31
|
end
|
data/lib/rubcask/version.rb
CHANGED
data/lib/rubcask.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubcask
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marcin Henryk Bartkowiak
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-01-06 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: concurrent-ruby
|
@@ -24,6 +23,20 @@ dependencies:
|
|
24
23
|
- - "~>"
|
25
24
|
- !ruby/object:Gem::Version
|
26
25
|
version: '1.1'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: stringio
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.1'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.1'
|
27
40
|
description: Bitcask-like Key/Value storege library with a TCP server included
|
28
41
|
email:
|
29
42
|
- mhbartkowiak@gmail.com
|
@@ -59,6 +72,7 @@ files:
|
|
59
72
|
- lib/rubcask/hint_file.rb
|
60
73
|
- lib/rubcask/hinted_file.rb
|
61
74
|
- lib/rubcask/keydir_entry.rb
|
75
|
+
- lib/rubcask/marshaled_directory.rb
|
62
76
|
- lib/rubcask/merge_directory.rb
|
63
77
|
- lib/rubcask/protocol.rb
|
64
78
|
- lib/rubcask/server/abstract_server.rb
|
@@ -80,7 +94,6 @@ homepage: https://github.com/mhib/rubcask
|
|
80
94
|
licenses:
|
81
95
|
- MIT
|
82
96
|
metadata: {}
|
83
|
-
post_install_message:
|
84
97
|
rdoc_options: []
|
85
98
|
require_paths:
|
86
99
|
- lib
|
@@ -95,8 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
108
|
- !ruby/object:Gem::Version
|
96
109
|
version: '0'
|
97
110
|
requirements: []
|
98
|
-
rubygems_version: 3.
|
99
|
-
signing_key:
|
111
|
+
rubygems_version: 3.6.2
|
100
112
|
specification_version: 4
|
101
113
|
summary: Key/Value storage library
|
102
114
|
test_files: []
|