brown 2.2.0 → 2.2.1

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