majordomo 0.0.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 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: []