rubcask 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.standard.yml +3 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +74 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +14 -0
- data/benchmark/benchmark_io.rb +49 -0
- data/benchmark/benchmark_server.rb +10 -0
- data/benchmark/benchmark_server_pipeline.rb +24 -0
- data/benchmark/benchmark_worker.rb +46 -0
- data/benchmark/op_times.rb +32 -0
- data/benchmark/profile.rb +15 -0
- data/benchmark/server_benchmark_helper.rb +138 -0
- data/example/server_runner.rb +15 -0
- data/lib/rubcask/bytes.rb +11 -0
- data/lib/rubcask/concurrency/fake_atomic_fixnum.rb +34 -0
- data/lib/rubcask/concurrency/fake_lock.rb +41 -0
- data/lib/rubcask/concurrency/fake_monitor_mixin.rb +21 -0
- data/lib/rubcask/config.rb +55 -0
- data/lib/rubcask/data_entry.rb +9 -0
- data/lib/rubcask/data_file.rb +91 -0
- data/lib/rubcask/directory.rb +437 -0
- data/lib/rubcask/expirable_entry.rb +9 -0
- data/lib/rubcask/hint_entry.rb +9 -0
- data/lib/rubcask/hint_file.rb +56 -0
- data/lib/rubcask/hinted_file.rb +148 -0
- data/lib/rubcask/keydir_entry.rb +9 -0
- data/lib/rubcask/merge_directory.rb +75 -0
- data/lib/rubcask/protocol.rb +74 -0
- data/lib/rubcask/server/abstract_server.rb +113 -0
- data/lib/rubcask/server/async.rb +78 -0
- data/lib/rubcask/server/client.rb +131 -0
- data/lib/rubcask/server/config.rb +31 -0
- data/lib/rubcask/server/pipeline.rb +49 -0
- data/lib/rubcask/server/runner/config.rb +43 -0
- data/lib/rubcask/server/runner.rb +107 -0
- data/lib/rubcask/server/threaded.rb +171 -0
- data/lib/rubcask/task/clean_directory.rb +19 -0
- data/lib/rubcask/tombstone.rb +40 -0
- data/lib/rubcask/version.rb +5 -0
- data/lib/rubcask/worker/direct_worker.rb +23 -0
- data/lib/rubcask/worker/factory.rb +42 -0
- data/lib/rubcask/worker/ractor_worker.rb +40 -0
- data/lib/rubcask/worker/thread_worker.rb +40 -0
- data/lib/rubcask.rb +19 -0
- metadata +102 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubcask
|
4
|
+
module Server
|
5
|
+
class Runner
|
6
|
+
# @!attribute merge_interval
|
7
|
+
# How frequent in seconds should merge operation by run.
|
8
|
+
#
|
9
|
+
# Default: 3600
|
10
|
+
# @return [Integer, null]
|
11
|
+
# @!attribute server_type
|
12
|
+
# Which type of server should be run.
|
13
|
+
#
|
14
|
+
# Use threaded if you are not on MRI. If you are on mri and can install `async-io` use :async.
|
15
|
+
#
|
16
|
+
# Default: :threaded
|
17
|
+
# @return [:threaded, :async]
|
18
|
+
# @!attribute directory_path
|
19
|
+
# Path of the directory in which the data is stored.
|
20
|
+
#
|
21
|
+
# Default: no default value, user has to set it manually.
|
22
|
+
# @return [String]
|
23
|
+
# Server runner config
|
24
|
+
Config = Struct.new(:merge_interval, :server_type, :directory_path) do
|
25
|
+
# Overide fields with the block
|
26
|
+
# @yieldparam [self] config
|
27
|
+
def initialize
|
28
|
+
self.server_type = :threaded
|
29
|
+
self.merge_interval = 3_600
|
30
|
+
self.directory_path = nil
|
31
|
+
|
32
|
+
yield(self) if block_given?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Calls new and freezes the config
|
36
|
+
# @see .initialize
|
37
|
+
def self.configure(&block)
|
38
|
+
new(&block).freeze
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent"
|
4
|
+
require_relative "./config"
|
5
|
+
require_relative "./runner/config"
|
6
|
+
require_relative "../config"
|
7
|
+
|
8
|
+
module Rubcask
|
9
|
+
module Server
|
10
|
+
# ServerRunner runs a server alongside merge worker
|
11
|
+
# It supports graceful shutdown with Ctrl-c
|
12
|
+
class Runner
|
13
|
+
def initialize(
|
14
|
+
server_config: Rubcask::Server::Config.new,
|
15
|
+
dir_config: Rubcask::Config::DEFAULT_SERVER_CONFIG,
|
16
|
+
runner_config: Rubcask::Server::Runner::Config.new
|
17
|
+
)
|
18
|
+
@dir = Rubcask::Directory.new(
|
19
|
+
runner_config.directory_path,
|
20
|
+
config: dir_config
|
21
|
+
)
|
22
|
+
@server = new_server(runner_config.server_type, server_config)
|
23
|
+
@merge_worker = if runner_config.merge_interval && runner_config.merge_interval > 0
|
24
|
+
Concurrent::TimerTask.new(
|
25
|
+
execution_interval: runner_config.merge_interval
|
26
|
+
) do
|
27
|
+
merge_dir
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Starts the runner.
|
33
|
+
# @note It blocks the current thread
|
34
|
+
def start
|
35
|
+
install_trap!
|
36
|
+
@merge_worker.execute
|
37
|
+
@server.start
|
38
|
+
end
|
39
|
+
|
40
|
+
# Stops the server
|
41
|
+
def close
|
42
|
+
close_server
|
43
|
+
mutex_close
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def close_server
|
49
|
+
puts "Shutting down server!"
|
50
|
+
begin
|
51
|
+
@server.shutdown
|
52
|
+
rescue
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def mutex_close
|
57
|
+
if @merge_worker
|
58
|
+
puts "Stoping merge worker"
|
59
|
+
@merge_worker.shutdown
|
60
|
+
if @merge_worker.wait_for_termination(60)
|
61
|
+
puts "Closed merge worker"
|
62
|
+
else
|
63
|
+
puts "Failed to close worker"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
puts "Closing Dir!"
|
68
|
+
begin
|
69
|
+
@dir.close
|
70
|
+
rescue
|
71
|
+
end
|
72
|
+
puts "Closed dir"
|
73
|
+
end
|
74
|
+
|
75
|
+
def install_trap!
|
76
|
+
Signal.trap("INT") do
|
77
|
+
puts ""
|
78
|
+
# Close server in the same thread
|
79
|
+
close_server
|
80
|
+
|
81
|
+
# Other things might needs mutex so a new thread is needed
|
82
|
+
Thread.new do
|
83
|
+
mutex_close
|
84
|
+
end.join
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def new_server(type, config)
|
89
|
+
if type == :threaded
|
90
|
+
require_relative "threaded"
|
91
|
+
Rubcask::Server::Threaded.new(@dir, config: config)
|
92
|
+
elsif type == :async
|
93
|
+
require_relative "async"
|
94
|
+
Rubcask::Server::Async.new(@dir, config: config)
|
95
|
+
else
|
96
|
+
raise ArgumentError, "Unknown server typ #{type}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def merge_dir
|
101
|
+
@server.dir.merge
|
102
|
+
rescue => e
|
103
|
+
puts e
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "socket"
|
5
|
+
require "io/wait"
|
6
|
+
require "stringio"
|
7
|
+
|
8
|
+
require_relative "../bytes"
|
9
|
+
require_relative "../protocol"
|
10
|
+
require_relative "config"
|
11
|
+
require_relative "abstract_server"
|
12
|
+
|
13
|
+
module Rubcask
|
14
|
+
module Server
|
15
|
+
# Thread-based server supporting Rubcask protocol
|
16
|
+
# If you are running on CRuby you should consider using Server::Async as it is generally more performant
|
17
|
+
class Threaded < AbstractServer
|
18
|
+
include Protocol
|
19
|
+
|
20
|
+
def initialize(dir, config: Server::Config.new)
|
21
|
+
@dir = dir
|
22
|
+
@config = config
|
23
|
+
@hostname = config.hostname
|
24
|
+
@port = config.port
|
25
|
+
@logger = Logger.new($stdout)
|
26
|
+
@logger.level = Logger::INFO
|
27
|
+
@threads = ThreadGroup.new
|
28
|
+
@connected = false
|
29
|
+
@status = :stopped
|
30
|
+
@listeners = []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates sockets
|
34
|
+
# @return [self]
|
35
|
+
def connect
|
36
|
+
return if @connected
|
37
|
+
@connected = true
|
38
|
+
@listeners = Socket.tcp_server_sockets(@hostname, @port)
|
39
|
+
if @config.keepalive
|
40
|
+
@listeners.each do |s|
|
41
|
+
s.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@listeners.each do |s|
|
45
|
+
address = s.connect_address
|
46
|
+
logger.info "Listening on #{address.ip_address}:#{address.ip_port}"
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Starts the server
|
52
|
+
# @note It blocks the current thread
|
53
|
+
def start
|
54
|
+
connect
|
55
|
+
|
56
|
+
setup_shutdown_pipe
|
57
|
+
|
58
|
+
@status = :running
|
59
|
+
|
60
|
+
Thread.handle_interrupt(Exception => :never) do
|
61
|
+
Thread.handle_interrupt(Exception => :immediate) do
|
62
|
+
accept_loop
|
63
|
+
end
|
64
|
+
ensure
|
65
|
+
cleanup_shutdown_pipe
|
66
|
+
@status = :shutdown
|
67
|
+
cleanup_listeners
|
68
|
+
@threads.list.each(&:kill)
|
69
|
+
@status = :stopped
|
70
|
+
@connected = false
|
71
|
+
logger.info "Closed server"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Shuts down the server
|
76
|
+
# @note You probably want to use it in a signal trap
|
77
|
+
def shutdown
|
78
|
+
if @status == :running
|
79
|
+
@status = :shutdown
|
80
|
+
end
|
81
|
+
@shutdown_pipe[1].write_nonblock("\0")
|
82
|
+
@shutdown_pipe[1].close
|
83
|
+
end
|
84
|
+
|
85
|
+
# Prepares an IO pipe that is used in shutdown process
|
86
|
+
# Call if you need to shutdown the server from a different thread
|
87
|
+
# @return [self]
|
88
|
+
def setup_shutdown_pipe
|
89
|
+
@shutdown_pipe ||= IO.pipe
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
attr_reader :logger
|
96
|
+
|
97
|
+
def cleanup_listeners
|
98
|
+
@listeners.each do |listener|
|
99
|
+
listener.shutdown
|
100
|
+
rescue Errno::ENOTCONN
|
101
|
+
listener.close
|
102
|
+
else
|
103
|
+
listener.close
|
104
|
+
end
|
105
|
+
@listeners.clear
|
106
|
+
end
|
107
|
+
|
108
|
+
def cleanup_shutdown_pipe
|
109
|
+
pipe = @shutdown_pipe
|
110
|
+
pipe&.each(&:close)
|
111
|
+
@shutdown_pipe = nil
|
112
|
+
end
|
113
|
+
|
114
|
+
def accept_loop
|
115
|
+
shutdown_read = @shutdown_pipe[0]
|
116
|
+
while @status == :running
|
117
|
+
begin
|
118
|
+
fds = IO.select([shutdown_read, *@listeners])[0]
|
119
|
+
if fds.include?(shutdown_read)
|
120
|
+
consume_pipe(shutdown_read)
|
121
|
+
break
|
122
|
+
end
|
123
|
+
fds.each do |listener|
|
124
|
+
client = accept_client(listener)
|
125
|
+
next unless client
|
126
|
+
@threads.add(
|
127
|
+
Thread.start(client) { |conn| client_block(conn) }
|
128
|
+
)
|
129
|
+
end
|
130
|
+
rescue Errno::EBADF, Errno::ENOTSOCK, IOError
|
131
|
+
# Possible if socket was manually shut down
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def accept_client(listener)
|
137
|
+
sock = listener.accept_nonblock(exception: false)
|
138
|
+
return nil if sock == :wait_readable
|
139
|
+
sock[0]
|
140
|
+
rescue
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
|
144
|
+
def consume_pipe(pipe)
|
145
|
+
buf = +""
|
146
|
+
while String === pipe.read_nonblock([pipe.nread, 8].max, buf, exception: false)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def client_block(conn)
|
151
|
+
conn.binmode
|
152
|
+
with_interrupt_handle(conn) do |io|
|
153
|
+
client_loop(io)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def with_interrupt_handle(conn)
|
158
|
+
Thread.handle_interrupt(Exception => :never) do
|
159
|
+
Thread.handle_interrupt(Exception => :immediate) do
|
160
|
+
yield conn
|
161
|
+
end
|
162
|
+
ensure
|
163
|
+
begin
|
164
|
+
conn.close
|
165
|
+
rescue # It sometimes failes on jruby
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubcask
|
4
|
+
module Task
|
5
|
+
# Removes all files marked as executable in the directory
|
6
|
+
class CleanDirectory
|
7
|
+
def initialize(directory)
|
8
|
+
@directory = directory
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
Dir.glob(["*.data", "*.hint"].map! { |ext| File.join(@directory, ext) }).each do |file|
|
13
|
+
next unless File.executable?(file)
|
14
|
+
File.delete(file)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubcask
|
4
|
+
# Tombstone represents deleted value
|
5
|
+
|
6
|
+
# The prev_file_id is
|
7
|
+
# stored to support merge of subset of directory files, that is currently not implemented
|
8
|
+
module Tombstone
|
9
|
+
extend self
|
10
|
+
|
11
|
+
PREFIX = "TOMBSTONE".b
|
12
|
+
PREFIX_SIZE = PREFIX.bytesize
|
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
|
22
|
+
|
23
|
+
# Creates a new tombstone value
|
24
|
+
# @param [Integer] current_file_id Id of the active file
|
25
|
+
# @param [Integer] prev_file_id Id of the file where the record is currently located
|
26
|
+
# @return [String]
|
27
|
+
def new_tombstone(current_file_id, prev_file_id)
|
28
|
+
return PREFIX if prev_file_id == current_file_id
|
29
|
+
PREFIX.b << [prev_file_id].pack(FILE_ID_FORMAT)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Gets file id from tombstone value
|
33
|
+
# @param [String] value Tombstone value
|
34
|
+
# @return [Integer, nil]
|
35
|
+
def tombstone_file_id(value)
|
36
|
+
return nil if value.bytesize < FULL_BYTE_SIZE
|
37
|
+
value.byteslice(PREFIX_SIZE, 4).unpack1(FILE_ID_FORMAT)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module Rubcask
|
6
|
+
module Worker
|
7
|
+
# Worker implementation that executes the job in the current thread
|
8
|
+
class DirectWorker
|
9
|
+
def initialize
|
10
|
+
@logger = Logger.new($stdout)
|
11
|
+
end
|
12
|
+
|
13
|
+
def push(task)
|
14
|
+
task.call
|
15
|
+
rescue => e
|
16
|
+
@logger.warn("Error while executing task #{e}")
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "direct_worker"
|
4
|
+
require_relative "ractor_worker"
|
5
|
+
require_relative "thread_worker"
|
6
|
+
|
7
|
+
module Rubcask
|
8
|
+
module Worker
|
9
|
+
module Factory
|
10
|
+
extend self
|
11
|
+
|
12
|
+
# Returns a new worker of provided type
|
13
|
+
# @param [:direct, :thread, :reactor] type Type of worker to create
|
14
|
+
# @return [Worker]
|
15
|
+
# @raise [ArgumentError] if unknown type
|
16
|
+
def new_worker(type)
|
17
|
+
case type
|
18
|
+
when :direct
|
19
|
+
DirectWorker.new
|
20
|
+
when :thread
|
21
|
+
ThreadWorker.new
|
22
|
+
when :ractor
|
23
|
+
RactorWorker.new
|
24
|
+
else
|
25
|
+
raise ArgumentError, "#{type} is not a known worker type"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Class for documentation purposes
|
30
|
+
class Worker
|
31
|
+
# @param [#call] arg job to execute
|
32
|
+
# @return [void]
|
33
|
+
def push(arg)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [void]
|
37
|
+
def close
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
module Rubcask
|
7
|
+
module Worker
|
8
|
+
# Worker implementation that delegates work to a dedicated ractor
|
9
|
+
class RactorWorker
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def_delegator :@ractor, :send, :push
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@ractor = new_ractor
|
16
|
+
@logger = Logger.new($stdout)
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
push(nil)
|
21
|
+
@ractor.take
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def new_ractor
|
27
|
+
Ractor.new(@logger) do |logger|
|
28
|
+
while (value = Ractor.receive)
|
29
|
+
begin
|
30
|
+
value.call
|
31
|
+
rescue => e
|
32
|
+
logger.warn("Error while executing task " + e)
|
33
|
+
end
|
34
|
+
Ractor.yield(nil)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Rubcask
|
6
|
+
module Worker
|
7
|
+
# Worker implementation that delegates work to a dedicated thread
|
8
|
+
class ThreadWorker
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def_delegator :@queue, :push
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@queue = Queue.new
|
15
|
+
@logger = Logger.new($stdout)
|
16
|
+
@thread = new_thread
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
@queue.close
|
21
|
+
@thread.join
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def new_thread
|
28
|
+
Thread.new(@queue) do |queue|
|
29
|
+
while (el = queue.pop)
|
30
|
+
begin
|
31
|
+
el.call
|
32
|
+
rescue => e
|
33
|
+
@logger.warn(e)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/rubcask.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "rubcask/version"
|
4
|
+
require_relative "rubcask/directory"
|
5
|
+
require_relative "rubcask/bytes"
|
6
|
+
|
7
|
+
module Rubcask
|
8
|
+
class Error < StandardError; end
|
9
|
+
|
10
|
+
class LoadError < Error; end
|
11
|
+
|
12
|
+
class ChecksumError < LoadError; end
|
13
|
+
|
14
|
+
class MergeAlreadyInProgressError < Error; end
|
15
|
+
|
16
|
+
class ConfigValidationError < Error; end
|
17
|
+
|
18
|
+
NO_EXPIRE_TIMESTAMP = 0
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubcask
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marcin Henryk Bartkowiak
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-01-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: concurrent-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
description: Bitcask-like Key/Value storege library with a TCP server included
|
28
|
+
email:
|
29
|
+
- mhbartkowiak@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".standard.yml"
|
35
|
+
- Gemfile
|
36
|
+
- Gemfile.lock
|
37
|
+
- LICENSE.txt
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- benchmark/benchmark_io.rb
|
41
|
+
- benchmark/benchmark_server.rb
|
42
|
+
- benchmark/benchmark_server_pipeline.rb
|
43
|
+
- benchmark/benchmark_worker.rb
|
44
|
+
- benchmark/op_times.rb
|
45
|
+
- benchmark/profile.rb
|
46
|
+
- benchmark/server_benchmark_helper.rb
|
47
|
+
- example/server_runner.rb
|
48
|
+
- lib/rubcask.rb
|
49
|
+
- lib/rubcask/bytes.rb
|
50
|
+
- lib/rubcask/concurrency/fake_atomic_fixnum.rb
|
51
|
+
- lib/rubcask/concurrency/fake_lock.rb
|
52
|
+
- lib/rubcask/concurrency/fake_monitor_mixin.rb
|
53
|
+
- lib/rubcask/config.rb
|
54
|
+
- lib/rubcask/data_entry.rb
|
55
|
+
- lib/rubcask/data_file.rb
|
56
|
+
- lib/rubcask/directory.rb
|
57
|
+
- lib/rubcask/expirable_entry.rb
|
58
|
+
- lib/rubcask/hint_entry.rb
|
59
|
+
- lib/rubcask/hint_file.rb
|
60
|
+
- lib/rubcask/hinted_file.rb
|
61
|
+
- lib/rubcask/keydir_entry.rb
|
62
|
+
- lib/rubcask/merge_directory.rb
|
63
|
+
- lib/rubcask/protocol.rb
|
64
|
+
- lib/rubcask/server/abstract_server.rb
|
65
|
+
- lib/rubcask/server/async.rb
|
66
|
+
- lib/rubcask/server/client.rb
|
67
|
+
- lib/rubcask/server/config.rb
|
68
|
+
- lib/rubcask/server/pipeline.rb
|
69
|
+
- lib/rubcask/server/runner.rb
|
70
|
+
- lib/rubcask/server/runner/config.rb
|
71
|
+
- lib/rubcask/server/threaded.rb
|
72
|
+
- lib/rubcask/task/clean_directory.rb
|
73
|
+
- lib/rubcask/tombstone.rb
|
74
|
+
- lib/rubcask/version.rb
|
75
|
+
- lib/rubcask/worker/direct_worker.rb
|
76
|
+
- lib/rubcask/worker/factory.rb
|
77
|
+
- lib/rubcask/worker/ractor_worker.rb
|
78
|
+
- lib/rubcask/worker/thread_worker.rb
|
79
|
+
homepage: https://github.com/mhib/rubcask
|
80
|
+
licenses:
|
81
|
+
- MIT
|
82
|
+
metadata: {}
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 2.7.0
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubygems_version: 3.4.1
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Key/Value storage library
|
102
|
+
test_files: []
|