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.
@@ -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