rubcask 0.2.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 +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: []
|