zactor 0.0.5 → 0.0.6
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/.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
|