zactor 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.rake_tasks~ +19 -0
- data/.yardopts +1 -0
- data/Gemfile +6 -2
- data/Gemfile.lock +66 -16
- data/Guardfile +12 -0
- data/README.md +147 -73
- data/Rakefile +0 -2
- data/VERSION +1 -0
- data/examples/chat/client.rb +88 -0
- data/examples/chat/server.rb +59 -0
- data/examples/ping/inproc.rb +50 -0
- data/lib/zactor.rb +263 -0
- data/lib/zactor/actor_pub.rb +19 -0
- data/lib/zactor/actor_sub.rb +46 -0
- data/lib/zactor/broker.rb +71 -0
- data/lib/zactor/log_subscriber.rb +25 -0
- data/lib/zactor/message.rb +23 -0
- data/lib/zactor/version.rb +3 -0
- data/spec/lib/actor_spec.rb +136 -0
- data/spec/lib/exchange_spec.rb +81 -0
- data/spec/lib/link_spec.rb +67 -0
- data/spec/lib/zactor_spec.rb +40 -0
- data/spec/spec_helper.rb +1 -1
- data/{ruby-interface.gemspec → zactor.gemspec} +13 -7
- metadata +100 -15
- data/lib/ruby-interface/version.rb +0 -3
- data/lib/ruby-interface/yard.rb +0 -8
- data/lib/ruby_interface.rb +0 -71
- data/spec/ruby_interface_spec.rb +0 -127
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# ruby server.rb PORT
|
4
|
+
# ruby server.rb 8000
|
5
|
+
require 'bundler'
|
6
|
+
ENV['BUNDLE_GEMFILE'] = File.join(File.dirname(__FILE__), '..', '..', 'Gemfile')
|
7
|
+
Bundler.setup(:default)
|
8
|
+
|
9
|
+
require 'zactor'
|
10
|
+
|
11
|
+
class Server
|
12
|
+
include Zactor
|
13
|
+
|
14
|
+
zactor do
|
15
|
+
identity "server"
|
16
|
+
|
17
|
+
event(:new_client) do |o, msg, login|
|
18
|
+
o.new_client msg.sender, login
|
19
|
+
msg.reply :ok
|
20
|
+
end
|
21
|
+
|
22
|
+
event(:client_request) do |o, msg, login|
|
23
|
+
if client = o.clients.detect { |k, v| v == login }
|
24
|
+
msg.reply :ok, client.first
|
25
|
+
else
|
26
|
+
msg.reply :error
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
event(:message) do |o, msg, text|
|
31
|
+
o.send_message msg.sender, text
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_accessor :clients
|
36
|
+
def initialize
|
37
|
+
zactor.init
|
38
|
+
@clients = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def new_client(client, login)
|
42
|
+
@clients[client] = login
|
43
|
+
send_message client, "присоединился"
|
44
|
+
zactor.link client do
|
45
|
+
send_message client, "отсоединился"
|
46
|
+
clients.delete client
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def send_message(from, message)
|
51
|
+
(clients.keys - [from]).each { |c| zactor.send_request c, :message, "#{clients[from]}: #{message}"}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
EM.run do
|
56
|
+
Zactor.start ARGV[0]#, :debug => true
|
57
|
+
Server.new
|
58
|
+
puts "Server started"
|
59
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler"
|
3
|
+
|
4
|
+
ENV['BUNDLE_GEMFILE'] = File.join(File.dirname(__FILE__), '..', '..', 'Gemfile')
|
5
|
+
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
|
8
|
+
require 'zactor'
|
9
|
+
require 'eventmachine'
|
10
|
+
|
11
|
+
class A
|
12
|
+
include Zactor
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
zactor.init
|
16
|
+
ping Zactor.get_actor("b")
|
17
|
+
end
|
18
|
+
|
19
|
+
def ping(actor)
|
20
|
+
puts "Ping!"
|
21
|
+
zactor.send_request actor, :ping do |res|
|
22
|
+
puts res
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
class B
|
29
|
+
include Zactor
|
30
|
+
|
31
|
+
zactor do
|
32
|
+
identity "b"
|
33
|
+
|
34
|
+
event(:ping) do |o, msg|
|
35
|
+
msg.reply "Pong!"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
zactor.init
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
EM.run do
|
46
|
+
Zactor.start 8000
|
47
|
+
|
48
|
+
a = A.new
|
49
|
+
b = B.new
|
50
|
+
end
|
data/lib/zactor.rb
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'ruby_interface'
|
3
|
+
require 'active_support/all'
|
4
|
+
require 'ffi-rzmq'
|
5
|
+
require 'em-zeromq'
|
6
|
+
require 'bson'
|
7
|
+
|
8
|
+
module Zactor
|
9
|
+
extend ActiveSupport::Autoload
|
10
|
+
|
11
|
+
autoload :Broker
|
12
|
+
autoload :ActorSub
|
13
|
+
autoload :ActorPub
|
14
|
+
autoload :Message
|
15
|
+
require 'zactor/log_subscriber'
|
16
|
+
|
17
|
+
mattr_accessor :stub
|
18
|
+
mattr_accessor :zmq, :logger
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def broker
|
22
|
+
@broker
|
23
|
+
end
|
24
|
+
|
25
|
+
def broker_port
|
26
|
+
@broker_port
|
27
|
+
end
|
28
|
+
|
29
|
+
def host
|
30
|
+
@host
|
31
|
+
end
|
32
|
+
|
33
|
+
def start(broker_port, params = {})
|
34
|
+
self.zmq ||= EM::ZeroMQ::Context.new(1)
|
35
|
+
self.logger ||= Logger.new(STDOUT).tap { |o| o.level = params[:debug] ? Logger::DEBUG : Logger::INFO }
|
36
|
+
@host = "#{params.delete(:host) || '0.0.0.0'}:#{broker_port}"
|
37
|
+
@broker_port = broker_port
|
38
|
+
@params = params
|
39
|
+
|
40
|
+
logger.info "Starting Zactor"
|
41
|
+
@broker = Broker.new :balancer => params[:balancer]
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_actor(pid, options = {})
|
45
|
+
h = options[:host] || host
|
46
|
+
{ 'identity' => pid, 'host' => h }
|
47
|
+
end
|
48
|
+
|
49
|
+
def register(zactor)
|
50
|
+
@zactors ||= {}
|
51
|
+
@zactors[zactor] = true
|
52
|
+
end
|
53
|
+
|
54
|
+
def deregister(zactor)
|
55
|
+
@zactors.delete zactor
|
56
|
+
end
|
57
|
+
|
58
|
+
def finish
|
59
|
+
@zactors.keys.each(&:finish)
|
60
|
+
@broker.finish
|
61
|
+
end
|
62
|
+
|
63
|
+
def clear
|
64
|
+
@zactors = {}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
module ZMQMEssages
|
70
|
+
class ZMQMessage
|
71
|
+
attr_accessor :res
|
72
|
+
def initialize(&blk)
|
73
|
+
@res = []
|
74
|
+
blk.call self
|
75
|
+
end
|
76
|
+
|
77
|
+
def str(str)
|
78
|
+
@res << ZMQ::Message.new.tap { |o| o.copy_in_string str.to_s }
|
79
|
+
end
|
80
|
+
|
81
|
+
def mes(message)
|
82
|
+
@res << message
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def send_messages(messages)
|
87
|
+
last = messages[-1]
|
88
|
+
messages[0...-1].each do |mes|
|
89
|
+
sent = @socket.send mes, ZMQ::NOBLOCK | ZMQ::SNDMORE
|
90
|
+
break unless sent
|
91
|
+
end
|
92
|
+
sent = @socket.send last, ZMQ::NOBLOCK
|
93
|
+
unless sent
|
94
|
+
Zactor.logger.info "[Zactor] Error while sending messages"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def messages(&blk)
|
99
|
+
ZMQMessage.new(&blk).res
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
extend RubyInterface
|
105
|
+
interface :zactor do
|
106
|
+
interfaced do
|
107
|
+
self.zactor do
|
108
|
+
event(:finish) { |o| o.finish }
|
109
|
+
event(:link) do |o, msg|
|
110
|
+
o.zactor.linked msg
|
111
|
+
# msg.reply :ok
|
112
|
+
end
|
113
|
+
event(:link_ping) do |o, msg|
|
114
|
+
msg.reply :pong
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
include ZMQMEssages
|
120
|
+
|
121
|
+
class_attribute :identity_val
|
122
|
+
class_attribute :events
|
123
|
+
self.events = {}
|
124
|
+
class << self
|
125
|
+
def identity(val)
|
126
|
+
self.identity_val = val
|
127
|
+
end
|
128
|
+
|
129
|
+
def event(name, &clb)
|
130
|
+
self.events = self.events.merge name.to_sym => clb
|
131
|
+
end
|
132
|
+
end
|
133
|
+
attr_accessor :actor, :identity
|
134
|
+
attr_accessor :pubs
|
135
|
+
|
136
|
+
# Инициализация, подписывается на сообщения для себя, создает сокет для отправки локальных сообщений
|
137
|
+
def init
|
138
|
+
@actor = Zactor.get_actor @identity || self.class.identity_val || "actor.#{owner.object_id}-#{Zactor.host}"
|
139
|
+
Zactor.register self
|
140
|
+
return if Zactor.stub
|
141
|
+
@sub = make_sub
|
142
|
+
@callbacks, @timeouts = {}, {}
|
143
|
+
@pubs = {}
|
144
|
+
@pubs["0.0.0.0:#{Zactor.broker_port}"] = make_pub "inproc://zactor_broker_sub"
|
145
|
+
end
|
146
|
+
|
147
|
+
# Закрываем все соедниения и чистим сисьему. Обязательно нужно делать, когда объект перестает существовать
|
148
|
+
def finish
|
149
|
+
Zactor.deregister self
|
150
|
+
return if Zactor.stub
|
151
|
+
if @linked
|
152
|
+
@linked.each do |link|
|
153
|
+
link.reply :finish
|
154
|
+
end
|
155
|
+
end
|
156
|
+
@finished = true
|
157
|
+
@sub.close
|
158
|
+
@pubs.values.each(&:close)
|
159
|
+
end
|
160
|
+
|
161
|
+
def make_pub(endpoint)
|
162
|
+
Zactor::ActorPub.new self, endpoint
|
163
|
+
end
|
164
|
+
|
165
|
+
def make_sub
|
166
|
+
Zactor::ActorSub.new self
|
167
|
+
end
|
168
|
+
|
169
|
+
def send_request(actor, event, *args, &clb)
|
170
|
+
return if @finished || Zactor.stub
|
171
|
+
@callbacks[clb.object_id.to_s] = clb if clb
|
172
|
+
send_to actor, messages { |m|
|
173
|
+
m.str 'request'
|
174
|
+
m.str "#{clb ? clb.object_id : ''}"
|
175
|
+
m.str event
|
176
|
+
m.str BSON.serialize({ 'args' => args })
|
177
|
+
}
|
178
|
+
@last_callback = clb.object_id.to_s
|
179
|
+
self
|
180
|
+
end
|
181
|
+
|
182
|
+
def timeout(secs, &clb)
|
183
|
+
raise "Only for requests" unless @last_callback
|
184
|
+
@timeouts[@last_callback] = EM.add_timer(secs, &clb)
|
185
|
+
end
|
186
|
+
|
187
|
+
def send_reply(actor, callback_id, *args)
|
188
|
+
return if @finished || Zactor.stub
|
189
|
+
Zactor.logger.debug "Zactor: send reply"
|
190
|
+
send_to actor, messages { |m|
|
191
|
+
m.str 'reply'
|
192
|
+
m.str callback_id
|
193
|
+
m.str BSON.serialize({ 'args' => args })
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
def send_to(actor, mes = [])
|
198
|
+
pub = @pubs[actor['host']] ||= make_pub("tcp://#{actor['host']}")
|
199
|
+
pub.send_messages(messages { |m|
|
200
|
+
m.str actor['identity']
|
201
|
+
m.str bson_actor
|
202
|
+
} + mes)
|
203
|
+
end
|
204
|
+
|
205
|
+
def link(actor, &clb)
|
206
|
+
Zactor.logger.debug { "Zactor: link #{actor} with #{self.actor}"}
|
207
|
+
send_request actor, :link do
|
208
|
+
clb.call
|
209
|
+
end
|
210
|
+
link_ping actor, &clb
|
211
|
+
self
|
212
|
+
end
|
213
|
+
|
214
|
+
def link_ping(actor, &clb)
|
215
|
+
EM.add_timer(5) do
|
216
|
+
unless @finished
|
217
|
+
send_request(actor, :link_ping) do
|
218
|
+
link_ping actor, &clb
|
219
|
+
end.timeout(5) do
|
220
|
+
clb.call
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def linked(msg)
|
227
|
+
Zactor.logger.debug { "Zactor: linked #{actor} with #{msg.sender}"}
|
228
|
+
@linked ||= []
|
229
|
+
@linked << msg
|
230
|
+
end
|
231
|
+
|
232
|
+
def receive_reply(callback_id, *args)
|
233
|
+
Zactor.logger.debug "Zactor: receive reply"
|
234
|
+
if (callback = @callbacks[callback_id])
|
235
|
+
if timeout = @timeouts[callback_id]
|
236
|
+
EM.cancel_timer timeout
|
237
|
+
end
|
238
|
+
callback.call(*args)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def receive_request(sender, event_name, callback_id, *args)
|
243
|
+
Zactor.logger.debug "Zactor: receive request"
|
244
|
+
mes = Message.new self, :sender => sender, :callback_id => callback_id, :args => args
|
245
|
+
if (event = self.class.events[event_name.to_sym])
|
246
|
+
event.call owner, mes, *args
|
247
|
+
else
|
248
|
+
raise "Undefined event #{event_name}"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def bson_actor
|
253
|
+
@bson_actor ||= BSON.serialize(@actor).to_s
|
254
|
+
end
|
255
|
+
|
256
|
+
|
257
|
+
def inspect
|
258
|
+
"<#Zactor Interface for #{owner.inspect}"
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Zactor
|
2
|
+
class ActorPub
|
3
|
+
include ZMQMEssages
|
4
|
+
|
5
|
+
attr_accessor :actor
|
6
|
+
def initialize(actor, endpoint)
|
7
|
+
Zactor.logger.debug "ZactorPub (#{actor.actor}): starting"
|
8
|
+
@actor = actor
|
9
|
+
@connection = Zactor.zmq.connect ZMQ::PUB, endpoint, self
|
10
|
+
@socket = @connection.socket
|
11
|
+
end
|
12
|
+
|
13
|
+
def close
|
14
|
+
@connection.unbind
|
15
|
+
rescue
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Zactor
|
2
|
+
class ActorSub
|
3
|
+
attr_accessor :actor
|
4
|
+
def initialize(actor)
|
5
|
+
Zactor.logger.debug "ZactorSub (#{actor.actor}): starting"
|
6
|
+
@actor = actor
|
7
|
+
@connection = Zactor.zmq.connect ZMQ::SUB, "inproc://zactor_broker_pub", self
|
8
|
+
@connection.subscribe actor.actor['identity']
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_readable(socket, messages)
|
12
|
+
@connection.notify_readable = true
|
13
|
+
Zactor.logger.debug "ZactorSub for #{actor.actor}: Messages!"
|
14
|
+
to = messages.shift
|
15
|
+
sender = messages.shift
|
16
|
+
case messages.shift.copy_out_string
|
17
|
+
when "reply"
|
18
|
+
reply messages
|
19
|
+
when "request"
|
20
|
+
request sender, messages
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def request(sender_mes, messages)
|
25
|
+
Zactor.logger.debug "ZactorSub for #{actor.actor}: request!"
|
26
|
+
sender = BSON.deserialize(sender_mes.copy_out_string)
|
27
|
+
callback_id = messages[0].copy_out_string
|
28
|
+
event = messages[1].copy_out_string
|
29
|
+
args = BSON.deserialize(messages[2].copy_out_string)['args']
|
30
|
+
actor.receive_request sender, event, callback_id, *args
|
31
|
+
end
|
32
|
+
|
33
|
+
def reply(messages)
|
34
|
+
Zactor.logger.debug "ZactorSub for #{actor.actor}: reply!"
|
35
|
+
callback_id = messages[0].copy_out_string
|
36
|
+
if callback_id != ''
|
37
|
+
actor.receive_reply callback_id, *BSON.deserialize(messages[1].copy_out_string)['args']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def close
|
42
|
+
@connection.unbind
|
43
|
+
rescue
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Zactor
|
2
|
+
class BrokerIn
|
3
|
+
attr_accessor :broker, :params
|
4
|
+
def initialize(broker, params = {})
|
5
|
+
@broker = broker
|
6
|
+
@params = params
|
7
|
+
init_connection
|
8
|
+
end
|
9
|
+
|
10
|
+
def on_readable(socket, messages)
|
11
|
+
Zactor.logger.debug "Broker: messages"
|
12
|
+
@broker.pub.request messages
|
13
|
+
end
|
14
|
+
|
15
|
+
def close
|
16
|
+
@connection.unbind
|
17
|
+
rescue
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class BrokerPub
|
22
|
+
include ZMQMEssages
|
23
|
+
def initialize(broker)
|
24
|
+
@broker = broker
|
25
|
+
@connection = Zactor.zmq.bind ZMQ::PUB, "inproc://zactor_broker_pub", self
|
26
|
+
@socket = @connection.socket
|
27
|
+
end
|
28
|
+
|
29
|
+
def request(messages)
|
30
|
+
Zactor.logger.debug { "Broker: request #{messages.map(&:copy_out_string).inspect}" }
|
31
|
+
send_messages messages
|
32
|
+
end
|
33
|
+
|
34
|
+
def close
|
35
|
+
@connection.unbind
|
36
|
+
rescue
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class BrokerSub < BrokerIn
|
41
|
+
def init_connection
|
42
|
+
Zactor.logger.info "Starting sub broker tcp://0.0.0.0:#{Zactor.broker_port}"
|
43
|
+
@connection = Zactor.zmq.bind ZMQ::SUB, "tcp://0.0.0.0:#{Zactor.broker_port}", self
|
44
|
+
@connection.bind "inproc://zactor_broker_sub"
|
45
|
+
@connection.subscribe ''
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class BrokerPull < BrokerIn
|
50
|
+
def init_connection
|
51
|
+
Zactor.logger.info "Starting pull broker tcp://#{params[:host]}"
|
52
|
+
@connection = Zactor.zmq.connect ZMQ::PULL, "tcp://#{params[:host]}", self
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Broker
|
57
|
+
attr_accessor :sub, :pub
|
58
|
+
def initialize(params = {})
|
59
|
+
Zactor.logger.info "Broker: starting"
|
60
|
+
@pub = BrokerPub.new self
|
61
|
+
@subs = []
|
62
|
+
@subs << BrokerSub.new(self)
|
63
|
+
@subs << BrokerPull.new(self, :host => params[:balancer]) if params[:balancer]
|
64
|
+
end
|
65
|
+
|
66
|
+
def finish
|
67
|
+
@pub.close
|
68
|
+
@subs.each(&:close)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|