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 +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/examples/sample_client.rb +24 -0
- data/examples/sample_worker.rb +11 -0
- data/lib/MDP.rb +12 -0
- data/lib/majordomo.rb +13 -0
- data/lib/majordomo/client.rb +79 -0
- data/lib/majordomo/version.rb +3 -0
- data/lib/majordomo/worker.rb +163 -0
- data/majordomo.gemspec +24 -0
- metadata +67 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
data/lib/majordomo.rb
ADDED
@@ -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,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: []
|