fraggle-spanx 4.0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|