dr-rubbis 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b71d14548833154a78afeaff9ac69af91456087d
4
+ data.tar.gz: fc4363db29d51f7d7f69870bebf1ba47e10ff397
5
+ SHA512:
6
+ metadata.gz: 9a79f73e98b8bf455d7e8522012b4bb4b0100d466da80d9fe242a87de20cf1161758c17a4ed504f0ca0742743ff96f694c77a9362d34916f397cdaf6461e5d0f
7
+ data.tar.gz: 0625170d31443d3ce56152c741350ff9dce243d4519f90949bf5c20e3c65ae7f23a6b9f2b1e62721d52ba7f71e951e3eb0e966cc32fd993947f8b4a748f912c4
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'redis'
4
+ gem 'rspec'
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.3)
5
+ redis (3.3.3)
6
+ rspec (3.6.0)
7
+ rspec-core (~> 3.6.0)
8
+ rspec-expectations (~> 3.6.0)
9
+ rspec-mocks (~> 3.6.0)
10
+ rspec-core (3.6.0)
11
+ rspec-support (~> 3.6.0)
12
+ rspec-expectations (3.6.0)
13
+ diff-lcs (>= 1.2.0, < 2.0)
14
+ rspec-support (~> 3.6.0)
15
+ rspec-mocks (3.6.0)
16
+ diff-lcs (>= 1.2.0, < 2.0)
17
+ rspec-support (~> 3.6.0)
18
+ rspec-support (3.6.0)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ redis
25
+ rspec
26
+
27
+ BUNDLED WITH
28
+ 1.15.3
data/README.md ADDED
@@ -0,0 +1 @@
1
+ A tutorial for ruby. Implementing redis in ruby.
data/bin/rspec ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'rspec' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("rspec-core", "rspec")
data/bin/server ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ port = ARGV.fetch(0, 6379)
4
+
5
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
6
+
7
+ require 'rubbis/server'
8
+
9
+ Rubbis::Server.new(port: port, aof_file: "./data.aof").listen
data/lib/dr_rubbis.rb ADDED
@@ -0,0 +1,3 @@
1
+ # $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+
3
+ require 'rubbis/server'
@@ -0,0 +1,78 @@
1
+ require 'rubbis/transaction'
2
+ module Rubbis
3
+ class Handler
4
+
5
+ attr_reader :client, :buffer, :tx, :server
6
+
7
+ def initialize(socket, server)
8
+ @client = socket
9
+ @server = server
10
+ @buffer = ""
11
+ reset_tx!
12
+ end
13
+
14
+ def reset_tx!
15
+ @tx = Transaction.new
16
+ end
17
+
18
+ def process!(state)
19
+ buffer << client.read_nonblock(1024)
20
+ cmds, processed = Rubbis::Protocol.unmarshal(buffer)
21
+
22
+ @buffer = buffer[processed..-1]
23
+
24
+ cmds.each do |cmd|
25
+ response = if tx.active?
26
+ case cmd[0].downcase
27
+ when 'exec'
28
+ result = tx.buffer.map do |cmd|
29
+ dispatch state, cmd
30
+ end unless tx.dirty?
31
+ reset_tx!
32
+ result
33
+ else
34
+ tx.queue cmd
35
+ :queued
36
+ end
37
+ else
38
+ dispatch state, cmd
39
+ end
40
+
41
+ unless response == :block
42
+ respond! response
43
+ end
44
+
45
+ state.process_list_watches!
46
+ end
47
+ end
48
+
49
+ def respond!(response)
50
+ if active?
51
+ server.commit!
52
+ client.write Rubbis::Protocol.marshal(response)
53
+ end
54
+ end
55
+
56
+ def active?
57
+ client
58
+ end
59
+
60
+ def disconnect!(state)
61
+ state.unsubscribe_all(self)
62
+ @client = nil
63
+ end
64
+
65
+ def dispatch(state, cmd)
66
+ case cmd[0].downcase
67
+ when 'bgsave' then server.bgsave; :ok
68
+ when 'multi' then tx.start!; :ok
69
+ when 'watch' then
70
+ curr_tx = tx
71
+ state.watch(cmd[1]) {
72
+ tx.dirty! if curr_tx == tx
73
+ }
74
+ else state.apply_command(self, cmd)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,63 @@
1
+ module Rubbis
2
+ class Protocol
3
+ class ProtocolError < RuntimeError; end
4
+
5
+ class << self
6
+ def marshal(ruby)
7
+ case ruby
8
+ when Symbol then "+#{ruby.to_s.upcase}\r\n"
9
+ when nil then "$-1\r\n"
10
+ when Integer then ":#{ruby}\r\n"
11
+ when String then "$#{ruby.length}\r\n#{ruby}\r\n"
12
+ when Error then "-ERR #{ruby.message}\r\n"
13
+ when Array then "*#{ruby.length}\r\n#{ruby.map {|x| marshal(x)}.join}"
14
+ else raise "Dont know how to marshal #{ruby}"
15
+ end
16
+ end
17
+
18
+ def unmarshal(data)
19
+ io = StringIO.new(data)
20
+ result = []
21
+ processed = 0
22
+ begin
23
+ loop do
24
+ header = safe_readline(io)
25
+
26
+ raise ProtocolError unless header[0] == '*'
27
+
28
+ n = header[1..-1].to_i
29
+
30
+ result << n.times.map do
31
+ raise ProtocolError unless io.readpartial(1) == '$'
32
+
33
+ length = safe_readline(io).to_i
34
+ safe_readpartial(io, length).tap do
35
+ safe_readline(io)
36
+ end
37
+ end
38
+
39
+ processed = io.pos
40
+ end
41
+ rescue ProtocolError
42
+ processed = io.pos
43
+ rescue EOFError
44
+ end
45
+
46
+ [result, processed]
47
+ end
48
+
49
+ def safe_readline(io)
50
+ io.readline("\r\n").tap do |line|
51
+ raise EOFError unless line.end_with?("\r\n")
52
+ end
53
+ end
54
+
55
+ def safe_readpartial(io, length)
56
+ io.readpartial(length).tap do |data|
57
+ raise EOFError unless data.length == length
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,166 @@
1
+ require "socket"
2
+ require 'stringio'
3
+ require 'tempfile'
4
+
5
+ require 'rubbis/protocol'
6
+ require 'rubbis/state'
7
+ require 'rubbis/handler'
8
+
9
+ module Rubbis
10
+ class Server
11
+
12
+ class Clock
13
+ def now
14
+ Time.now.to_f
15
+ end
16
+
17
+ def sleep(x)
18
+ ::Kernel.sleep x
19
+ end
20
+ end
21
+
22
+ def initialize(opts = {})
23
+ @port = opts[:port]
24
+ @shutdown_pipe = IO.pipe
25
+ @clock = Clock.new
26
+ @state = State.new(@clock)
27
+ @server_file = opts[:server_file]
28
+ @aof_file = opts[:aof_file]
29
+ end
30
+
31
+ def bgsave
32
+ return unless server_file
33
+ begin
34
+ tmpf = Tempfile.new(File.basename(server_file))
35
+ tmpf.write state.serialize
36
+ tmpf.close
37
+ FileUtils.mv(tmpf, server_file)
38
+ :ok
39
+ ensure
40
+ if tmpf
41
+ tmpf.close
42
+ tmpf.unlink
43
+ end
44
+ end
45
+ end
46
+
47
+ def bgrewriteaof
48
+ return unless aof_file
49
+ begin
50
+ tmpf = Tempfile.new(File.basename(aof_file))
51
+ state.minimal_log.each do |cmd|
52
+ tmpf.write Rubbis::Protocol.marshal(cmd)
53
+ end
54
+
55
+ tmpf.close
56
+
57
+ command_log.close
58
+ FileUtils.mv(tmpf, aof_file)
59
+ @command_log = File.open(aof_file, 'a')
60
+ :ok
61
+ ensure
62
+ if tmpf
63
+ tmpf.close
64
+ tmpf.unlink
65
+ end
66
+ end
67
+ end
68
+
69
+ def commit!
70
+ return unless command_log
71
+
72
+ state.log.each do |cmd|
73
+ command_log.write Rubbis::Protocol.marshal(cmd)
74
+ end
75
+ command_log.fsync
76
+ state.log.clear
77
+ end
78
+
79
+ def check_background_processes!
80
+
81
+ end
82
+
83
+ def shutdown
84
+ shutdown_pipe[1].close
85
+ end
86
+
87
+ class AofClient < StringIO
88
+ def write(*_)
89
+ # noop
90
+ end
91
+ end
92
+ def apply_log(contents)
93
+ Handler.new(AofClient.new(contents), self).process!(state)
94
+ end
95
+
96
+ def listen
97
+ readable = []
98
+ clients = {}
99
+ running = true
100
+
101
+ server = TCPServer.new(port)
102
+
103
+ expire_pipe = IO.pipe
104
+ readable << server
105
+ readable << shutdown_pipe[0]
106
+ readable << expire_pipe[0]
107
+
108
+ @command_log = File.open(aof_file, 'a') if aof_file
109
+
110
+ if aof_file && File.exists?(aof_file)
111
+ apply_log File.read(aof_file)
112
+ elsif server_file && File.exists?(server_file)
113
+ @state.deserialize File.read(server_file)
114
+ end
115
+
116
+ timer_thread = Thread.new do
117
+ begin
118
+ while running
119
+ sleep 0.1
120
+ expire_pipe[1].write '.'
121
+ end
122
+ rescue Errno::EPIPE, IOError
123
+ end
124
+ end
125
+
126
+ while running
127
+ ready_to_read, _ = IO.select(readable + clients.keys)
128
+ ready_to_read.each do |socket|
129
+ case socket
130
+ when server
131
+ child_socket = socket.accept_nonblock
132
+ clients[child_socket] = Handler.new(child_socket, self)
133
+ when shutdown_pipe[0]
134
+ running = false
135
+ when expire_pipe[0]
136
+ state.expire_keys!
137
+ check_background_processes!
138
+ else
139
+ begin
140
+ clients[socket].process!(state)
141
+ rescue EOFError
142
+ handler = clients.delete(socket)
143
+ handler.disconnect!(state)
144
+ socket.close
145
+ end
146
+ end
147
+ end
148
+ end
149
+ ensure
150
+ running = false
151
+ (readable + clients.keys).each do |file|
152
+ file.close
153
+ end
154
+ expire_pipe[1].close if expire_pipe
155
+ timer_thread.join if timer_thread
156
+ end
157
+
158
+ private
159
+
160
+ attr_reader :shutdown_pipe, :state, :clock, :server_file, :aof_file, :command_log
161
+
162
+ def port
163
+ @port
164
+ end
165
+ end
166
+ end