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
data/.rake_tasks~
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
build
|
2
|
+
console[script]
|
3
|
+
gemcutter:release
|
4
|
+
gemspec
|
5
|
+
gemspec:debug
|
6
|
+
gemspec:generate
|
7
|
+
gemspec:release
|
8
|
+
gemspec:validate
|
9
|
+
git:release
|
10
|
+
install
|
11
|
+
rcov
|
12
|
+
release
|
13
|
+
spec
|
14
|
+
version
|
15
|
+
version:bump:major
|
16
|
+
version:bump:minor
|
17
|
+
version:bump:patch
|
18
|
+
version:write
|
19
|
+
yard
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-m markdown -e ruby-interface/yard
|
data/Gemfile
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
3
|
group :development do
|
4
|
-
gem "rspec", "
|
4
|
+
gem "rspec", ">= 2"
|
5
5
|
gem "yard", "~> 0.6.0"
|
6
6
|
gem "bundler", "~> 1.0.0"
|
7
|
-
gem "jeweler", "~> 1.5.2"
|
8
7
|
gem "rcov", ">= 0"
|
9
8
|
gem "bluecloth"
|
10
9
|
gem "rr"
|
10
|
+
gem "guard-rspec"
|
11
|
+
gem "growl"
|
12
|
+
gem "ruby-debug19"
|
13
|
+
gem "em-spec", :git => "https://github.com/mloughran/em-spec.git", :branch => 'rspec2'
|
11
14
|
end
|
12
15
|
|
16
|
+
|
13
17
|
gemspec
|
data/Gemfile.lock
CHANGED
@@ -1,33 +1,80 @@
|
|
1
|
+
GIT
|
2
|
+
remote: https://github.com/mloughran/em-spec.git
|
3
|
+
revision: fc888b289a424fa93848ffdac3dc4b24fdd34950
|
4
|
+
branch: rspec2
|
5
|
+
specs:
|
6
|
+
em-spec (0.2.2)
|
7
|
+
|
1
8
|
PATH
|
2
9
|
remote: .
|
3
10
|
specs:
|
4
|
-
zactor (0.0.
|
11
|
+
zactor (0.0.6)
|
5
12
|
activesupport (> 0.1)
|
6
|
-
|
13
|
+
bson (> 0.1)
|
14
|
+
bson_ext (> 0.1)
|
15
|
+
em-zeromq (> 0.1)
|
16
|
+
ffi (> 0.1)
|
17
|
+
ffi-rzmq (> 0.1)
|
18
|
+
ruby-interface (> 0)
|
7
19
|
|
8
20
|
GEM
|
9
21
|
remote: http://rubygems.org/
|
10
22
|
specs:
|
11
23
|
activesupport (3.0.7)
|
24
|
+
archive-tar-minitar (0.5.2)
|
12
25
|
bluecloth (2.1.0)
|
26
|
+
bson (1.2.4)
|
27
|
+
bson_ext (1.2.4)
|
28
|
+
columnize (0.3.2)
|
29
|
+
configuration (1.2.0)
|
13
30
|
diff-lcs (1.1.2)
|
14
|
-
|
31
|
+
em-zeromq (0.2.1)
|
32
|
+
eventmachine (>= 1.0.0.beta.3)
|
33
|
+
ffi-rzmq (>= 0.7.2)
|
34
|
+
eventmachine (1.0.0.beta.3)
|
35
|
+
ffi (1.0.7)
|
36
|
+
rake (>= 0.8.7)
|
37
|
+
ffi-rzmq (0.7.2)
|
38
|
+
growl (1.0.3)
|
39
|
+
guard (0.3.0)
|
40
|
+
open_gem (~> 1.4.2)
|
41
|
+
thor (~> 0.14.6)
|
42
|
+
guard-rspec (0.2.0)
|
43
|
+
guard (>= 0.2.2)
|
15
44
|
i18n (0.5.0)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
45
|
+
launchy (0.3.7)
|
46
|
+
configuration (>= 0.0.5)
|
47
|
+
rake (>= 0.8.1)
|
48
|
+
linecache19 (0.5.11)
|
49
|
+
ruby_core_source (>= 0.1.4)
|
50
|
+
open_gem (1.4.2)
|
51
|
+
launchy (~> 0.3.5)
|
20
52
|
rake (0.8.7)
|
21
53
|
rcov (0.9.9)
|
22
54
|
rr (1.0.2)
|
23
|
-
rspec (2.
|
24
|
-
rspec-core (~> 2.
|
25
|
-
rspec-expectations (~> 2.
|
26
|
-
rspec-mocks (~> 2.
|
27
|
-
rspec-core (2.
|
28
|
-
rspec-expectations (2.
|
55
|
+
rspec (2.5.0)
|
56
|
+
rspec-core (~> 2.5.0)
|
57
|
+
rspec-expectations (~> 2.5.0)
|
58
|
+
rspec-mocks (~> 2.5.0)
|
59
|
+
rspec-core (2.5.1)
|
60
|
+
rspec-expectations (2.5.0)
|
29
61
|
diff-lcs (~> 1.1.2)
|
30
|
-
rspec-mocks (2.
|
62
|
+
rspec-mocks (2.5.0)
|
63
|
+
ruby-debug-base19 (0.11.24)
|
64
|
+
columnize (>= 0.3.1)
|
65
|
+
linecache19 (>= 0.5.11)
|
66
|
+
ruby_core_source (>= 0.1.4)
|
67
|
+
ruby-debug19 (0.11.6)
|
68
|
+
columnize (>= 0.3.1)
|
69
|
+
linecache19 (>= 0.5.11)
|
70
|
+
ruby-debug-base19 (>= 0.11.19)
|
71
|
+
ruby-interface (0.0.2)
|
72
|
+
activesupport (> 0.1)
|
73
|
+
i18n (> 0.1)
|
74
|
+
ruby-interface
|
75
|
+
ruby_core_source (0.1.4)
|
76
|
+
archive-tar-minitar (>= 0.5.2)
|
77
|
+
thor (0.14.6)
|
31
78
|
yard (0.6.5)
|
32
79
|
|
33
80
|
PLATFORMS
|
@@ -36,9 +83,12 @@ PLATFORMS
|
|
36
83
|
DEPENDENCIES
|
37
84
|
bluecloth
|
38
85
|
bundler (~> 1.0.0)
|
39
|
-
|
86
|
+
em-spec!
|
87
|
+
growl
|
88
|
+
guard-rspec
|
40
89
|
rcov
|
41
90
|
rr
|
42
|
-
rspec (
|
91
|
+
rspec (>= 2)
|
92
|
+
ruby-debug19
|
43
93
|
yard (~> 0.6.0)
|
44
94
|
zactor!
|
data/Guardfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(/^spec\/(.*)_spec.rb/)
|
6
|
+
watch(/^lib\/(.*)\.rb/) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch(/^spec\/spec_helper.rb/) { "spec" }
|
8
|
+
|
9
|
+
# watch(/^lib\/zactor.rb/) { "spec/lib/actor_spec.rb" }
|
10
|
+
|
11
|
+
watch(/^app\/(.*)\.rb/) { |m| "spec/app/#{m[1]}_spec.rb" }
|
12
|
+
end
|
data/README.md
CHANGED
@@ -1,93 +1,167 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
Что это
|
2
|
+
=======
|
3
|
+
Zactor позволяет любой руби-объект научить общаться с другими объектами посредством отправки и получения сообщений. При этом неважно где именно расположен объект, в том же процессе, в соседнем, или на другой физической машине.
|
3
4
|
|
4
|
-
|
5
|
+
Zactor использует zmq как бекенд для сообщений, eventmachine и em-zeromq для асинхронной работы с zmq, RubyInterface для описания себя, BSON для сериализации данных.
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def set_parent(parent)
|
22
|
-
parent.tree.childs << owner
|
23
|
-
@parent = parent
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
7
|
+
Каждый zactor-объект имеет свой identity, который генерируется автоматически, либо задается вручную и постоянен. Совокупность identity, host и port на которых был рожден этот объект, это достаточная информация для того что бы отправить этому объекту сообщение из другого объекта. Выглядит примерно так:
|
8
|
+
|
9
|
+
zactor.actor =>
|
10
|
+
{"identity"=>"actor.2154247120-0.0.0.0:8000", "host"=>"0.0.0.0:8000"}
|
11
|
+
|
12
|
+
Ипользование
|
13
|
+
============
|
14
|
+
|
15
|
+
Для начала нужно стартануть Zactor.
|
16
|
+
|
17
|
+
Zactor.start 8000
|
18
|
+
|
19
|
+
Процесс забиндится на 0.0.0.0:8000, через эту точку будет происходить общение между zactor-процессами. Стартоваться должен в запущенном EM-контексте.
|
20
|
+
|
21
|
+
В каждый zactor-активный класс нужно делать include Zactor, после чего у класса и его экземпляров для доступа к функциями Zactor появится метод zactor. После создания объекта нужно выполнить zactor.init
|
27
22
|
|
28
23
|
class A
|
29
|
-
include
|
24
|
+
include Zactor
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
zactor.init
|
28
|
+
end
|
30
29
|
end
|
30
|
+
|
31
|
+
Для отправки сообщений другому объекту нам нужно знать его идентификатор. Идентификатор можно получить тремя способами:
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
* Непосрдественной передачей. При инициализации или в любом другом месте, это исключительно внутренняя логика приложения. Идентификатор объекта можно получить вызвав zactor.actor
|
34
|
+
* При получении сообщения. В сообщении всегда содержится информация об отправителе
|
35
|
+
* Если объект имеет заранее известный identity, то мы можем получить его полный идентификатор вызвав Zactor.get_actor с identity и хостом, на котором он запущен
|
36
|
+
|
37
|
+
actor = Zactor.get_actor "broker", :host => "0.0.0.0:8001"
|
38
|
+
|
39
|
+
Получив идентификатор можно отправлять ему сообщения
|
40
|
+
|
41
|
+
zactor.send_request actor, :show_me, :boobs
|
36
42
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
А при использовании методов относящихся к интерфейсу мы явно видим к какому же интерфейсу он относится. Всем профит!
|
43
|
+
|
44
|
+
Каждый класс может определять какие именно ивенты он может получать и что с ними делать
|
45
|
+
|
46
|
+
include Zactor
|
42
47
|
|
43
|
-
|
48
|
+
zactor do
|
49
|
+
event(:show_me) do |o, msg, what|
|
50
|
+
case what
|
51
|
+
when :boobs
|
52
|
+
do_it
|
53
|
+
else
|
54
|
+
do_smth_else
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Рассмотрим пример банального ping-pong
|
44
60
|
|
45
|
-
|
61
|
+
class A
|
62
|
+
include Zactor
|
46
63
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
64
|
+
def initialize
|
65
|
+
zactor.init
|
66
|
+
ping Zactor.get_actor("b")
|
67
|
+
end
|
68
|
+
|
69
|
+
def ping(actor)
|
70
|
+
puts "Ping!"
|
71
|
+
zactor.send_request actor, :ping do |res|
|
72
|
+
puts res
|
52
73
|
end
|
53
74
|
end
|
54
75
|
end
|
55
76
|
|
56
|
-
|
57
|
-
|
77
|
+
|
78
|
+
class B
|
79
|
+
include Zactor
|
80
|
+
|
81
|
+
zactor do
|
82
|
+
identity "b"
|
83
|
+
|
84
|
+
event(:ping) do |o, msg|
|
85
|
+
msg.reply "Pong!"
|
86
|
+
end
|
87
|
+
end
|
58
88
|
|
59
|
-
|
60
|
-
|
61
|
-
state(:idling) # => New state idling
|
89
|
+
def initialize
|
90
|
+
zactor.init
|
62
91
|
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
EM.run do
|
96
|
+
Zactor.start 8000
|
97
|
+
|
98
|
+
a = A.new
|
99
|
+
b = B.new
|
63
100
|
end
|
101
|
+
|
102
|
+
A посылает сообщение :ping для B, а B отвечает "Pong!"
|
103
|
+
|
104
|
+
В коллбэк определенный в event передается объект получившый сообщение, объект сообщения ({Zactor::Message}) и далее переданные в запросе аргументы (если они есть). У {Zactor::Message} есть два основных метода: sender, возвращающий идентификатор отправителя и reply, который посылает ответ на запрос.
|
105
|
+
|
106
|
+
Важный момент, identity должно задаваться ДО zactor.init и после этого не может меняться.
|
107
|
+
|
108
|
+
ZMQ
|
109
|
+
===
|
110
|
+
|
111
|
+
При Zactor.start стартует брокер, по одному на каждый процесс, через него проходят все сообщения данного процесса, принимает сообщения через SUB-сокет, отправляет через PUB. SUB подписан на все сообщения. Каждый zactor-объект создает по паре сокетов, PUB подключается к SUB-брокера, а SUB к PUB-брокера. SUB подписывается на сообщения содержащие его identity.
|
112
|
+
|
113
|
+

|
114
|
+
|
115
|
+
Рассмотрим жизнь сообщения на примере с ping-ping. В случае с b в том же процессе:
|
116
|
+
|
117
|
+
<div class=wsd wsd_style="default"><pre>
|
118
|
+
A[PUB]->Broker[SUB]: Посылаем запрос :ping
|
119
|
+
Broker[SUB]->Broker[PUB]: Перебрасываем запрос в PUB сокет
|
120
|
+
Broker[PUB]->B[SUB]: Передаем получателю сообщение
|
121
|
+
B[PUB]->Broker[SUB]: Отправляем ответ "Pong!"
|
122
|
+
Broker[SUB]->Broker[PUB]: Перебрасываем запрос в PUB сокет
|
123
|
+
Broker[PUB]->A[SUB]: Отправитель получает ответ
|
124
|
+
</pre></div><script type="text/javascript" src="http://www.websequencediagrams.com/service.js"></script>
|
125
|
+
|
126
|
+
В случае с b в другом процессе:
|
127
|
+
|
128
|
+
<div class=wsd wsd_style="default"><pre>
|
129
|
+
A[PUB for App2]->App2 Broker[SUB]: Посылаем запрос :ping
|
130
|
+
App2 Broker[SUB]->App2 Broker[PUB]: Перебрасываем запрос в PUB сокет
|
131
|
+
App2 Broker[PUB]->B[SUB]: Передаем получателю сообщение
|
132
|
+
B[PUB for App1]->App1 Broker[SUB]: Отправляем ответ "Pong!"
|
133
|
+
App1 Broker[SUB]->App1 Broker[PUB]: Перебрасываем запрос в PUB сокет
|
134
|
+
App1 Broker[PUB]->A[SUB]: Отправитель получает ответ
|
135
|
+
</pre></div><script type="text/javascript" src="http://www.websequencediagrams.com/service.js"></script>
|
136
|
+
|
137
|
+
Балансировка
|
138
|
+
============
|
139
|
+
|
140
|
+
Так как это ZMQ, мы можем очень просто изменить тип получения сообщения. Например, добавив балансер. Теперь можно запускать процессы со ссылкой на этот балансер.
|
141
|
+
|
142
|
+
Zactor.start :balancer => "0.0.0.0:4000"
|
64
143
|
|
65
|
-
|
144
|
+
У нас получится примерно следующая схема:
|
66
145
|
|
67
|
-
|
68
|
-
происходит после добавления интерфейса в класс, в контексте этого класса.
|
146
|
+

|
69
147
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
B.new.baz # => "bar"
|
92
|
-
|
93
|
-
В каждом модуле может быть определено произвольное количество интерфейсов
|
148
|
+
Теперь наш ping можно отправлять в балансер, а отвечать будет один из подключенных воркеров.
|
149
|
+
|
150
|
+
ping Zactor.get_actor("b", :host => "0.0.0.0:4000")
|
151
|
+
|
152
|
+
|
153
|
+
Протокол обмена
|
154
|
+
===============
|
155
|
+
|
156
|
+
|
157
|
+
Perfomance
|
158
|
+
==========
|
159
|
+
|
160
|
+
А хрен его знает, толком не мерялось ничего :)
|
161
|
+
|
162
|
+
TODO
|
163
|
+
====
|
164
|
+
|
165
|
+
* Сделать событие отваливания объектов. Наверное, что-то вроде простого аналога link в эрланге.
|
166
|
+
* Добавить таймауты для запросов с коллбэками. Сейчас они будут висеть бесконечно и засрут память.
|
167
|
+
* Доступ к отправителю в колллбэке запроса. В случае с балансировкой он будет не тем же, кому мы посылали сообщение
|
data/Rakefile
CHANGED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.3
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# ruby client.rb SELF_PORT SERVER LOGIN
|
4
|
+
# ruby client.rb 6001 0.0.0.0:8000 lolo
|
5
|
+
require 'bundler'
|
6
|
+
ENV['BUNDLE_GEMFILE'] = File.join(File.dirname(__FILE__), '..', '..', 'Gemfile')
|
7
|
+
|
8
|
+
Bundler.setup(:default)
|
9
|
+
|
10
|
+
require 'zactor'
|
11
|
+
class Client
|
12
|
+
include Zactor
|
13
|
+
|
14
|
+
zactor do
|
15
|
+
event(:message) do |o, msg, text|
|
16
|
+
puts text
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(login)
|
21
|
+
@login = login
|
22
|
+
@persons = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def start(server)
|
26
|
+
zactor.init
|
27
|
+
@server = Zactor.get_actor("server", :host => server)
|
28
|
+
connect
|
29
|
+
end
|
30
|
+
|
31
|
+
def connect
|
32
|
+
puts "Подключаемся"
|
33
|
+
zactor.send_request(@server, :new_client, @login) do
|
34
|
+
puts "Поключились!"
|
35
|
+
zactor.link(@server) { connect }
|
36
|
+
end.timeout(5) { puts "Проблемы с подключением..." }
|
37
|
+
end
|
38
|
+
|
39
|
+
def send_message(text)
|
40
|
+
if text =~ /(\w+) -> (.+)/
|
41
|
+
send_personal($1, $2)
|
42
|
+
else
|
43
|
+
zactor.send_request(@server, :message, text)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def send_personal(login, text)
|
48
|
+
if client = @persons[login]
|
49
|
+
zactor.send_request(client, :message, "(personally) #{@login}:" + "#{text}")
|
50
|
+
else
|
51
|
+
zactor.send_request(@server, :client_request, login) do |res, client|
|
52
|
+
case res
|
53
|
+
when :ok
|
54
|
+
@persons[login] = client
|
55
|
+
zactor.link(client) { @persons.delete login }
|
56
|
+
zactor.send_request(client, :message, "(personally) #{@login}:" + "#{text}")
|
57
|
+
else
|
58
|
+
puts "Ошибка отправки сообщения"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def stop
|
65
|
+
zactor.finish
|
66
|
+
EM.stop
|
67
|
+
end
|
68
|
+
|
69
|
+
module KeyboardInput
|
70
|
+
include EM::Protocols::LineText2
|
71
|
+
attr_accessor :client
|
72
|
+
def receive_line(data)
|
73
|
+
client.send_message data
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
client = Client.new ARGV[2]
|
80
|
+
|
81
|
+
Signal.trap('INT') { client.stop }
|
82
|
+
Signal.trap('TERM') { client.stop }
|
83
|
+
|
84
|
+
EM.run do
|
85
|
+
Zactor.start ARGV[0]#, :debug => true
|
86
|
+
client.start ARGV[1]
|
87
|
+
EM.open_keyboard(Client::KeyboardInput) { |c| c.client = client }
|
88
|
+
end
|