rubcask 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.standard.yml +1 -1
- data/Gemfile +2 -2
- data/Gemfile.lock +40 -24
- data/lib/rubcask/data_entry.rb +1 -1
- data/lib/rubcask/data_file.rb +16 -7
- data/lib/rubcask/directory.rb +13 -14
- data/lib/rubcask/marshaled_directory.rb +58 -0
- data/lib/rubcask/protocol.rb +1 -1
- data/lib/rubcask/server/abstract_server.rb +18 -24
- data/lib/rubcask/server/async.rb +35 -3
- data/lib/rubcask/server/client.rb +1 -1
- data/lib/rubcask/server/runner.rb +2 -2
- data/lib/rubcask/server/threaded.rb +24 -2
- 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
|
@@ -3,6 +3,7 @@ PATH
|
|
|
3
3
|
specs:
|
|
4
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/
|
|
@@ -11,50 +12,65 @@ GEM
|
|
|
11
12
|
benchmark-ips (2.10.0)
|
|
12
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
|
|
@@ -92,14 +98,15 @@ module Rubcask
|
|
|
92
98
|
|
|
93
99
|
key_size = entry.key.bytesize
|
|
94
100
|
value_size = entry.value.bytesize
|
|
95
|
-
|
|
101
|
+
timestamp_with_deleted = entry.expire_timestamp
|
|
102
|
+
timestamp_with_deleted |= DELETED_MASK if entry.deleted?
|
|
96
103
|
crc = Zlib.crc32([
|
|
97
|
-
|
|
104
|
+
timestamp_with_deleted,
|
|
98
105
|
key_size,
|
|
99
106
|
value_size
|
|
100
107
|
].pack(HEADER_WITHOUT_CRC_FORMAT) + entry.key + entry.value)
|
|
101
108
|
@write_pos += @file.write(
|
|
102
|
-
[crc,
|
|
109
|
+
[crc, timestamp_with_deleted, key_size, value_size].pack(HEADER_FORMAT),
|
|
103
110
|
entry.key,
|
|
104
111
|
entry.value
|
|
105
112
|
)
|
|
@@ -110,16 +117,18 @@ module Rubcask
|
|
|
110
117
|
private
|
|
111
118
|
|
|
112
119
|
def read_from_io(io)
|
|
113
|
-
header = io.read(
|
|
120
|
+
header = io.read(HEADER_SIZE)
|
|
114
121
|
|
|
115
122
|
return nil unless header
|
|
116
123
|
|
|
117
|
-
crc,
|
|
124
|
+
crc, expire_timestamp_with_deleted, key_size, value_size = header.unpack(HEADER_FORMAT)
|
|
118
125
|
key = io.read(key_size)
|
|
119
126
|
value = io.read(value_size)
|
|
127
|
+
expire_timestamp = (expire_timestamp_with_deleted & EXPIRE_MASK)
|
|
128
|
+
deleted = (expire_timestamp_with_deleted & DELETED_MASK) != 0
|
|
120
129
|
|
|
121
130
|
raise ChecksumError, "Checksums do not match" if crc != Zlib.crc32(header[4..] + key + value)
|
|
122
|
-
DataEntry.new(expire_timestamp, key, value)
|
|
131
|
+
DataEntry.new(expire_timestamp, key, value, deleted)
|
|
123
132
|
end
|
|
124
133
|
end
|
|
125
134
|
end
|
data/lib/rubcask/directory.rb
CHANGED
|
@@ -61,6 +61,7 @@ module Rubcask
|
|
|
61
61
|
def initialize(dir, config: Config.new)
|
|
62
62
|
@dir = dir
|
|
63
63
|
@config = check_config(config)
|
|
64
|
+
@active = nil
|
|
64
65
|
|
|
65
66
|
max_id = 0
|
|
66
67
|
files = dir_data_files
|
|
@@ -107,7 +108,6 @@ module Rubcask
|
|
|
107
108
|
# @param [String] value
|
|
108
109
|
# @param [Integer] ttl Time to live
|
|
109
110
|
# @return [String] the value provided by the user
|
|
110
|
-
# @return [String] the value provided by the user
|
|
111
111
|
# @raise [ArgumentError] if ttl is negative
|
|
112
112
|
def set_with_ttl(key, value, ttl)
|
|
113
113
|
raise ArgumentError, "Negative ttl" if ttl.negative?
|
|
@@ -122,8 +122,6 @@ module Rubcask
|
|
|
122
122
|
# @return [nil] If no value associated with the key
|
|
123
123
|
def [](key)
|
|
124
124
|
key = normalize_key(key)
|
|
125
|
-
entry = nil
|
|
126
|
-
data_file = nil
|
|
127
125
|
@lock.with_read_lock do
|
|
128
126
|
entry = @keydir[key]
|
|
129
127
|
return nil unless entry
|
|
@@ -135,9 +133,9 @@ module Rubcask
|
|
|
135
133
|
data_file = @files[entry.file_id]
|
|
136
134
|
|
|
137
135
|
# We are using pread so there's no need to synchronize the read
|
|
138
|
-
|
|
139
|
-
return nil if
|
|
140
|
-
return value
|
|
136
|
+
entry = data_file.pread(entry.value_pos, entry.value_size)
|
|
137
|
+
return nil if entry.deleted?
|
|
138
|
+
return entry.value
|
|
141
139
|
end
|
|
142
140
|
end
|
|
143
141
|
|
|
@@ -199,9 +197,9 @@ module Rubcask
|
|
|
199
197
|
@lock.with_read_lock do
|
|
200
198
|
@keydir.each do |key, entry|
|
|
201
199
|
file = @files[entry.file_id]
|
|
202
|
-
|
|
203
|
-
next if
|
|
204
|
-
yield [key, value]
|
|
200
|
+
entry = file[entry.value_pos, entry.value_size]
|
|
201
|
+
next if entry.deleted?
|
|
202
|
+
yield [key, entry.value]
|
|
205
203
|
end
|
|
206
204
|
end
|
|
207
205
|
end
|
|
@@ -271,10 +269,11 @@ module Rubcask
|
|
|
271
269
|
attr_reader :config, :active, :worker, :logger
|
|
272
270
|
|
|
273
271
|
def put(key, value, expire_timestamp)
|
|
272
|
+
expire_timestamp = expire_timestamp.clamp(NO_EXPIRE_TIMESTAMP, DataFile::MAX_EXPIRE_VALUE)
|
|
274
273
|
key = normalize_key(key)
|
|
275
274
|
@lock.with_write_lock do
|
|
276
275
|
@keydir[key] = active.append(
|
|
277
|
-
DataEntry.new(expire_timestamp, key, value)
|
|
276
|
+
DataEntry.new(expire_timestamp, key, value, false)
|
|
278
277
|
)
|
|
279
278
|
if active.write_pos >= @config.max_file_size
|
|
280
279
|
create_new_file!
|
|
@@ -286,7 +285,7 @@ module Rubcask
|
|
|
286
285
|
# @note This method assumes write lock and normalized key
|
|
287
286
|
def do_delete(key, prev_file_id)
|
|
288
287
|
active.append(
|
|
289
|
-
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)
|
|
290
289
|
)
|
|
291
290
|
@keydir.delete(key)
|
|
292
291
|
if active.write_pos >= @config.max_file_size
|
|
@@ -329,9 +328,10 @@ module Rubcask
|
|
|
329
328
|
end
|
|
330
329
|
end
|
|
331
330
|
|
|
331
|
+
merging_paths.each { |_id, path| FileUtils.chmod("+x", path) }
|
|
332
|
+
|
|
332
333
|
@lock.with_write_lock do
|
|
333
334
|
close_not_active
|
|
334
|
-
merging_paths.each { |_id, path| FileUtils.chmod("+x", path) }
|
|
335
335
|
reload!
|
|
336
336
|
end
|
|
337
337
|
clear_files
|
|
@@ -345,8 +345,7 @@ module Rubcask
|
|
|
345
345
|
start_pos = pos
|
|
346
346
|
pos = file.pos
|
|
347
347
|
|
|
348
|
-
next if entry.expired?
|
|
349
|
-
next if Tombstone.is_tombstone?(entry.value)
|
|
348
|
+
next if entry.expired? || entry.deleted?
|
|
350
349
|
|
|
351
350
|
@lock.acquire_read_lock
|
|
352
351
|
begin
|
|
@@ -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
|
|
@@ -18,25 +18,29 @@ module Rubcask
|
|
|
18
18
|
|
|
19
19
|
private
|
|
20
20
|
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
length = conn.gets(Protocol::SEPARATOR)
|
|
21
|
+
def read_command_args(conn)
|
|
22
|
+
length = conn.gets(SEPARATOR, chomp: true)
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
return nil unless length
|
|
25
|
+
length = length.to_i
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
command_body = read_command_body(conn, length)
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
return nil unless command_body
|
|
30
|
+
return nil if command_body.bytesize != length
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
reader = StringIO.new(command_body)
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
command&.chomp!(SEPARATOR)
|
|
34
|
+
command = reader.gets(SEPARATOR, chomp: true)
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
args = parse_args(reader)
|
|
37
|
+
[command, args]
|
|
38
|
+
end
|
|
39
39
|
|
|
40
|
+
def client_loop(conn)
|
|
41
|
+
while running?
|
|
42
|
+
command_args = read_command_args(conn)
|
|
43
|
+
break unless command_args
|
|
40
44
|
conn.write(execute_command!(command, args))
|
|
41
45
|
end
|
|
42
46
|
end
|
|
@@ -82,23 +86,13 @@ module Rubcask
|
|
|
82
86
|
end
|
|
83
87
|
|
|
84
88
|
def parse_word(reader)
|
|
85
|
-
length = reader.gets(SEPARATOR).to_i
|
|
89
|
+
length = reader.gets(SEPARATOR, chomp: true).to_i
|
|
86
90
|
return nil if length.zero?
|
|
87
91
|
reader.read(length)
|
|
88
92
|
end
|
|
89
93
|
|
|
90
94
|
def read_command_body(conn, length)
|
|
91
|
-
|
|
92
|
-
size = 0
|
|
93
|
-
|
|
94
|
-
while size < length
|
|
95
|
-
val = conn.read([MAX_READ_SIZE, length - size].min)
|
|
96
|
-
return nil if val.nil?
|
|
97
|
-
size += val.bytesize
|
|
98
|
-
command_body << val
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
command_body
|
|
95
|
+
conn.read(length)
|
|
102
96
|
end
|
|
103
97
|
|
|
104
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
|
|
@@ -65,6 +65,10 @@ module Rubcask
|
|
|
65
65
|
@status = :shutdown
|
|
66
66
|
cleanup_listeners
|
|
67
67
|
@threads.list.each(&:kill)
|
|
68
|
+
@threads.list.each do |t|
|
|
69
|
+
t.join
|
|
70
|
+
rescue
|
|
71
|
+
end
|
|
68
72
|
@status = :stopped
|
|
69
73
|
@connected = false
|
|
70
74
|
logger.info "Closed server"
|
|
@@ -77,8 +81,12 @@ module Rubcask
|
|
|
77
81
|
if @status == :running
|
|
78
82
|
@status = :shutdown
|
|
79
83
|
end
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
82
90
|
end
|
|
83
91
|
|
|
84
92
|
# Prepares an IO pipe that is used in shutdown process
|
|
@@ -93,6 +101,10 @@ module Rubcask
|
|
|
93
101
|
|
|
94
102
|
attr_reader :logger
|
|
95
103
|
|
|
104
|
+
def running?
|
|
105
|
+
@status == :running
|
|
106
|
+
end
|
|
107
|
+
|
|
96
108
|
def cleanup_listeners
|
|
97
109
|
@listeners.each do |listener|
|
|
98
110
|
listener.shutdown
|
|
@@ -146,6 +158,16 @@ module Rubcask
|
|
|
146
158
|
end
|
|
147
159
|
end
|
|
148
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
|
+
|
|
149
171
|
def client_block(conn)
|
|
150
172
|
conn.binmode
|
|
151
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: []
|