fraggle-spanx 4.0.1.1
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.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +223 -0
- data/Rakefile +17 -0
- data/bench/gs.rb +87 -0
- data/example/getdir.rb +29 -0
- data/example/readme.rb +82 -0
- data/example/walk.rb +33 -0
- data/example/watch.rb +16 -0
- data/fraggle.gemspec +28 -0
- data/lib/fraggle.rb +59 -0
- data/lib/fraggle/client.rb +211 -0
- data/lib/fraggle/connection.rb +124 -0
- data/lib/fraggle/msg.pb.rb +60 -0
- data/lib/fraggle/response.rb +28 -0
- data/lib/fraggle/version.rb +3 -0
- data/test/fraggle_client_test.rb +238 -0
- data/test/fraggle_protocol_test.rb +88 -0
- data/test/fraggle_test.rb +12 -0
- data/test/fraggle_transaction_test.rb +116 -0
- data/test/helper.rb +78 -0
- metadata +135 -0
data/example/walk.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'fraggle'
|
3
|
+
|
4
|
+
EM.run do
|
5
|
+
c = Fraggle.connect do |c, err|
|
6
|
+
if err
|
7
|
+
fail err.message
|
8
|
+
end
|
9
|
+
|
10
|
+
c.rev do |v|
|
11
|
+
# Valid
|
12
|
+
req = c.walk(v, "/ctl/node/**") do |ents, err|
|
13
|
+
if err
|
14
|
+
p [:err, err]
|
15
|
+
else
|
16
|
+
ents.each do |e|
|
17
|
+
puts File.join(req.path, e.path) + "=" + e.value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Limit 0 return nothing
|
23
|
+
c.walk(v, "/ctl/node/**", 0, 0) do |ents, err|
|
24
|
+
p [:nothing, ents, err]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Error
|
28
|
+
c.walk(v, "/nothere") do |ents, err|
|
29
|
+
p [:nothere, ents, err]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/example/watch.rb
ADDED
data/fraggle.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "fraggle/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "fraggle-spanx"
|
7
|
+
s.version = Fraggle::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Blake Mizerany"]
|
10
|
+
s.email = ["blake.mizerany@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{An EventMachine Client for Doozer}
|
13
|
+
s.description = s.summary
|
14
|
+
|
15
|
+
s.rubyforge_project = "fraggle"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency "beefcake-spanx"
|
23
|
+
s.add_dependency "eventmachine"
|
24
|
+
|
25
|
+
s.add_development_dependency "turn"
|
26
|
+
s.add_development_dependency "statsample"
|
27
|
+
s.add_development_dependency "rake"
|
28
|
+
end
|
data/lib/fraggle.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'fraggle/client'
|
2
|
+
|
3
|
+
module Fraggle
|
4
|
+
include Response::Err
|
5
|
+
|
6
|
+
Clobber = Client::MaxInt64
|
7
|
+
|
8
|
+
DEFAULT_URI = "doozer:?" + [
|
9
|
+
"ca=127.0.0.1:8046",
|
10
|
+
"ca=127.0.0.1:8041",
|
11
|
+
"ca=127.0.0.1:8042",
|
12
|
+
"ca=127.0.0.1:8043"
|
13
|
+
].join("&")
|
14
|
+
|
15
|
+
def self.connect(uri=nil, &blk)
|
16
|
+
uri = uri || ENV["DOOZER_URI"] || DEFAULT_URI
|
17
|
+
|
18
|
+
addrs, sk = uri(uri)
|
19
|
+
|
20
|
+
if addrs.length == 0
|
21
|
+
raise ArgumentError, "there were no addrs supplied in the uri (#{uri.inspect})"
|
22
|
+
end
|
23
|
+
|
24
|
+
addr = addrs.shift
|
25
|
+
host, port = addr.split(":")
|
26
|
+
|
27
|
+
cn = EM.connect(host, port, Connection, addr)
|
28
|
+
c = Client.new(cn, addrs)
|
29
|
+
c.access(sk) do |_, err|
|
30
|
+
if err
|
31
|
+
blk.call(nil, err)
|
32
|
+
else
|
33
|
+
blk.call(c, nil)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.uri(u)
|
39
|
+
addrs, sk = [], ""
|
40
|
+
|
41
|
+
if u =~ /^doozer:\?(.*)$/
|
42
|
+
parts = $1.split("&")
|
43
|
+
parts.each do |pt|
|
44
|
+
k, v = pt.split("=")
|
45
|
+
case k
|
46
|
+
when "ca"
|
47
|
+
addrs << v
|
48
|
+
when "sk"
|
49
|
+
sk = v
|
50
|
+
end
|
51
|
+
end
|
52
|
+
else
|
53
|
+
raise ArgumentError, "invalid doozer uri"
|
54
|
+
end
|
55
|
+
|
56
|
+
[addrs, sk]
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'fraggle/connection'
|
3
|
+
|
4
|
+
module Fraggle
|
5
|
+
class Client
|
6
|
+
include Request::Verb
|
7
|
+
|
8
|
+
MaxInt64 = 1<<63 - 1
|
9
|
+
|
10
|
+
attr_reader :cn, :addrs
|
11
|
+
|
12
|
+
def initialize(cn, addrs)
|
13
|
+
@cn, @addrs = cn, addrs
|
14
|
+
@attempt = Proc.new {|_| true }
|
15
|
+
end
|
16
|
+
|
17
|
+
def addr
|
18
|
+
cn.addr
|
19
|
+
end
|
20
|
+
|
21
|
+
def set(rev, path, value, &blk)
|
22
|
+
req = Request.new
|
23
|
+
req.verb = SET
|
24
|
+
req.rev = rev
|
25
|
+
req.path = path
|
26
|
+
req.value = value
|
27
|
+
|
28
|
+
idemp(req, &blk)
|
29
|
+
end
|
30
|
+
|
31
|
+
def get(rev, path, &blk)
|
32
|
+
req = Request.new
|
33
|
+
req.verb = GET
|
34
|
+
req.rev = rev
|
35
|
+
req.path = path
|
36
|
+
|
37
|
+
resend(req, &blk)
|
38
|
+
end
|
39
|
+
|
40
|
+
def del(rev, path, &blk)
|
41
|
+
req = Request.new
|
42
|
+
req.verb = DEL
|
43
|
+
req.rev = rev
|
44
|
+
req.path = path
|
45
|
+
|
46
|
+
idemp(req, &blk)
|
47
|
+
end
|
48
|
+
|
49
|
+
def _getdir(rev, path, offset, &blk)
|
50
|
+
req = Request.new
|
51
|
+
req.verb = GETDIR
|
52
|
+
req.rev = rev
|
53
|
+
req.path = path
|
54
|
+
req.offset = offset
|
55
|
+
|
56
|
+
resend(req, &blk)
|
57
|
+
end
|
58
|
+
|
59
|
+
def _walk(rev, path, offset, &blk)
|
60
|
+
req = Request.new
|
61
|
+
req.verb = WALK
|
62
|
+
req.rev = rev
|
63
|
+
req.path = path
|
64
|
+
req.offset = offset
|
65
|
+
|
66
|
+
resend(req, &blk)
|
67
|
+
end
|
68
|
+
|
69
|
+
def wait(rev, path, &blk)
|
70
|
+
req = Request.new
|
71
|
+
req.verb = WAIT
|
72
|
+
req.rev = rev
|
73
|
+
req.path = path
|
74
|
+
|
75
|
+
resend(req, &blk)
|
76
|
+
end
|
77
|
+
|
78
|
+
def rev(&blk)
|
79
|
+
req = Request.new
|
80
|
+
req.verb = REV
|
81
|
+
|
82
|
+
resend(req) do |v, _|
|
83
|
+
blk.call(v.rev)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def stat(rev, path, &blk)
|
88
|
+
req = Request.new
|
89
|
+
req.rev = rev
|
90
|
+
req.verb = STAT
|
91
|
+
req.path = path
|
92
|
+
|
93
|
+
resend(req, &blk)
|
94
|
+
end
|
95
|
+
|
96
|
+
def access(secret, &blk)
|
97
|
+
req = Request.new
|
98
|
+
req.verb = ACCESS
|
99
|
+
req.value = secret
|
100
|
+
|
101
|
+
resend(req, &blk)
|
102
|
+
end
|
103
|
+
|
104
|
+
def watch(rev, path, &blk)
|
105
|
+
wait(rev, path) do |e, err|
|
106
|
+
blk.call(e, err)
|
107
|
+
if ! err
|
108
|
+
watch(e.rev+1, path, &blk)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def getdir(rev, path, off=0, lim=MaxInt64, ents=[], &blk)
|
114
|
+
all(:_getdir, rev, path, off, lim, ents, &blk)
|
115
|
+
end
|
116
|
+
|
117
|
+
def walk(rev, path, off=0, lim=MaxInt64, ents=[], &blk)
|
118
|
+
all(:_walk, rev, path, off, lim, ents, &blk)
|
119
|
+
end
|
120
|
+
|
121
|
+
def all(m, rev, path, off, lim, ents=[], &blk)
|
122
|
+
# We're decrementing lim as we go, so we need to return
|
123
|
+
# the accumulated values
|
124
|
+
if lim == 0
|
125
|
+
cn.next_tick { blk.call(ents, nil) }
|
126
|
+
return
|
127
|
+
end
|
128
|
+
|
129
|
+
__send__(m, rev, path, off) do |e, err|
|
130
|
+
case err && err.code
|
131
|
+
when nil
|
132
|
+
all(m, rev, path, off+1, lim-1, ents << e, &blk)
|
133
|
+
when Fraggle::Response::Err::RANGE
|
134
|
+
blk.call(ents, nil)
|
135
|
+
else
|
136
|
+
blk.call(nil, e)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Sends a request to the server. Returns the request with a new tag
|
142
|
+
# assigned.
|
143
|
+
def send(req, &blk)
|
144
|
+
cb = Proc.new do |e, err|
|
145
|
+
case err
|
146
|
+
when Connection::DisconnectedError
|
147
|
+
if cn.err?
|
148
|
+
reconnect!
|
149
|
+
end
|
150
|
+
end
|
151
|
+
blk.call(e, err)
|
152
|
+
end
|
153
|
+
|
154
|
+
cn.send_request(req, cb)
|
155
|
+
end
|
156
|
+
|
157
|
+
def resend(req, &blk)
|
158
|
+
send(req) do |e, err|
|
159
|
+
case err
|
160
|
+
when Connection::DisconnectedError
|
161
|
+
req.tag = nil
|
162
|
+
resend(req, &blk)
|
163
|
+
else
|
164
|
+
blk.call(e, err)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def idemp(req, &blk)
|
170
|
+
send(req) do |e, err|
|
171
|
+
case err
|
172
|
+
when Connection::DisconnectedError
|
173
|
+
# If we're trying to update a value that isn't missing or that we're
|
174
|
+
# not trying to clobber, it's safe to retry. We can't idempotently
|
175
|
+
# update missing values because there may be a race with another
|
176
|
+
# client that sets and/or deletes the key during the time between your
|
177
|
+
# read and write.
|
178
|
+
if (req.rev || 0) > 0
|
179
|
+
req.tag = nil
|
180
|
+
idemp(req, &blk)
|
181
|
+
else
|
182
|
+
blk.call(e, err)
|
183
|
+
end
|
184
|
+
else
|
185
|
+
blk.call(e, err)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# Setting `blk` will cause a client to call it before attempting to reconnect.
|
192
|
+
# `blk` is called with one parameter `addr`, which is the address that will be
|
193
|
+
# for reconnect.
|
194
|
+
def attempt(&blk)
|
195
|
+
@attempt = blk
|
196
|
+
end
|
197
|
+
|
198
|
+
def reconnect!
|
199
|
+
addr = @addrs.slice(rand(@addrs.length))
|
200
|
+
if @attempt.call(addr)
|
201
|
+
reconnect(addr)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def reconnect(addr)
|
206
|
+
host, port = addr.split(":")
|
207
|
+
@cn = EM.connect(host, port, Fraggle::Connection, addr)
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'fraggle/response'
|
2
|
+
|
3
|
+
module Fraggle
|
4
|
+
|
5
|
+
module Connection
|
6
|
+
|
7
|
+
# Raised when a request is invalid
|
8
|
+
class SendError < StandardError
|
9
|
+
def initialize(req, msg=nil)
|
10
|
+
@req = req
|
11
|
+
super(msg)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class DisconnectedError < StandardError
|
16
|
+
def ==(o)
|
17
|
+
return false if ! o.kind_of?(self.class)
|
18
|
+
message == o.message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class ResponseError < StandardError
|
23
|
+
attr_reader :code
|
24
|
+
|
25
|
+
alias :detail :message
|
26
|
+
|
27
|
+
def initialize(res)
|
28
|
+
@code = res.err_code
|
29
|
+
super("#{res.name_for(Response::Err, code)}: #{res.err_detail}")
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(o)
|
33
|
+
return false if ! o.kind_of?(self.class)
|
34
|
+
code == o.code && message == o.message
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
attr_reader :addr
|
40
|
+
|
41
|
+
def initialize(addr)
|
42
|
+
@addr, @cb = addr, {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def receive_data(data)
|
46
|
+
(@buf ||= "") << data
|
47
|
+
|
48
|
+
while @buf.length > 0
|
49
|
+
if @len && @buf.length >= @len
|
50
|
+
bytes = @buf.slice!(0, @len)
|
51
|
+
@len = nil
|
52
|
+
res = Response.decode(bytes)
|
53
|
+
receive_response(res)
|
54
|
+
elsif @buf.length >= 4
|
55
|
+
bytes = @buf.slice!(0, 4)
|
56
|
+
@len = bytes.unpack("N")[0]
|
57
|
+
else
|
58
|
+
break
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# The default receive_response
|
64
|
+
def receive_response(res)
|
65
|
+
return if err?
|
66
|
+
req, blk = @cb.delete(res.tag)
|
67
|
+
return if ! blk
|
68
|
+
if res.err_code
|
69
|
+
blk.call(nil, ResponseError.new(res))
|
70
|
+
else
|
71
|
+
blk.call(res, nil)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def send_request(req, blk)
|
76
|
+
if req.tag
|
77
|
+
raise SendError, "Already sent #{req.inspect}"
|
78
|
+
end
|
79
|
+
|
80
|
+
if err?
|
81
|
+
next_tick { blk.call(nil, DisconnectedError.new(self.addr)) }
|
82
|
+
return req
|
83
|
+
end
|
84
|
+
|
85
|
+
req.tag = 0
|
86
|
+
while @cb.has_key?(req.tag)
|
87
|
+
req.tag += 1
|
88
|
+
|
89
|
+
req.tag %= 2**31
|
90
|
+
end
|
91
|
+
|
92
|
+
# TODO: remove this!
|
93
|
+
@cb[req.tag] = [req, blk]
|
94
|
+
|
95
|
+
data = req.encode
|
96
|
+
head = [data.length].pack("N")
|
97
|
+
|
98
|
+
send_data("#{head}#{data}")
|
99
|
+
|
100
|
+
req
|
101
|
+
end
|
102
|
+
|
103
|
+
def unbind
|
104
|
+
@err = true
|
105
|
+
@cb.values.each do |_, blk|
|
106
|
+
blk.call(nil, DisconnectedError.new(self.addr))
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def err?
|
111
|
+
!!@err
|
112
|
+
end
|
113
|
+
|
114
|
+
def timer(n, &blk)
|
115
|
+
EM.add_timer(n, &blk)
|
116
|
+
end
|
117
|
+
|
118
|
+
def next_tick(&blk)
|
119
|
+
EM.next_tick(&blk)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|