brown 2.2.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +166 -0
  3. data/bin/brown +23 -17
  4. data/lib/brown/agent.rb +2 -0
  5. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 831f8702bad84d8fe35c098f0a624632ab06f835
4
- data.tar.gz: 55684d48c7e8233a7cd6d68621f77116e2b05f49
3
+ metadata.gz: 80d8b8839069b1aef10dc6a78f8d300b7f0d0191
4
+ data.tar.gz: 76552fb92abda3c6e4362fb8bcc12f2a602c8e14
5
5
  SHA512:
6
- metadata.gz: f879eb251cb5b9e511d4331a2b5e84c1c8590a80a846bf7c1d7ff813f50bf359be28a7421ffa5b718e5190d6a2fa59e25139c47f9399529d203bd8427a238096
7
- data.tar.gz: cbf37ca7b491d245d85e389d74ad91187d6824ec86503010b054b5eb9e0a7c5ab00950eaaa871df283a487f684843b59bdae48f18d8f0d6d854c3cb216d4ac52
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 = ThreadGroup.new
26
+ agents = []
27
27
 
28
28
  def stop_agents(agents)
29
- agents.list.each do |th|
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.add(th)
60
+ agents << th
61
61
 
62
62
  Brown::Agent.logger.info { "Started agent #{klass}" }
63
63
  end
64
64
 
65
65
  loop do
66
- sleep 1
66
+ if agents.empty?
67
+ break
68
+ end
67
69
 
68
- agents.list.each do |th|
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
- Brown::Agent.logger.info { "Re-starting #{klass} agent" }
78
+ Brown::Agent.logger.info { "Re-starting #{klass} agent" }
82
79
 
83
- th = Thread.new(klass) do |klass|
84
- klass.run
85
- Thread[:agent_class] = klass
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
- agents.add(th)
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" }
@@ -351,6 +351,8 @@ class Brown::Agent
351
351
  (@thread_group.list rescue []).each do |th|
352
352
  th.join
353
353
  end
354
+
355
+ @runner_thread.raise(Brown::StopSignal.new("Clean stop"))
354
356
  end
355
357
 
356
358
  # Set the logger that this agent will use to report problems.
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.0
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-13 00:00:00.000000000 Z
11
+ date: 2015-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny