fraggle 0.4.0 → 1.0.0pre

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 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