fraggle 0.4.0 → 1.0.0pre

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fraggle.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ HOME = ENV["HOME"]
5
+ PBDIR = HOME+"/src/doozer/src/pkg/proto"
6
+
7
+ namespace :proto do
8
+ task :update do
9
+ ENV["BEEFCAKE_NAMESPACE"] = "Fraggle"
10
+ sh(
11
+ "protoc",
12
+ "--beefcake_out", "lib/fraggle",
13
+ "-I", PBDIR,
14
+ PBDIR+"/msg.proto"
15
+ )
16
+ end
17
+ end
data/example/again.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'fraggle'
4
+
5
+ EM.run do
6
+
7
+ c = Fraggle.connect
8
+ c.level = Fraggle::Logger::INFO
9
+
10
+ paths = []
11
+
12
+ c.walk("/**") do |e|
13
+ paths << e.path+"="+e.value
14
+ end.again do |w|
15
+ # We've been connected to to a new server, Resend the request
16
+ c.send(w)
17
+ end.done do
18
+ puts "## DONE", *paths
19
+ end
20
+
21
+ end
data/example/getdir.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'fraggle'
4
+
5
+ EM.run do
6
+
7
+ EM.error_handler do |e|
8
+ $stderr.puts e.message + "\n" + (e.backtrace * "\n")
9
+ end
10
+
11
+ c = Fraggle.connect
12
+ c.level = Fraggle::Logger::INFO
13
+
14
+ ents = []
15
+ req = c.getdir "/doozer" do |e|
16
+ ents << e.path
17
+ end
18
+
19
+ req.error do
20
+ puts *ents
21
+ end
22
+
23
+
24
+ end
data/example/ping.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'fraggle'
4
+
5
+ EM.run do
6
+
7
+ EM.error_handler do |e|
8
+ $stderr.puts e.message + "\n" + (e.backtrace * "\n")
9
+ end
10
+
11
+ c = Fraggle.connect
12
+
13
+ EM.add_periodic_timer(1) do
14
+ c.get nil, "/ping" do |e|
15
+ p [:e, e]
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'fraggle'
4
+
5
+ EM.run do
6
+
7
+ EM.error_handler do |e|
8
+ $stderr.puts e.message + "\n" + (e.backtrace * "\n")
9
+ end
10
+
11
+ c = Fraggle.connect :level => Fraggle::Client::DEBUG
12
+
13
+ c.session "example." do |session_id|
14
+ c.debug "established session (#{session_id})!"
15
+ end
16
+ end
data/example/set.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'fraggle'
4
+
5
+ EM.run do
6
+ EM.error_handler do |e|
7
+ $stderr.puts e.message + "\n" + (e.backtrace * "\n")
8
+ end
9
+
10
+ c = Fraggle.connect
11
+
12
+ EM.add_periodic_timer(1) do
13
+ c.get "/hello" do |e|
14
+ p [:e, e]
15
+ end
16
+ end
17
+
18
+ c.set '/hello', 'world', :missing do |e|
19
+ p e
20
+ end
21
+ end
data/example/stat.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'fraggle'
4
+
5
+ EM.run do
6
+
7
+ EM.error_handler do |e|
8
+ $stderr.puts e.message + "\n" + (e.backtrace * "\n")
9
+ end
10
+
11
+ c = Fraggle.connect
12
+
13
+ EM.add_periodic_timer(1) do
14
+ c.stat "/example" do |e|
15
+ p e
16
+ end
17
+ end
18
+
19
+ EM.add_periodic_timer(0.5) do
20
+ c.set "/example/#{rand(10)}", "test", :clobber
21
+ end
22
+
23
+ end
data/example/walk.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'fraggle'
4
+
5
+ EM.run do
6
+
7
+ EM.error_handler do |e|
8
+ $stderr.puts e.message + "\n" + (e.backtrace * "\n")
9
+ end
10
+
11
+ c = Fraggle.connect
12
+
13
+ paths = []
14
+ req = c.walk "/**" do |e|
15
+ paths << e.path+"="+e.value
16
+ end
17
+
18
+ req.done do
19
+ puts *paths
20
+ end
21
+
22
+
23
+ end
data/example/watch.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'fraggle'
4
+
5
+ EM.run do
6
+
7
+ EM.error_handler do |e|
8
+ $stderr.puts e.message + "\n" + (e.backtrace * "\n")
9
+ end
10
+
11
+ c = Fraggle.connect
12
+ c.level = Fraggle::Logger::WARN
13
+
14
+ c.watch "/example/*" do |e|
15
+ p e
16
+ end
17
+
18
+ EM.add_periodic_timer(0.5) do
19
+ c.set "/example/#{rand(10)}", "test", :clobber
20
+ end
21
+
22
+ end
data/fraggle.gemspec ADDED
@@ -0,0 +1,25 @@
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"
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", "~>0.3"
23
+
24
+ s.add_development_dependency "turn"
25
+ end
data/lib/fraggle.rb CHANGED
@@ -1,50 +1,43 @@
1
1
  require 'fraggle/client'
2
- require 'fraggle/errors'
3
- require 'fraggle/logger'
4
- require 'uri'
5
2
 
6
3
  module Fraggle
7
- extend Logger
8
4
 
9
- DefaultUri = "doozer:?"+
10
- # Default host/port
11
- "ca=127.0.0.1:8046&"+
12
-
13
- # Default host + test-cluster ports
14
- "ca=127.0.0.1:8041&"+
15
- "ca=127.0.0.1:8042&"+
5
+ DEFAULT_URI = "doozerd:?" + [
6
+ "ca=127.0.0.1:8046",
7
+ "ca=127.0.0.1:8041",
8
+ "ca=127.0.0.1:8042",
16
9
  "ca=127.0.0.1:8043"
10
+ ].join("&")
11
+
12
+ def self.connect(uri=nil)
13
+ uri = uri || ENV["DOOZER_URI"] || DEFAULT_URI
14
+
15
+ addrs = uri(uri)
16
+
17
+ if addrs.length == 0
18
+ raise ArgumentError, "there were no addrs supplied in the uri (#{uri.inspect})"
19
+ end
17
20
 
18
- def self.connect(*args)
19
- opts = args.last.is_a?(Hash) ? args.pop : {}
20
- uri = args.shift || ENV["DOOZER_URI"] || DefaultUri
21
- addrs = addrs_for(uri)
21
+ addr = addrs.shift
22
+ host, port = addr.split(":")
22
23
 
23
- host, port = addrs.first.split(":")
24
- EM.connect(host, port, Client, addrs.shift, addrs, opts)
24
+ cn = EM.connect(host, port, Connection, addr)
25
+ Client.new(cn, addrs)
25
26
  end
26
27
 
27
- def self.addrs_for(uri)
28
- params = uri.gsub(/^doozer:\?/, '').split("&")
29
- addrs = []
30
-
31
- params.each do |param|
32
- k, v = param.split("=")
33
- if k == "ca"
34
- # Perform a liberal validation to weed out most mistakes
35
- if v =~ /^[\d\w\-.]+:\d+$/
36
- addrs << v
37
- else
38
- warn "invalid addr (#{v}) in #{uri}"
28
+ def self.uri(u)
29
+ if u =~ /^doozerd:\?(.*)$/
30
+ parts = $1.split("&")
31
+ parts.inject([]) do |m, pt|
32
+ k, v = pt.split("=")
33
+ if k == "ca"
34
+ m << v
39
35
  end
36
+ m
40
37
  end
38
+ else
39
+ raise ArgumentError, "invalid doozerd uri"
41
40
  end
42
-
43
- if addrs.empty?
44
- raise NoAddrs
45
- end
46
-
47
- addrs
48
41
  end
49
42
 
50
43
  end
@@ -1,253 +1,99 @@
1
- require 'fraggle/errors'
2
- require 'fraggle/logger'
3
- require 'fraggle/meta'
4
- require 'fraggle/protocol'
5
- require 'fraggle/request'
6
- require 'fraggle/response'
7
- require 'set'
8
- require 'uri'
1
+ require 'eventmachine'
2
+ require 'fraggle/connection'
9
3
 
10
4
  module Fraggle
5
+ class Client
6
+ include Request::Verb
11
7
 
12
- module Client
13
- include Protocol
14
- include Logger
15
-
16
- class Error < StandardError ; end
17
-
18
-
19
- MinTag = 0
20
- MaxTag = (1<<32)
21
-
22
- Nibbles = "0123456789abcdef"
23
-
24
- def initialize(addr, addrs=[], opts={})
25
- @cbx = {}
26
-
27
- @addr = addr
28
- @addrs = {}
29
-
30
- addrs.each_with_index do |addr, i|
31
- @addrs[i] = addr
32
- end
33
-
34
- # Logging
35
- @level = opts[:level] || ERROR
36
- @writer = $stderr
8
+ class NoMoreAddrs < StandardError
37
9
  end
38
10
 
39
- def receive_response(res)
40
- debug "received response: #{res.inspect}"
41
-
42
- if res.err_code
43
- if req = @cbx.delete(res.tag)
44
- req.emit(:error, res)
45
- return
46
- end
47
- end
48
-
49
- if (res.flags & Response::Flag::VALID) > 0
50
- if req = @cbx[res.tag]
51
- req.emit(:valid, res)
52
- end
53
- end
11
+ attr_reader :cn
54
12
 
55
- if (res.flags & Response::Flag::DONE) > 0
56
- if req = @cbx.delete(res.tag)
57
- req.emit(:done)
58
- end
59
- end
60
- end
61
-
62
- def rev
63
- req = Request.new
64
- req.verb = Request::Verb::REV
65
-
66
- resend(req)
13
+ def initialize(cn, addrs)
14
+ @cn, @addrs = cn, addrs
67
15
  end
68
16
 
69
- def checkin(path, rev)
17
+ def set(rev, path, value, &blk)
70
18
  req = Request.new
71
- req.verb = Request::Verb::CHECKIN
72
- req.path = path
73
- req.rev = casify(rev)
74
-
75
- resend(req)
76
- end
77
-
78
- def session(prefix=nil, &blk)
79
- name = "#{prefix}#{genkey}"
80
- estab = false
81
-
82
- f = Proc.new do |e|
83
- # If this is the first response from the server, it's go-time.
84
- if ! estab
85
- blk.call(name)
86
- end
87
-
88
- # We've successfully established a session. Say so.
89
- estab = true
90
-
91
- # Get back to the server ASAP
92
- checkin(name, e.rev).valid(&f)
93
- end
19
+ req.verb = SET
20
+ req.rev = rev
21
+ req.path = path
22
+ req.value = value
23
+ req.valid(&blk)
94
24
 
95
- checkin(name, 0).valid(&f)
25
+ send(req)
96
26
  end
97
27
 
98
- def get(rev, path)
28
+ def get(rev, path, &blk)
99
29
  req = Request.new
30
+ req.verb = GET
100
31
  req.rev = rev
101
- req.verb = Request::Verb::GET
102
32
  req.path = path
33
+ req.valid(&blk)
103
34
 
104
- resend(req)
35
+ send(req)
105
36
  end
106
37
 
107
- def stat(rev, path)
38
+ def del(rev, path, &blk)
108
39
  req = Request.new
40
+ req.verb = DEL
109
41
  req.rev = rev
110
- req.verb = Request::Verb::STAT
111
42
  req.path = path
112
-
113
- resend(req)
114
- end
115
-
116
- def getdir(rev, path, offset, limit)
117
- req = Request.new
118
- req.rev = rev
119
- req.verb = Request::Verb::GETDIR
120
- req.offset = offset if offset != 0
121
- req.limit = limit if limit != 0
122
- req.path = path
123
-
124
- resend(req)
125
- end
126
-
127
- def set(path, value, rev)
128
- req = Request.new
129
- req.verb = Request::Verb::SET
130
- req.path = path
131
- req.value = value
132
- req.rev = casify(rev)
133
-
134
- send(req)
135
- end
136
-
137
- def del(path, rev)
138
- req = Request.new
139
- req.verb = Request::Verb::DEL
140
- req.path = path
141
- req.rev = casify(rev)
43
+ req.valid(&blk)
142
44
 
143
45
  send(req)
144
46
  end
145
47
 
146
- def walk(rev, glob, offset=nil, limit=nil)
48
+ def getdir(rev, path, offset=nil, limit=nil, &blk)
147
49
  req = Request.new
148
- req.verb = Request::Verb::WALK
149
- req.rev = rev
150
- req.path = glob
50
+ req.verb = GETDIR
51
+ req.rev = rev
52
+ req.path = path
151
53
  req.offset = offset
152
54
  req.limit = limit
55
+ req.valid(&blk)
153
56
 
154
- cancelable(resend(req))
57
+ send(req)
155
58
  end
156
59
 
157
- def watch(rev, glob)
60
+ def walk(rev, path, offset=nil, limit=nil, &blk)
158
61
  req = Request.new
62
+ req.verb = WALK
159
63
  req.rev = rev
160
- req.verb = Request::Verb::WATCH
161
- req.path = glob
64
+ req.path = path
65
+ req.offset = offset
66
+ req.limit = limit
67
+ req.valid(&blk)
162
68
 
163
- cancelable(resend(req))
69
+ send(req)
164
70
  end
165
71
 
166
- def monitor(rev, glob)
72
+ def watch(rev, path, &blk)
167
73
  req = Request.new
74
+ req.verb = WATCH
168
75
  req.rev = rev
169
- req.path = glob
170
-
171
- wt = nil
172
- wk = nil
173
-
174
- req.metadef :cancel do
175
- wt.cancel if wt
176
- wk.cancel if wk
177
- end
178
-
179
- wk = walk(rev, glob).valid do |e|
180
- req.emit(:valid, e)
181
- end.error do |e|
182
- req.emit(:error, e)
183
- end.done do
184
- req.emit(:done)
185
-
186
- wt = watch(rev+1, glob).valid do |e|
187
- req.emit(:valid, e)
188
- end.error do |e|
189
- req.emit(:error, e)
190
- end
191
- end
192
-
193
- req
194
- end
195
-
196
- def noop(&blk)
197
- req = Request.new
198
- req.verb = Request::Verb::NOOP
76
+ req.path = path
77
+ req.valid(&blk)
199
78
 
200
79
  send(req)
201
80
  end
202
81
 
203
- # Be careful with this. It is recommended you use #cancel on the Request
204
- # returned to ensure you don't run into a race-condition where you cancel an
205
- # operation you may have thought was something else.
206
- def __cancel__(what)
82
+ def rev(&blk)
207
83
  req = Request.new
208
- req.verb = Request::Verb::CANCEL
209
- req.id = what.tag
210
-
211
- # Hold on to the tag as unavaiable for reuse until the cancel succeeds.
212
- @cbx[what.tag] = nil
213
-
214
- send(req).valid do |res|
215
- # Do not send any more responses from the server to this request.
216
- @cbx.delete(what.tag)
217
- what.emit(:valid, res)
218
- end
219
- end
220
-
221
- def next_tag
222
- tag = MinTag
223
-
224
- while @cbx.has_key?(tag)
225
- tag += 1
226
- if tag > MaxTag
227
- tag = MinTag
228
- end
229
- end
230
-
231
- tag
232
- end
233
-
234
- def send(req)
235
- req.tag ||= next_tag
84
+ req.verb = REV
85
+ req.valid(&blk)
236
86
 
237
- @cbx[req.tag] = req
238
-
239
- debug "sending request: #{req.inspect}"
240
- send_request(req)
241
-
242
- req
87
+ send(req)
243
88
  end
244
89
 
245
- def resend(req)
246
- req.tag ||= next_tag
90
+ def send(req, &onre)
91
+ wr = Request.new(req.to_hash)
92
+ wr = cn.send_request(wr)
247
93
 
248
- wrap = Request.new(req.to_hash)
94
+ req.tag = wr.tag
249
95
 
250
- req.valid do |e|
96
+ wr.valid do |e|
251
97
  if req.offset
252
98
  req.offset += 1
253
99
  end
@@ -260,108 +106,45 @@ module Fraggle
260
106
  req.rev = e.rev
261
107
  end
262
108
 
263
- wrap.emit(:valid, e)
264
- end
265
-
266
- req.error do |err|
267
- if err.disconnected?
268
- send(req)
269
- else
270
- wrap.emit(:error, err)
271
- end
109
+ req.emit(:valid, e)
272
110
  end
273
111
 
274
- req.done do
275
- wrap.emit(:done)
112
+ wr.done do
113
+ req.emit(:done)
276
114
  end
277
115
 
278
- send(req)
279
-
280
- wrap
281
- end
282
-
283
- def cancelable(req)
284
- c = self
285
- can = true
286
-
287
- req.metadef :cancel do
288
- if can
289
- can = false
290
- c.__cancel__(self)
116
+ wr.error do |e|
117
+ case true
118
+ when cn.err? || e.redirect?
119
+ reconnect!
120
+ onre.call if onre
121
+ else
122
+ req.emit(:error, e)
291
123
  end
292
124
  end
293
125
 
294
- req.metadef :canceled? do
295
- !can
296
- end
297
-
298
126
  req
299
127
  end
300
128
 
301
- def post_init
302
- info "successfully connected to #{@addr}"
303
-
304
- res = Response.new(:err_code => Errno::ECONNREFUSED::Errno)
305
- @cbx.values.compact.each do |req|
306
- debug "sending disconnected error to #{req.inspect}"
307
- req.emit(:error, res)
308
- end
309
-
310
- if ! @tracking
311
- trackaddrs
312
- @tracking = true
313
- end
314
- end
315
-
316
- # Track addresses of doozers in a cluster. This will retry
317
- # in the event of a new connection.
318
- def trackaddrs
319
- rev.valid do |v|
320
- monitor(v.rev, "/doozer/slot/*").valid do |e|
321
- if e.value == ""
322
- @addrs.delete(e.path)
323
- else
324
- get(v.rev, "/doozer/info/#{e.value}/addr").valid do |g|
325
- next if g.value == @addr
326
- @addrs[e.path] = g.value
327
- end
328
- end
329
- end.error do |err|
330
- error "address tracking: #{err.inspect} for #{req.inspect}"
331
- end
332
- end
333
- end
334
-
335
- # What happens when a connection is closed for any reason.
336
- def unbind
337
- info "disconnected from #{@addr}"
338
-
339
- _, @addr = @addrs.shift
340
- if ! @addr
341
- raise "No more addrs"
129
+ def resend(req)
130
+ send(req) do
131
+ req.tag = nil
132
+ resend(req)
342
133
  end
343
-
344
- host, port = @addr.split(":")
345
-
346
- info "attempting connection to #{@addr}"
347
-
348
- reconnect(host, port.to_i)
349
-
350
- post_init
351
134
  end
352
135
 
353
- def casify(cas)
354
- case cas
355
- when :missing then Response::Missing
356
- when :clobber then Response::Clobber
357
- else cas
136
+ def reconnect!
137
+ if addr = @addrs.shift
138
+ reconnect(addr)
139
+ else
140
+ raise NoMoreAddrs
358
141
  end
359
142
  end
360
143
 
361
- def genkey
362
- (0...16).map { Nibbles[rand(Nibbles.length)].chr }.join
144
+ def reconnect(addr)
145
+ host, port = addr.split(":")
146
+ @cn = EM.connect(host, port, Fraggle::Connection, @addrs)
363
147
  end
364
148
 
365
149
  end
366
-
367
150
  end