zactor 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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