brown 2.2.0 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +166 -0
- data/bin/brown +23 -17
- data/lib/brown/agent.rb +2 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 80d8b8839069b1aef10dc6a78f8d300b7f0d0191
|
4
|
+
data.tar.gz: 76552fb92abda3c6e4362fb8bcc12f2a602c8e14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71edb2cda3ba54528d608bc5032e008d501bfd79de97cbb168516ca407c1086ae46fc2a9d21645d6ba5346a03e1534a32261a913338f1326f238a481b1d33b50
|
7
|
+
data.tar.gz: e1bc450d4ab6eeb14aae29184676481598c25a43b949b596f28e279a462d6f0924da00cdd26897432fec6cf81692b977fb6af5eb5970994098e82aaa2cc1410b
|
data/README.md
CHANGED
@@ -90,6 +90,172 @@ You can pass arguments to the agent method call, by giving them to
|
|
90
90
|
`worker.call`.
|
91
91
|
|
92
92
|
|
93
|
+
## Agent-wide common variables
|
94
|
+
|
95
|
+
There is some state you will want to keep across the entire agent. For
|
96
|
+
this, Brown provides the concept of "memos". These are persistent objects,
|
97
|
+
which you access via a class or instance method. To declare them, you
|
98
|
+
simply do:
|
99
|
+
|
100
|
+
class MemoUser < Brown::Agent
|
101
|
+
memo(:foo) { Foo.new }
|
102
|
+
end
|
103
|
+
|
104
|
+
The way this works is that the memo defines a method (both class and
|
105
|
+
instance) which, the first time you run it, runs the provided block to
|
106
|
+
create the memo object. Thereafter, that cached object is provided to the
|
107
|
+
caller of the memo method.
|
108
|
+
|
109
|
+
Because of Brown's multi-threaded nature, memos come with a built-in mutex
|
110
|
+
to prevent concurrent usage. That means that every time you want to access
|
111
|
+
the memo, you must do so inside a block:
|
112
|
+
|
113
|
+
class MemoUser < Brown::Agent
|
114
|
+
memo(:foo) { Foo.new }
|
115
|
+
|
116
|
+
every(10) do
|
117
|
+
foo do |f|
|
118
|
+
f.frob
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
The crucial thing to note here is that you *only have the memo lock inside
|
124
|
+
the block*. If you were to capture the memo object into a variable outside
|
125
|
+
the block, and then use it (read *or* write) outside the block, Really Bad
|
126
|
+
Things can happen. So **don't do that**.
|
127
|
+
|
128
|
+
When you have multiple memos, it is entirely possible that you can end up
|
129
|
+
deadlocking your agent by acquiring the locks for various memos in different
|
130
|
+
orders. Those dining philosophers are always getting themselves in a
|
131
|
+
muddle. To prevent this problem, it is highly recommended that you always
|
132
|
+
acquire the locks for your memos in the order they are written in the class
|
133
|
+
definition:
|
134
|
+
|
135
|
+
class MemoUser < Brown::Agent
|
136
|
+
memo(:foo) { Foo.new }
|
137
|
+
memo(:bar) { Bar.new }
|
138
|
+
|
139
|
+
every(5) do
|
140
|
+
# Acquiring a single lock is OK
|
141
|
+
foo do |f|
|
142
|
+
f.brob
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
every(6) do
|
147
|
+
# Acquiring a single lock, even if it is later in the
|
148
|
+
# list, is fine
|
149
|
+
bar do |b|
|
150
|
+
b.baznicate(b)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
every(7) do
|
155
|
+
# This is the right order to acquire nested locks
|
156
|
+
foo do |f|
|
157
|
+
bar do |b|
|
158
|
+
f.frob(b)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
every(7) do
|
164
|
+
# This is THE WRONG WAY AROUND. DO NOT DO THIS!
|
165
|
+
# YOU WILL GET DEADLOCKS!
|
166
|
+
bar do |b|
|
167
|
+
foo do |f|
|
168
|
+
b.baznicate(f)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
Another "gotcha" in the world of memos is that the memoised object itself is
|
175
|
+
persistent. When you get the lock, and the memo object comes in via the
|
176
|
+
argument to your block, that is *a reference* to the memo object. That
|
177
|
+
means that reassigning that variable to a new object won't change the value
|
178
|
+
of the memo object:
|
179
|
+
|
180
|
+
class MemoUser < Brown::Agent
|
181
|
+
memo(:now) { Time.now }
|
182
|
+
|
183
|
+
every(5) do
|
184
|
+
now do |t|
|
185
|
+
puts t
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
every(60) do
|
190
|
+
now do |t|
|
191
|
+
t = Time.now
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
The above code will *always* print the time as at the first time that `now`
|
197
|
+
was called, even though every minute we *think* we're resetting the memo
|
198
|
+
value to a new `Time`.
|
199
|
+
|
200
|
+
The "hack" around this is to use a single-value array to "contain" the
|
201
|
+
object that we actually want to periodically replace:
|
202
|
+
|
203
|
+
class MutableMemoUser < Brown::Agent
|
204
|
+
memo(:now) { [Time.now] }
|
205
|
+
|
206
|
+
every(5) do
|
207
|
+
now do |t|
|
208
|
+
puts t[0]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
every(60) do
|
213
|
+
now to |t|
|
214
|
+
t[0] = Time.now
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
This example code will print the same time for a minute, before changing to
|
220
|
+
a new minute.
|
221
|
+
|
222
|
+
|
223
|
+
### Thread-safe memos
|
224
|
+
|
225
|
+
There are some classes which are themselves thread-safe -- usually because
|
226
|
+
the class author has gone to some trouble to provide zer own, more
|
227
|
+
fine-grained, locking on the data within the object. If you are *quite
|
228
|
+
sure* you have such a thread-safe object to memoise, you can use
|
229
|
+
{Brown::Agent::ClassMethods.safe_memo} for that purpose:
|
230
|
+
|
231
|
+
class SafeMemoUser < Brown::Agent
|
232
|
+
safe_memo(:foo) { ThreadSafeFoo.new }
|
233
|
+
end
|
234
|
+
|
235
|
+
The benefit of this form of memos is that you don't have to access them in a
|
236
|
+
block:
|
237
|
+
|
238
|
+
class SafeMemoUser < Brown::Agent
|
239
|
+
safe_memo(:foo) { ThreadSafeFoo.new }
|
240
|
+
|
241
|
+
every(10) do
|
242
|
+
foo.frob
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
Like regular memos, you cannot reassign a thread-safe memo to another
|
247
|
+
object:
|
248
|
+
|
249
|
+
class SafeMemoUser < Brown::Agent
|
250
|
+
safe_memo(:foo) { ThreadSafeFoo.new }
|
251
|
+
|
252
|
+
every(10) do
|
253
|
+
# This will explode with a NameError
|
254
|
+
foo = ThreadSafeFoo.new
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
|
93
259
|
## AMQP publishing / consumption
|
94
260
|
|
95
261
|
Since message-based communication is a common pattern amongst cooperating
|
data/bin/brown
CHANGED
@@ -23,10 +23,10 @@ Brown::Agent.logger = Logger.new($stderr)
|
|
23
23
|
Brown::Agent.logger.level = Logger.const_get(ENVied.BROWN_LOG_LEVEL.upcase.to_sym)
|
24
24
|
Brown::Agent.logger.formatter = proc { |s,dt,n,msg| "#{$$}-#{Thread.current.object_id} [#{s[0]}] #{msg}\n" }
|
25
25
|
|
26
|
-
agents =
|
26
|
+
agents = []
|
27
27
|
|
28
28
|
def stop_agents(agents)
|
29
|
-
agents.
|
29
|
+
agents.each do |th|
|
30
30
|
th[:agent_class] && th[:agent_class].stop
|
31
31
|
end
|
32
32
|
end
|
@@ -53,39 +53,45 @@ Brown::Agent.logger.info { "Brown starting up..." }
|
|
53
53
|
|
54
54
|
agent_classes.each do |klass|
|
55
55
|
th = Thread.new(klass) do |klass|
|
56
|
+
Thread.current[:agent_class] = klass
|
56
57
|
klass.run
|
57
|
-
Thread[:agent_class] = klass
|
58
58
|
end
|
59
59
|
|
60
|
-
agents
|
60
|
+
agents << th
|
61
61
|
|
62
62
|
Brown::Agent.logger.info { "Started agent #{klass}" }
|
63
63
|
end
|
64
64
|
|
65
65
|
loop do
|
66
|
-
|
66
|
+
if agents.empty?
|
67
|
+
break
|
68
|
+
end
|
67
69
|
|
68
|
-
agents.
|
70
|
+
agents.each do |th|
|
69
71
|
unless th.alive?
|
70
72
|
begin
|
71
|
-
th.join
|
73
|
+
th.join(0.5)
|
72
74
|
rescue Exception => ex
|
73
75
|
Brown::Agent.logger.fatal { "Agent #{th[:agent_class]} crashed: #{ex.message} (#{ex.class})" }
|
74
76
|
Brown::Agent.logger.info { ex.backtrace.map { |l| " #{l}" }.join("\n") }
|
75
|
-
else
|
76
|
-
Brown::Agent.logger.warn "Agent #{th[:agent_class]} terminated itself"
|
77
|
-
end
|
78
|
-
klass = th[:agent_class]
|
79
|
-
th[:agent_class] = nil
|
80
77
|
|
81
|
-
|
78
|
+
Brown::Agent.logger.info { "Re-starting #{klass} agent" }
|
82
79
|
|
83
|
-
|
84
|
-
|
85
|
-
|
80
|
+
new_th = Thread.new(klass) do |klass|
|
81
|
+
Thread.current[:agent_class] = klass
|
82
|
+
klass.run
|
83
|
+
end
|
84
|
+
|
85
|
+
agents << new_th
|
86
|
+
else
|
87
|
+
Brown::Agent.logger.warn "Agent #{th[:agent_class]} exited cleanly; not restarting"
|
86
88
|
end
|
87
89
|
|
88
|
-
|
90
|
+
klass = th[:agent_class]
|
91
|
+
th[:agent_class] = nil
|
92
|
+
agents.delete(th)
|
89
93
|
end
|
90
94
|
end
|
91
95
|
end
|
96
|
+
|
97
|
+
Brown::Agent.logger.info { "Brown exiting" }
|
data/lib/brown/agent.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brown
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.
|
4
|
+
version: 2.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Palmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bunny
|