majordomo 0.0.1

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 rb-mdp.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+
2
+ # Majordomo Protocol client example. Uses the mdcli API to hide all MDP aspects
3
+ # Author : Mark Wotton <mark@ninjablocks.com>, modelled on python
4
+ # version by Min RK <benjaminrk@gmail.com>
5
+
6
+ require 'majordomo'
7
+
8
+ client = Majordomo::Client.new("tcp://localhost:5773", true)
9
+ count = 0
10
+ while count < 100000
11
+ request = "Hello world"
12
+ begin
13
+ warn "sending"
14
+ reply = client.mdp_send("echo", request)
15
+ rescue => e
16
+ warn e
17
+ warn e.backtrace
18
+ break
19
+ end
20
+ break unless reply
21
+ raise "bad response: got #{reply}" unless reply == ["Hello world"]
22
+ count += 1
23
+ puts "#{count} requests/replies processed"
24
+ end
@@ -0,0 +1,11 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'majordomo'
3
+
4
+ worker = Majordomo::Worker.new "tcp://localhost:5773", "echo", true
5
+ reply = nil
6
+ while true
7
+ request = worker.recv(reply)
8
+ break unless request
9
+ puts "got a request: #{request}"
10
+ reply = request # Echo is complex… :-)
11
+ end
data/lib/MDP.rb ADDED
@@ -0,0 +1,12 @@
1
+
2
+ module MDP
3
+ W_READY = "\x01"
4
+ W_REQUEST = "\x02"
5
+ W_REPLY = "\x03"
6
+ W_HEARTBEAT = "\x04"
7
+ W_WORKER = 'MDPW01'
8
+
9
+ C_CLIENT = 'MDPC01'
10
+
11
+
12
+ end
data/lib/majordomo.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "majordomo/version"
2
+
3
+ require 'majordomo/worker'
4
+ require 'majordomo/client'
5
+ module Majordomo
6
+ W_READY = "\x01"
7
+ W_REQUEST = "\x02"
8
+ W_REPLY = "\x03"
9
+ W_HEARTBEAT = "\x04"
10
+ W_WORKER = 'MDPW01'
11
+
12
+ C_CLIENT = 'MDPC01'
13
+ end
@@ -0,0 +1,79 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Majordomo Protocol Client API, Python version.
3
+
4
+ Implements the MDP/Worker spec at http:#rfc.zeromq.org/spec:7.
5
+
6
+ Author: Min RK <benjaminrk@gmail.com>
7
+ Based on Java example by Arkadiusz Orzechowski
8
+ """
9
+
10
+ require 'ffi-rzmq'
11
+
12
+ class Majordomo::Client
13
+ def initialize broker, verbose=false
14
+ @broker = broker
15
+ @verbose = verbose
16
+ @ctx = ZMQ::Context.new
17
+ @poller = ZMQ::Poller.new
18
+ @retries = 3
19
+ @timeout = 2500
20
+ reconnect_to_broker()
21
+ end
22
+
23
+ # Connect or reconnect to broker
24
+ def reconnect_to_broker
25
+ if @client
26
+ @poller.unregister(@client)
27
+ @client.close()
28
+ end
29
+ @client = @ctx.socket(ZMQ::REQ)
30
+ @client.setsockopt(ZMQ::LINGER, 0)
31
+ @client.connect(@broker)
32
+ @poller.register(@client, ZMQ::POLLIN)
33
+ warn "I: connecting to broker at #{@broker}…" if @verbose
34
+ end
35
+
36
+ # Send request to broker and get reply by hook or crook.
37
+ # Takes ownership of request message and destroys it when sent.
38
+ # Returns the reply message or None if there was no reply.
39
+ def mdp_send service, request
40
+ request = [Majordomo::C_CLIENT, service, request]
41
+ warn "I: send request to '#{service}' service: #{request}" if @verbose
42
+ reply = nil
43
+ retries = @retries
44
+ while retries > 0
45
+ warn request
46
+ @client.send_strings(request)
47
+ begin
48
+ items = @poller.poll(@timeout)
49
+ rescue => e
50
+ break # interrupted
51
+ end
52
+
53
+ if items
54
+ msg = []
55
+ @client.recv_strings(msg)
56
+ warn "I: received reply: #{msg}" if @verbose
57
+
58
+ # Don't try to handle errors, just assert noisily
59
+ raise "expected at least three parts" unless msg.length >= 3
60
+ header, reply_service, *rest = *msg
61
+ raise "bad header #{header}" unless Majordomo::C_CLIENT == header
62
+ raise "bad service #{reply_service}" unless service == reply_service
63
+
64
+ reply = rest
65
+ break
66
+ else
67
+ if retries
68
+ warn "W: no reply, reconnecting…"
69
+ reconnect_to_broker
70
+ else
71
+ warn "W: permanent error, abandoning"
72
+ break
73
+ end
74
+ retries -= 1
75
+ end
76
+ end
77
+ return reply
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ module Majordomo
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,163 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Majordomo Protocol Worker API, Ruby version
3
+
4
+ # Implements the MDP/Worker spec at http:#rfc.zeromq.org/spec:7.
5
+
6
+ # Author: Mark Wotton <mark@ninjablocks.com>
7
+ # Based on Python example by Min RK <benjaminrk@gmail.com>
8
+
9
+
10
+ require 'ffi-rzmq'
11
+
12
+ class Majordomo::Worker
13
+ HEARTBEAT_LIVENESS = 3 # 3-5 is reasonable
14
+ def initialize(broker, service, verbose=false)
15
+ @heartbeat_at = 0 # When to send HEARTBEAT (relative to time.time(), so in seconds)
16
+ @liveness = 0 # How many attempts left
17
+ @heartbeat = 2500 # Heartbeat delay, msecs
18
+ @reconnect = 2500 # Reconnect delay, msecs
19
+
20
+ # Internal state
21
+ @expect_reply = false # false only at start
22
+
23
+ @timeout = 2500 # poller timeout
24
+
25
+ # Return address, if any
26
+ @reply_to = nil
27
+
28
+
29
+ @broker = broker
30
+ @service = service
31
+ @verbose = verbose
32
+ @ctx = ZMQ::Context.new
33
+ @poller = ZMQ::Poller.new
34
+
35
+ reconnect_to_broker
36
+ end
37
+
38
+ def reconnect_to_broker
39
+ if @worker
40
+ @poller.unregister(@worker)
41
+ @worker.close()
42
+ end
43
+ @worker = @ctx.socket(ZMQ::DEALER)
44
+
45
+ @worker.setsockopt(ZMQ::LINGER, 0)
46
+ @worker.connect @broker
47
+ @poller.register @worker, ZMQ::POLLIN
48
+ warn "I: connecting to broker at #{@broker}…" if @verbose
49
+ # Register service with broker
50
+ send_to_broker Majordomo::W_READY, @service, []
51
+
52
+ # If liveness hits zero, queue is considered disconnected
53
+ @liveness = HEARTBEAT_LIVENESS
54
+ @heartbeat_at = Time.now + 1e-3 * @heartbeat
55
+ end
56
+
57
+ def send_to_broker command, option=nil, msg=[]
58
+ # @worker.send_string '', ZMQ::SNDMORE
59
+ # ffi-rzmq is a bit weird about copying in raw bytes. need to
60
+ # explode the int first
61
+
62
+ # @worker.send_msg
63
+ # @worker.send_string command, ZMQ::SNDMORE
64
+
65
+ msg = [option] + msg if option
66
+
67
+
68
+ msg = ['', Majordomo::W_WORKER, command] + msg
69
+ if @verbose
70
+ warn "I: sending #{command} to broker"
71
+ end
72
+ @worker.send_strings msg
73
+ end
74
+
75
+ def recv(reply=nil)
76
+ # Format and send the reply if we were provided one
77
+ raise "bad reply" unless (reply or not @expect_reply)
78
+
79
+ if reply
80
+ raise "not expecting reply" unless @reply_to
81
+ warn reply.inspect
82
+ warn reply.class
83
+ reply = [@reply_to, ''] + reply
84
+ send_to_broker Majordomo::W_REPLY, nil, reply
85
+
86
+ @expect_reply = true
87
+ end
88
+ while true
89
+ # Poll socket for a reply, with timeout
90
+ begin
91
+ items = @poller.poll(@timeout)
92
+ rescue => e
93
+ break # Interrupted
94
+ end
95
+ if items
96
+ if (result = handle_message)
97
+
98
+ warn "result from handle_message: |#{result}|"
99
+ return result
100
+ end
101
+ else
102
+ reduce_liveness
103
+ end
104
+ # Send HEARTBEAT if it's time
105
+ if Time.now > @heartbeat_at
106
+ send_to_broker(Majordomo::W_HEARTBEAT)
107
+ @heartbeat_at = Time.now + 1e-3*@heartbeat
108
+ end
109
+ end
110
+ logging.warn("W: interrupt received, killing worker…")
111
+ return nil
112
+ end
113
+
114
+ def handle_message
115
+ msg = []
116
+ @worker.recv_strings(msg)
117
+ warn "I: received message from broker: #{msg} " if @verbose
118
+
119
+ @liveness = @HEARTBEAT_LIVENESS
120
+ # Don't try to handle errors, just assert noisily
121
+ raise "expected at least 3 parts" unless msg.length >= 3
122
+ warn "full message: #{msg}"
123
+ empty, header, command, *rest = *msg
124
+
125
+ raise "no null frame" unless empty == ''
126
+ raise "bad protocol" unless header == Majordomo::W_WORKER
127
+ case command
128
+ when Majordomo::W_REQUEST
129
+ warn "request body: #{rest}"
130
+ # We should pop and save as many addresses as there are
131
+ # up to a null part, but for now, just save one…
132
+ raise "bad message" unless rest.length >= 2
133
+ @reply_to, empty, *body = *rest
134
+ warn "replyto = #{@reply_to}, body=#{body}"
135
+ # pop empty
136
+ raise "no null frame" unless empty == ''
137
+
138
+ return body # We have a request to process
139
+ when Majordomo::W_HEARTBEAT
140
+ # Do nothing for heartbeats
141
+ when Majordomo::W_DISCONNECT
142
+ @reconnect_to_broker
143
+ else
144
+ warn "E: invalid input message: #{msg}"
145
+ end
146
+ return nil # only return message if we found one
147
+ end
148
+
149
+ def reduce_liveness
150
+ @liveness -= 1
151
+ if @liveness == 0
152
+ if @verbose
153
+ logging.warn("W: disconnected from broker - retrying…")
154
+ end
155
+ begin
156
+ time.sleep(1e-3*@reconnect)
157
+ rescue => e # KeyboardInterrupt:
158
+ return nil
159
+ end
160
+ reconnect_to_broker
161
+ end
162
+ end
163
+ end
data/majordomo.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "majordomo/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "majordomo"
7
+ s.version = Majordomo::VERSION
8
+ s.authors = ["Mark Wotton"]
9
+ s.email = ["mark@ninjablocks.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Majordomo for Ruby}
12
+ s.description = %q{Majordomo for Ruby}
13
+
14
+ s.rubyforge_project = "majordomo"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ s.add_runtime_dependency "ffi-rzmq"
24
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: majordomo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mark Wotton
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ffi-rzmq
16
+ requirement: &14326140 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *14326140
25
+ description: Majordomo for Ruby
26
+ email:
27
+ - mark@ninjablocks.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - .gitignore
33
+ - Gemfile
34
+ - Rakefile
35
+ - examples/sample_client.rb
36
+ - examples/sample_worker.rb
37
+ - lib/MDP.rb
38
+ - lib/majordomo.rb
39
+ - lib/majordomo/client.rb
40
+ - lib/majordomo/version.rb
41
+ - lib/majordomo/worker.rb
42
+ - majordomo.gemspec
43
+ homepage: ''
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project: majordomo
63
+ rubygems_version: 1.8.17
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Majordomo for Ruby
67
+ test_files: []