bud 0.9.4 → 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/History.txt +106 -0
- data/README.md +6 -4
- data/Rakefile +91 -0
- data/bin/budlabel +63 -0
- data/bin/budplot +18 -8
- data/bin/budtimelines +2 -2
- data/bin/budvis +7 -1
- data/docs/README.md +8 -17
- data/docs/cheat.md +112 -13
- data/docs/getstarted.md +97 -84
- data/docs/operational.md +3 -3
- data/examples/basics/paths.rb +2 -2
- data/examples/chat/README.md +2 -0
- data/examples/chat/chat.rb +3 -2
- data/examples/chat/chat_protocol.rb +2 -2
- data/examples/chat/chat_server.rb +3 -2
- data/lib/bud.rb +229 -114
- data/lib/bud/aggs.rb +20 -4
- data/lib/bud/bud_meta.rb +83 -73
- data/lib/bud/collections.rb +306 -120
- data/lib/bud/depanalysis.rb +3 -4
- data/lib/bud/executor/README.rescan +2 -1
- data/lib/bud/executor/elements.rb +96 -95
- data/lib/bud/executor/group.rb +35 -32
- data/lib/bud/executor/join.rb +164 -183
- data/lib/bud/graphs.rb +3 -3
- data/lib/bud/labeling/bloomgraph.rb +47 -0
- data/lib/bud/labeling/budplot_style.rb +53 -0
- data/lib/bud/labeling/labeling.rb +288 -0
- data/lib/bud/lattice-core.rb +595 -0
- data/lib/bud/lattice-lib.rb +422 -0
- data/lib/bud/monkeypatch.rb +68 -32
- data/lib/bud/rebl.rb +28 -10
- data/lib/bud/rewrite.rb +361 -152
- data/lib/bud/server.rb +16 -8
- data/lib/bud/source.rb +21 -18
- data/lib/bud/state.rb +93 -4
- data/lib/bud/storage/zookeeper.rb +45 -33
- data/lib/bud/version.rb +3 -0
- data/lib/bud/viz.rb +10 -12
- data/lib/bud/viz_util.rb +8 -3
- metadata +107 -108
data/docs/getstarted.md
CHANGED
@@ -66,9 +66,11 @@ Second, note that our Bud program's one statement merges the values on its right
|
|
66
66
|
### Tables and Scratches ###
|
67
67
|
Before we dive into writing server code, let's try a slightly more involved single-timestep example. Start up rebl again, and paste in the following:
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
``` ruby
|
70
|
+
table :clouds
|
71
|
+
clouds <= [[1, "Cirrus"], [2, "Cumulus"]]
|
72
|
+
stdio <~ clouds.inspected
|
73
|
+
```
|
72
74
|
|
73
75
|
Now tick your rebl, but don't quit yet.
|
74
76
|
|
@@ -128,8 +130,10 @@ Now that we've seen a bit of Bloom, we're ready to write our first interesting s
|
|
128
130
|
|
129
131
|
Even though we're getting ahead of ourselves, let's have a peek at the Bloom statements that implement the server in `examples/chat/chat_server.rb`:
|
130
132
|
|
131
|
-
|
132
|
-
|
133
|
+
``` ruby
|
134
|
+
nodelist <= connect { |c| [c.client, c.nick] }
|
135
|
+
mcast <~ (mcast * nodelist).pairs { |m,n| [n.key, m.val] }
|
136
|
+
```
|
133
137
|
|
134
138
|
That's it! There is one statement for each of the two sentences describing the behavior of the "basic idea" above. We'll go through these two statements in more detail shortly. But it's nice to see right away how concisely and naturally a Bloom program can fit our intuitive description of a distributed service.
|
135
139
|
|
@@ -137,14 +141,16 @@ That's it! There is one statement for each of the two sentences describing the
|
|
137
141
|
|
138
142
|
Now that we've satisfied our need to peek, let's take this a bit more methodically. First we need declarations for the various Bloom collections we'll be using. We put the declarations that are common to both client and server into file `examples/chat/chat_protocol.rb`:
|
139
143
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
144
|
+
``` ruby
|
145
|
+
module ChatProtocol
|
146
|
+
state do
|
147
|
+
channel :connect, [:@addr, :client] => [:nick]
|
148
|
+
channel :mcast
|
149
|
+
end
|
150
|
+
|
151
|
+
DEFAULT_ADDR = "localhost:12345"
|
152
|
+
end
|
153
|
+
```
|
148
154
|
|
149
155
|
This defines a [Ruby mixin module](http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html) called `ChatProtocol` that has a couple special Bloom features:
|
150
156
|
|
@@ -156,33 +162,34 @@ This defines a [Ruby mixin module](http://www.ruby-doc.org/docs/ProgrammingRuby/
|
|
156
162
|
|
157
163
|
Given this protocol (and the Ruby constant at the bottom), we're now ready to examine `examples/chat/chat_server.rb` in more detail:
|
158
164
|
|
159
|
-
|
160
|
-
|
161
|
-
|
165
|
+
``` ruby
|
166
|
+
require 'rubygems'
|
167
|
+
require 'bud'
|
168
|
+
require_relative 'chat_protocol'
|
162
169
|
|
163
|
-
|
164
|
-
|
165
|
-
|
170
|
+
class ChatServer
|
171
|
+
include Bud
|
172
|
+
include ChatProtocol
|
166
173
|
|
167
|
-
|
168
|
-
|
169
|
-
bloom do
|
170
|
-
nodelist <= connect.payloads
|
171
|
-
mcast <~ (mcast * nodelist).pairs { |m,n| [n.key, m.val] }
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
if ARGV.first
|
176
|
-
addr = ARGV.first
|
177
|
-
else
|
178
|
-
addr = ChatProtocol::DEFAULT_ADDR
|
179
|
-
end
|
180
|
-
|
181
|
-
ip, port = addr.split(":")
|
182
|
-
puts "Server address: #{ip}:#{port}"
|
183
|
-
program = ChatServer.new(:ip => ip, :port => port.to_i)
|
184
|
-
program.run
|
174
|
+
state { table :nodelist }
|
185
175
|
|
176
|
+
bloom do
|
177
|
+
nodelist <= connect { |c| [c.client, c.nick] }
|
178
|
+
mcast <~ (mcast * nodelist).pairs { |m,n| [n.key, m.val] }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
if ARGV.first
|
183
|
+
addr = ARGV.first
|
184
|
+
else
|
185
|
+
addr = ChatProtocol::DEFAULT_ADDR
|
186
|
+
end
|
187
|
+
|
188
|
+
ip, port = addr.split(":")
|
189
|
+
puts "Server address: #{ip}:#{port}"
|
190
|
+
program = ChatServer.new(:ip => ip, :port => port.to_i)
|
191
|
+
program.run_fg
|
192
|
+
```
|
186
193
|
|
187
194
|
The first few lines get the appropriate Ruby classes and modules loaded via `require`. We then define the ChatServer class which mixes in the `Bud` module and the ChatProtocol module we looked at above. Then we have another `state` block that declares one additional collection, the `nodelist` table.
|
188
195
|
|
@@ -190,14 +197,18 @@ With those preliminaries aside, we have our first `bloom` block, which is how Bl
|
|
190
197
|
|
191
198
|
The first is pretty simple:
|
192
199
|
|
193
|
-
|
200
|
+
``` ruby
|
201
|
+
nodelist <= connect { |c| [c.client, c.nick] }
|
202
|
+
```
|
194
203
|
|
195
|
-
This says that whenever messages arrive on the channel named "connect",
|
204
|
+
This says that whenever messages arrive on the channel named "connect", the client address and user-provided nickname should be instantaneously merged into the table "nodelist", which will store them persistently. Note that nodelist has a \[key/val\] pair structure, so it is suitable for storing pairs of (IP address, nickname).
|
196
205
|
|
197
|
-
The next Bloom statement is more complex. Remember the description in the "basic idea" at the beginning of this section: the server needs to accept inbound chat messages from clients
|
206
|
+
The next Bloom statement is more complex. Remember the description in the "basic idea" at the beginning of this section: the server needs to accept inbound chat messages from clients and forward them to other clients.
|
207
|
+
|
208
|
+
``` ruby
|
209
|
+
mcast <~ (mcast * nodelist).pairs { |m,n| [n.key, m.val] }
|
210
|
+
```
|
198
211
|
|
199
|
-
mcast <~ (mcast * nodelist).pairs { |m,n| [n.key, m.val] }
|
200
|
-
|
201
212
|
The first thing to note is the lhs and operator in this statement. We are merging items (asynchronously, of course!) into the mcast channel, where they will be sent to their eventual destination.
|
202
213
|
|
203
214
|
The rhs is our first introduction to the `*` operator of Bloom collections, and the `pairs` method after it. You can think of the `*` operator as "all-pairs": it produces a Bloom collection containing all pairs of mcast and nodelist items. The `pairs` method iterates through these pairs, passing them through a code block via the block arguments `m` and `n`. Finally, for each such pair the block produces an item containing the `key` attribute of the nodelist item, and the `val` attribute of the mcast item. This is structured as a proper \[address, val\] entry to be merged back into the mcast channel. Putting this together, this statement *multicasts inbound payloads on the mcast channel to all nodes in the chat*.
|
@@ -205,7 +216,7 @@ The rhs is our first introduction to the `*` operator of Bloom collections, and
|
|
205
216
|
The remaining lines of plain Ruby simply instantiate and run the ChatServer class (which includes the `Bud` module) using an ip and port given on the command line (or the default from ChatProtocol.rb).
|
206
217
|
|
207
218
|
#### `*`'s and Clouds ####
|
208
|
-
You can think of
|
219
|
+
You can think of our use of the `*` operator on the rhs of the second statement in a few different ways:
|
209
220
|
|
210
221
|
* If you're familiar with event-loop programming, this implements an *event handler* for messages on the mcast channel: whenever an mcast message arrives, this handler performs lookups in the nodelist table to form new messages. (It is easy to add "filters" to these handlers as arguments to `pairs`.) The resulting messages are dispatched via the mcast channel accordingly. This is a very common pattern in Bloom programs: handling channel messages via lookups in a table.
|
211
222
|
|
@@ -218,49 +229,51 @@ Given our understanding of the server, the client should be pretty simple. It n
|
|
218
229
|
|
219
230
|
And here's the code:
|
220
231
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
bootstrap do
|
236
|
-
connect <~ [[@server, [ip_port, @nick]]]
|
237
|
-
end
|
238
|
-
|
239
|
-
bloom do
|
240
|
-
mcast <~ stdio do |s|
|
241
|
-
[@server, [ip_port, @nick, Time.new.strftime("%I:%M.%S"), s.line]]
|
242
|
-
end
|
243
|
-
|
244
|
-
stdio <~ mcast { |m| [pretty_print(m.val)] }
|
245
|
-
end
|
246
|
-
|
247
|
-
# format chat messages with timestamp on the right of the screen
|
248
|
-
def pretty_print(val)
|
249
|
-
str = val[1].to_s + ": " + (val[3].to_s || '')
|
250
|
-
pad = "(" + val[2].to_s + ")"
|
251
|
-
return str + " "*[66 - str.length,2].max + pad
|
252
|
-
end
|
253
|
-
end
|
232
|
+
``` ruby
|
233
|
+
require 'rubygems'
|
234
|
+
require 'bud'
|
235
|
+
require_relative 'chat_protocol'
|
236
|
+
|
237
|
+
class ChatClient
|
238
|
+
include Bud
|
239
|
+
include ChatProtocol
|
240
|
+
|
241
|
+
def initialize(nick, server, opts={})
|
242
|
+
@nick = nick
|
243
|
+
@server = server
|
244
|
+
super opts
|
245
|
+
end
|
254
246
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
247
|
+
bootstrap do
|
248
|
+
connect <~ [[@server, ip_port, @nick]]
|
249
|
+
end
|
250
|
+
|
251
|
+
bloom do
|
252
|
+
mcast <~ stdio do |s|
|
253
|
+
[@server, [ip_port, @nick, Time.new.strftime("%I:%M.%S"), s.line]]
|
259
254
|
end
|
260
255
|
|
261
|
-
|
262
|
-
|
263
|
-
|
256
|
+
stdio <~ mcast { |m| [pretty_print(m.val)] }
|
257
|
+
end
|
258
|
+
|
259
|
+
# format chat messages with timestamp on the right of the screen
|
260
|
+
def pretty_print(val)
|
261
|
+
str = val[1].to_s + ": " + (val[3].to_s || '')
|
262
|
+
pad = "(" + val[2].to_s + ")"
|
263
|
+
return str + " "*[66 - str.length,2].max + pad
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
if ARGV.length == 2
|
268
|
+
server = ARGV[1]
|
269
|
+
else
|
270
|
+
server = ChatProtocol::DEFAULT_ADDR
|
271
|
+
end
|
272
|
+
|
273
|
+
puts "Server address: #{server}"
|
274
|
+
program = ChatClient.new(ARGV[0], server, :stdin => $stdin)
|
275
|
+
program.run_fg
|
276
|
+
```
|
264
277
|
|
265
278
|
The ChatClient class has a typical Ruby `initialize` method that sets up two local instance variables: one for this client's nickname, and another for the 'IP:port' address string for the server. It then calls the initializer of the Bud superclass passing along a hash of options.
|
266
279
|
|
@@ -293,4 +306,4 @@ In this section we saw a number of features that we missed in our earlier single
|
|
293
306
|
* **the * operator and pairs method**: the way to combine items from multiple collections.
|
294
307
|
|
295
308
|
# The Big Picture and the Details #
|
296
|
-
Now that you've seen some working Bloom code, hopefully you're ready to delve deeper. The [README](README.md) provides links to places you can go for more information. Have fun and [stay in touch](http://groups.google.com/group/bloom-lang)!
|
309
|
+
Now that you've seen some working Bloom code, hopefully you're ready to delve deeper. The [README](README.md) provides links to places you can go for more information. Have fun and [stay in touch](http://groups.google.com/group/bloom-lang)!
|
data/docs/operational.md
CHANGED
@@ -58,7 +58,7 @@ Have a look at the following classic "transitive closure" example, which compute
|
|
58
58
|
|
59
59
|
state do
|
60
60
|
table :link, [:from, :to, :cost]
|
61
|
-
table :path, [:from, :to,
|
61
|
+
table :path, [:from, :to, :cost]
|
62
62
|
end
|
63
63
|
|
64
64
|
bloom :make_paths do
|
@@ -67,7 +67,7 @@ Have a look at the following classic "transitive closure" example, which compute
|
|
67
67
|
|
68
68
|
# recurse: path of length n+1 made by a link to a path of length n
|
69
69
|
path <= (link*path).pairs(:to => :from) do |l,p|
|
70
|
-
[l.from, p.to, l.cost+p.cost]
|
70
|
+
[l.from, p.to, l.cost + p.cost]
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
@@ -97,4 +97,4 @@ Note that it is possible to write a program in Bloom that is *unstratifiable*: t
|
|
97
97
|
|
98
98
|
glass <= one_item {|t| ['full'] if glass.empty? }
|
99
99
|
|
100
|
-
Consider the case where we start out with glass being empty. Then we know the fact `glass.empty?`, and the bloom statement says that `(glass.empty? => not glass.empty?)` which is equivalent to `(glass.empty? and not glass.empty?)` which is a contradiction. The Bud runtime detects cycles through non-monotonicity for you automatically when you instantiate your class.
|
100
|
+
Consider the case where we start out with glass being empty. Then we know the fact `glass.empty?`, and the bloom statement says that `(glass.empty? => not glass.empty?)` which is equivalent to `(glass.empty? and not glass.empty?)` which is a contradiction. The Bud runtime detects cycles through non-monotonicity for you automatically when you instantiate your class.
|
data/examples/basics/paths.rb
CHANGED
@@ -36,7 +36,7 @@ program = ShortestPaths.new
|
|
36
36
|
|
37
37
|
# populate our little example. we put two links between 'a' and 'b'
|
38
38
|
# to see whether our shortest-paths code does the right thing.
|
39
|
-
program.link
|
39
|
+
program.link <+ [['a', 'b', 1],
|
40
40
|
['a', 'b', 4],
|
41
41
|
['b', 'c', 1],
|
42
42
|
['c', 'd', 1],
|
@@ -48,6 +48,6 @@ program.shortest.to_a.sort.each {|t| puts t.inspect}
|
|
48
48
|
puts "----"
|
49
49
|
|
50
50
|
# now lets add an extra link and recompute
|
51
|
-
program.link
|
51
|
+
program.link <+ [['e', 'f', 1]]
|
52
52
|
program.tick
|
53
53
|
program.shortest.to_a.sort.each {|t| puts t.inspect}
|
data/examples/chat/README.md
CHANGED
data/examples/chat/chat.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'backports'
|
2
3
|
require 'bud'
|
3
|
-
|
4
|
+
require_relative 'chat_protocol'
|
4
5
|
|
5
6
|
class ChatClient
|
6
7
|
include Bud
|
@@ -13,7 +14,7 @@ class ChatClient
|
|
13
14
|
end
|
14
15
|
|
15
16
|
bootstrap do
|
16
|
-
connect <~ [[@server,
|
17
|
+
connect <~ [[@server, ip_port, @nick]]
|
17
18
|
end
|
18
19
|
|
19
20
|
bloom do
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'backports'
|
2
3
|
require 'bud'
|
3
|
-
|
4
|
+
require_relative 'chat_protocol'
|
4
5
|
|
5
6
|
class ChatServer
|
6
7
|
include Bud
|
@@ -9,7 +10,7 @@ class ChatServer
|
|
9
10
|
state { table :nodelist }
|
10
11
|
|
11
12
|
bloom do
|
12
|
-
nodelist <= connect.
|
13
|
+
nodelist <= connect { |c| [c.client, c.nick] }
|
13
14
|
mcast <~ (mcast * nodelist).pairs { |m,n| [n.key, m.val] }
|
14
15
|
end
|
15
16
|
end
|
data/lib/bud.rb
CHANGED
@@ -1,16 +1,27 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
gem 'ruby2ruby', '>= 2.0.1'
|
3
|
+
gem 'ruby_parser', '>= 3.0.2'
|
4
|
+
|
2
5
|
require 'eventmachine'
|
3
6
|
require 'msgpack'
|
7
|
+
require 'ruby2ruby'
|
8
|
+
require 'ruby_parser'
|
9
|
+
require 'set'
|
4
10
|
require 'socket'
|
5
11
|
require 'superators19'
|
6
12
|
require 'thread'
|
7
|
-
require 'bud/errors'
|
8
13
|
|
14
|
+
require 'bud/errors'
|
9
15
|
require 'bud/monkeypatch'
|
10
16
|
|
11
17
|
require 'bud/aggs'
|
12
18
|
require 'bud/bud_meta'
|
13
19
|
require 'bud/collections'
|
20
|
+
require 'bud/executor/elements.rb'
|
21
|
+
require 'bud/executor/group.rb'
|
22
|
+
require 'bud/executor/join.rb'
|
23
|
+
require 'bud/lattice-core'
|
24
|
+
require 'bud/lattice-lib'
|
14
25
|
require 'bud/metrics'
|
15
26
|
require 'bud/rtrace'
|
16
27
|
require 'bud/server'
|
@@ -19,10 +30,6 @@ require 'bud/storage/dbm'
|
|
19
30
|
require 'bud/storage/zookeeper'
|
20
31
|
require 'bud/viz'
|
21
32
|
|
22
|
-
require 'bud/executor/elements.rb'
|
23
|
-
require 'bud/executor/group.rb'
|
24
|
-
require 'bud/executor/join.rb'
|
25
|
-
|
26
33
|
ILLEGAL_INSTANCE_ID = -1
|
27
34
|
SIGNAL_CHECK_PERIOD = 0.2
|
28
35
|
|
@@ -61,12 +68,13 @@ $bud_instances = {} # Map from instance id => Bud instance
|
|
61
68
|
# :main: Bud
|
62
69
|
module Bud
|
63
70
|
attr_reader :budtime, :inbound, :options, :meta_parser, :viz, :rtracer, :dsock
|
64
|
-
attr_reader :tables, :builtin_tables, :channels, :zk_tables, :dbm_tables, :app_tables
|
71
|
+
attr_reader :tables, :builtin_tables, :channels, :zk_tables, :dbm_tables, :app_tables, :lattices
|
65
72
|
attr_reader :push_sources, :push_elems, :push_joins, :scanners, :merge_targets
|
66
|
-
attr_reader :this_stratum, :
|
73
|
+
attr_reader :this_stratum, :this_rule_context, :done_bootstrap
|
74
|
+
attr_reader :inside_tick
|
67
75
|
attr_accessor :stratified_rules
|
68
76
|
attr_accessor :metrics, :periodics
|
69
|
-
attr_accessor :
|
77
|
+
attr_accessor :qualified_name
|
70
78
|
attr_reader :running_async
|
71
79
|
|
72
80
|
# options to the Bud runtime are passed in a hash, with the following keys
|
@@ -101,14 +109,12 @@ module Bud
|
|
101
109
|
# * <tt>:dbm_dir</tt> filesystem directory to hold DBM-backed collections
|
102
110
|
# * <tt>:dbm_truncate</tt> if true, DBM-backed collections are opened with +OTRUNC+
|
103
111
|
def initialize(options={})
|
104
|
-
# capture the binding for a subsequent 'eval'. This ensures that local
|
105
|
-
# variable names introduced later in this method don't interfere with
|
106
|
-
# table names used in the eval block.
|
107
112
|
options[:dump_rewrite] ||= ENV["BUD_DUMP_REWRITE"].to_i > 0
|
108
113
|
options[:dump_ast] ||= ENV["BUD_DUMP_AST"].to_i > 0
|
109
114
|
options[:print_wiring] ||= ENV["BUD_PRINT_WIRING"].to_i > 0
|
110
115
|
@qualified_name = ""
|
111
116
|
@tables = {}
|
117
|
+
@lattices = {}
|
112
118
|
@channels = {}
|
113
119
|
@dbm_tables = {}
|
114
120
|
@zk_tables = {}
|
@@ -130,7 +136,8 @@ module Bud
|
|
130
136
|
@instance_id = ILLEGAL_INSTANCE_ID # Assigned when we start running
|
131
137
|
@metrics = {}
|
132
138
|
@endtime = nil
|
133
|
-
@this_stratum =
|
139
|
+
@this_stratum = -1
|
140
|
+
@this_rule_id = -1
|
134
141
|
@push_sorted_elems = nil
|
135
142
|
@running_async = false
|
136
143
|
@bud_started = false
|
@@ -144,23 +151,22 @@ module Bud
|
|
144
151
|
# NB: If using an ephemeral port (specified by port = 0), the actual port
|
145
152
|
# number won't be known until we start EM
|
146
153
|
|
154
|
+
load_lattice_defs
|
147
155
|
builtin_state
|
148
156
|
resolve_imports
|
149
157
|
call_state_methods
|
150
158
|
|
151
|
-
@declarations = self.class.instance_methods.select {|m| m =~ /^__bloom__.+$/}.map {|m| m.to_s}
|
152
|
-
|
153
159
|
@viz = VizOnline.new(self) if @options[:trace]
|
154
160
|
@rtracer = RTrace.new(self) if @options[:rtrace]
|
155
161
|
|
156
162
|
do_rewrite
|
157
163
|
if toplevel == self
|
158
164
|
# initialize per-stratum state
|
159
|
-
num_strata = @stratified_rules.length
|
160
|
-
@scanners = num_strata.times.map{{}}
|
161
|
-
@push_sources = num_strata.times.map{{}}
|
162
|
-
@push_joins = num_strata.times.map{[]}
|
163
|
-
@merge_targets = num_strata.times.map{Set.new}
|
165
|
+
@num_strata = @stratified_rules.length
|
166
|
+
@scanners = @num_strata.times.map{{}}
|
167
|
+
@push_sources = @num_strata.times.map{{}}
|
168
|
+
@push_joins = @num_strata.times.map{[]}
|
169
|
+
@merge_targets = @num_strata.times.map{Set.new}
|
164
170
|
end
|
165
171
|
end
|
166
172
|
|
@@ -248,16 +254,21 @@ module Bud
|
|
248
254
|
tables[qname.to_sym] = t
|
249
255
|
end
|
250
256
|
end
|
257
|
+
mod_inst.lattices.each_pair do |name, t|
|
258
|
+
qname = "#{local_name}.#{name}".to_sym
|
259
|
+
raise Bud::Error if lattices.has_key? qname
|
260
|
+
lattices[qname] = t
|
261
|
+
end
|
251
262
|
mod_inst.t_rules.each do |imp_rule|
|
252
263
|
qname = "#{local_name}.#{imp_rule.lhs}"
|
253
264
|
self.t_rules << [imp_rule.bud_obj, imp_rule.rule_id, qname, imp_rule.op,
|
254
|
-
imp_rule.src, imp_rule.orig_src, imp_rule.
|
265
|
+
imp_rule.src, imp_rule.orig_src, imp_rule.unsafe_funcs_called]
|
255
266
|
end
|
256
267
|
mod_inst.t_depends.each do |imp_dep|
|
257
268
|
qlname = "#{local_name}.#{imp_dep.lhs}"
|
258
269
|
qrname = "#{local_name}.#{imp_dep.body}"
|
259
270
|
self.t_depends << [imp_dep.bud_obj, imp_dep.rule_id, qlname,
|
260
|
-
imp_dep.op, qrname, imp_dep.nm]
|
271
|
+
imp_dep.op, qrname, imp_dep.nm, imp_dep.in_body]
|
261
272
|
end
|
262
273
|
mod_inst.t_provides.each do |imp_pro|
|
263
274
|
qintname = "#{local_name}.#{imp_pro.interface}"
|
@@ -304,14 +315,13 @@ module Bud
|
|
304
315
|
end
|
305
316
|
|
306
317
|
def do_wiring
|
307
|
-
@num_strata = @stratified_rules.length
|
308
|
-
|
309
318
|
@stratified_rules.each_with_index { |rules, stratum| eval_rules(rules, stratum) }
|
310
319
|
|
311
320
|
# Prepare list of tables that will be actively used at run time. First, all
|
312
|
-
# the user-defined
|
313
|
-
# an array later.
|
321
|
+
# the user-defined tables and lattices. We start @app_tables off as a set,
|
322
|
+
# then convert to an array later.
|
314
323
|
@app_tables = (@tables.keys - @builtin_tables.keys).map {|t| @tables[t]}.to_set
|
324
|
+
@app_tables.merge(@lattices.values)
|
315
325
|
|
316
326
|
# Check scan and merge_targets to see if any builtin_tables need to be added as well.
|
317
327
|
@scanners.each do |scs|
|
@@ -331,11 +341,11 @@ module Bud
|
|
331
341
|
seen = Set.new(working)
|
332
342
|
sorted_elems = [] # sorted elements in this stratum
|
333
343
|
while not working.empty?
|
334
|
-
sorted_elems
|
344
|
+
sorted_elems.concat(working)
|
335
345
|
wired_to = []
|
336
346
|
working.each do |e|
|
337
347
|
e.wirings.each do |out|
|
338
|
-
if (out.class <= PushElement and not seen.member?(out))
|
348
|
+
if ((out.class <= PushElement || out.class <= LatticePushElement) and not seen.member?(out))
|
339
349
|
seen << out
|
340
350
|
wired_to << out
|
341
351
|
end
|
@@ -352,6 +362,24 @@ module Bud
|
|
352
362
|
end
|
353
363
|
end
|
354
364
|
|
365
|
+
# We create "orphan" scanners for collections that don't appear on the RHS
|
366
|
+
# of any rules, but do appear on the LHS of at least one rule. These
|
367
|
+
# scanners aren't needed to compute the fixpoint, but they are used as part
|
368
|
+
# of rescan/invalidation (e.g., if an orphaned collection receives a manual
|
369
|
+
# deletion operation, we need to arrange for the collection to be
|
370
|
+
# re-filled).
|
371
|
+
@orphan_scanners = [] # Pairs of [scanner, stratum]
|
372
|
+
@app_tables.each do |t|
|
373
|
+
next unless t.class <= Bud::BudCollection # skip lattice wrappers
|
374
|
+
next if t.scanner_cnt > 0
|
375
|
+
|
376
|
+
stratum = collection_stratum(t.qualified_tabname.to_s)
|
377
|
+
# if the collection also doesn't appear on any LHSs, skip it
|
378
|
+
next if stratum.nil?
|
379
|
+
@orphan_scanners << [Bud::ScannerElement.new(t.tabname, self, t, t.schema),
|
380
|
+
stratum]
|
381
|
+
end
|
382
|
+
|
355
383
|
# Sanity check
|
356
384
|
@push_sorted_elems.each do |stratum_elems|
|
357
385
|
stratum_elems.each {|se| se.check_wiring}
|
@@ -417,19 +445,17 @@ module Bud
|
|
417
445
|
#
|
418
446
|
# scanner[stratum].rescan_set = Similar to above.
|
419
447
|
def prepare_invalidation_scheme
|
420
|
-
num_strata = @push_sorted_elems.size
|
421
448
|
if $BUD_SAFE
|
422
|
-
@app_tables = @tables.values # No
|
449
|
+
@app_tables = @tables.values + @lattices.values # No collections excluded
|
423
450
|
|
424
451
|
rescan = Set.new
|
425
452
|
invalidate = @app_tables.select {|t| t.class <= BudScratch}.to_set
|
426
|
-
num_strata.times do |stratum|
|
453
|
+
@num_strata.times do |stratum|
|
427
454
|
@push_sorted_elems[stratum].each do |elem|
|
428
455
|
invalidate << elem
|
429
456
|
rescan << elem
|
430
457
|
end
|
431
458
|
end
|
432
|
-
#prune_rescan_invalidate(rescan, invalidate)
|
433
459
|
@default_rescan = rescan.to_a
|
434
460
|
@default_invalidate = invalidate.to_a
|
435
461
|
@reset_list = [] # Nothing to reset at end of tick. It'll be overwritten anyway
|
@@ -438,15 +464,19 @@ module Bud
|
|
438
464
|
|
439
465
|
# By default, all tables are considered sources unless they appear on the
|
440
466
|
# lhs. We only consider non-temporal rules because invalidation is only
|
441
|
-
# about this tick. Also, we track (in
|
442
|
-
# targets of user-defined code blocks that call
|
443
|
-
#
|
444
|
-
# their contents, and thus forced to
|
445
|
-
|
467
|
+
# about this tick. Also, we track (in unsafe_targets) those tables that are
|
468
|
+
# the targets of user-defined code blocks that call "unsafe" functions that
|
469
|
+
# produce a different value in every tick (e.g., budtime). Elements that
|
470
|
+
# feed these tables are forced to rescan their contents, and thus forced to
|
471
|
+
# re-execute these code blocks.
|
472
|
+
unsafe_targets = Set.new
|
446
473
|
t_rules.each do |rule|
|
447
474
|
lhs = rule.lhs.to_sym
|
448
|
-
|
449
|
-
|
475
|
+
if rule.op == "<="
|
476
|
+
# Note that lattices cannot be sources
|
477
|
+
@tables[lhs].is_source = false if @tables.has_key? lhs
|
478
|
+
end
|
479
|
+
unsafe_targets << lhs if rule.unsafe_funcs_called
|
450
480
|
end
|
451
481
|
|
452
482
|
# Compute a set of tables and elements that should be explicitly told to
|
@@ -455,54 +485,102 @@ module Bud
|
|
455
485
|
invalidate = @app_tables.select {|t| t.invalidate_at_tick}.to_set
|
456
486
|
rescan = Set.new
|
457
487
|
|
458
|
-
num_strata.times do |stratum|
|
488
|
+
@num_strata.times do |stratum|
|
459
489
|
@push_sorted_elems[stratum].each do |elem|
|
460
490
|
rescan << elem if elem.rescan_at_tick
|
461
491
|
|
462
|
-
if elem.outputs.any?{|tab| not(tab.class <= PushElement) and
|
492
|
+
if elem.outputs.any?{|tab| not(tab.class <= PushElement) and not(tab.class <= LatticePushElement) and unsafe_targets.member? tab.qualified_tabname.to_sym }
|
463
493
|
rescan.merge(elem.wired_by)
|
464
494
|
end
|
465
495
|
end
|
466
496
|
rescan_invalidate_tc(stratum, rescan, invalidate)
|
467
497
|
end
|
468
498
|
|
469
|
-
prune_rescan_invalidate(rescan, invalidate)
|
470
|
-
# transitive closure
|
471
499
|
@default_rescan = rescan.to_a
|
472
500
|
@default_invalidate = invalidate.to_a
|
473
501
|
|
474
|
-
|
475
|
-
|
502
|
+
if $BUD_DEBUG
|
503
|
+
puts "Default rescan: #{rescan.inspect}"
|
504
|
+
puts "Default inval: #{invalidate.inspect}"
|
505
|
+
puts "Unsafe targets: #{unsafe_targets.inspect}"
|
506
|
+
end
|
476
507
|
|
477
|
-
#
|
478
|
-
# tables and elements that will
|
479
|
-
# invalidated at
|
508
|
+
# For each collection that is to be scanned, compute the set of dependent
|
509
|
+
# tables and elements that will need invalidation and/or rescan if that
|
510
|
+
# table were to be invalidated at runtime.
|
480
511
|
dflt_rescan = rescan
|
481
512
|
dflt_invalidate = invalidate
|
482
513
|
to_reset = rescan + invalidate
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
514
|
+
each_scanner do |scanner, stratum|
|
515
|
+
# If it is going to be always invalidated, it doesn't need further
|
516
|
+
# examination. Lattice scanners also don't get invalidated.
|
517
|
+
next if dflt_rescan.member? scanner
|
518
|
+
next if scanner.class <= LatticeScanner
|
519
|
+
|
520
|
+
rescan = dflt_rescan.clone
|
521
|
+
invalidate = dflt_invalidate + [scanner.collection]
|
522
|
+
rescan_invalidate_tc(stratum, rescan, invalidate)
|
523
|
+
prune_rescan_set(rescan)
|
524
|
+
|
525
|
+
# Make sure we reset the rescan/invalidate flag for this scanner at
|
526
|
+
# end-of-tick, but we can remove the scanner from its own
|
527
|
+
# rescan_set/inval_set.
|
528
|
+
to_reset.merge(rescan)
|
529
|
+
to_reset.merge(invalidate)
|
530
|
+
rescan.delete(scanner)
|
531
|
+
invalidate.delete(scanner.collection)
|
532
|
+
|
533
|
+
# Give the diffs (from default) to scanner; these are elements that are
|
534
|
+
# dependent on this scanner
|
535
|
+
diffscan = (rescan - dflt_rescan).find_all {|elem| elem.class <= PushElement}
|
536
|
+
scanner.invalidate_at_tick(diffscan, (invalidate - dflt_invalidate).to_a)
|
500
537
|
end
|
501
538
|
@reset_list = to_reset.to_a
|
539
|
+
|
540
|
+
# For each lattice, find the collections that should be rescanned when there
|
541
|
+
# is a new delta for the lattice. That is, if we have a rule like:
|
542
|
+
# "t2 <= t1 {|t| [t.key, lat_foo]}", whenever there is a delta on lat_foo we
|
543
|
+
# should rescan t1 (to produce tuples with the updated lat_foo value).
|
544
|
+
#
|
545
|
+
# TODO:
|
546
|
+
# (1) if t1 is fed by rules r1 and r2 but only r1 references lattice x,
|
547
|
+
# don't trigger rescan of r2 on deltas for x (hard)
|
548
|
+
t_depends.each do |dep|
|
549
|
+
src, target_name = dep.body.to_sym, dep.lhs.to_sym
|
550
|
+
if @lattices.has_key? src and dep.in_body
|
551
|
+
src_lat = @lattices[src]
|
552
|
+
if @tables.has_key? target_name
|
553
|
+
target = @tables[target_name]
|
554
|
+
else
|
555
|
+
target = @lattices[target_name]
|
556
|
+
end
|
557
|
+
|
558
|
+
# Conservatively, we rescan all the elements that feed the lhs (target)
|
559
|
+
# collection via positive (non-deletion) rules; we then also need to
|
560
|
+
# potentially rescan ancestors of those elements as well (e.g., setting
|
561
|
+
# a stateless PushElement to rescan does nothing; we want to tell its
|
562
|
+
# ancestor ScannerElement to rescan).
|
563
|
+
#
|
564
|
+
# XXX: do we need to consider all transitively reachable nodes for
|
565
|
+
# rescan?
|
566
|
+
lat_rescan = target.positive_predecessors.to_set
|
567
|
+
lat_inval = Set.new
|
568
|
+
target.positive_predecessors.each do |e|
|
569
|
+
e.add_rescan_invalidate(lat_rescan, lat_inval)
|
570
|
+
end
|
571
|
+
src_lat.rescan_on_delta.merge(lat_rescan)
|
572
|
+
end
|
573
|
+
end
|
502
574
|
end
|
503
575
|
|
504
|
-
#
|
576
|
+
# Given rescan, invalidate sets, compute transitive closure
|
505
577
|
def rescan_invalidate_tc(stratum, rescan, invalidate)
|
578
|
+
# XXX: hack. If there's nothing in the given stratum, don't do
|
579
|
+
# anything. This can arise if we have an orphan scanner whose input is a
|
580
|
+
# non-monotonic operator; the stratum(LHS) = stratum(RHS) + 1, but there's
|
581
|
+
# nothing else in stratum(LHS).
|
582
|
+
return if @push_sorted_elems[stratum].nil?
|
583
|
+
|
506
584
|
rescan_len = rescan.size
|
507
585
|
invalidate_len = invalidate.size
|
508
586
|
while true
|
@@ -515,12 +593,24 @@ module Bud
|
|
515
593
|
end
|
516
594
|
end
|
517
595
|
|
518
|
-
def
|
596
|
+
def prune_rescan_set(rescan)
|
519
597
|
rescan.delete_if {|e| e.rescan_at_tick}
|
520
598
|
end
|
521
599
|
|
600
|
+
def each_scanner
|
601
|
+
@num_strata.times do |stratum|
|
602
|
+
@scanners[stratum].each_value do |scanner|
|
603
|
+
yield scanner, stratum
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
@orphan_scanners.each do |scanner,stratum|
|
608
|
+
yield scanner, stratum
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
522
612
|
def do_rewrite
|
523
|
-
@meta_parser = BudMeta.new(self
|
613
|
+
@meta_parser = BudMeta.new(self)
|
524
614
|
@stratified_rules = @meta_parser.meta_rewrite
|
525
615
|
end
|
526
616
|
|
@@ -641,16 +731,30 @@ module Bud
|
|
641
731
|
# method blocks until Bud has been shutdown. If +stop_em+ is true, the
|
642
732
|
# EventMachine event loop is also shutdown; this will interfere with the
|
643
733
|
# execution of any other Bud instances in the same process (as well as
|
644
|
-
# anything else that happens to use EventMachine).
|
734
|
+
# anything else that happens to use EventMachine). We always shutdown the EM
|
735
|
+
# loop if there are no more running Bud instances (this does interfere with
|
736
|
+
# other EM-using apps, but it is necessary).
|
645
737
|
def stop(stop_em=false, do_shutdown_cb=true)
|
646
738
|
schedule_and_wait do
|
647
739
|
do_shutdown(do_shutdown_cb)
|
648
740
|
end
|
649
741
|
|
742
|
+
# If we're shutting down the last active Bud instance, shutdown the EM event
|
743
|
+
# loop as well. This is probably good practice in general, but it also
|
744
|
+
# prevents weird EM behavior -- it seems as though EM::ConnectionNotBound
|
745
|
+
# exceptions can be raised if the EM event loop is left running and
|
746
|
+
# subsequent events arrive.
|
747
|
+
$signal_lock.synchronize {
|
748
|
+
stop_em = true if $bud_instances.empty? and EventMachine::reactor_running?
|
749
|
+
}
|
750
|
+
|
650
751
|
if stop_em
|
651
752
|
Bud.stop_em_loop
|
652
|
-
EventMachine::reactor_thread
|
753
|
+
unless Thread.current == EventMachine::reactor_thread
|
754
|
+
EventMachine::reactor_thread.join
|
755
|
+
end
|
653
756
|
end
|
757
|
+
|
654
758
|
report_metrics if options[:metrics]
|
655
759
|
end
|
656
760
|
alias :stop_bg :stop
|
@@ -962,10 +1066,42 @@ module Bud
|
|
962
1066
|
end
|
963
1067
|
bootstrap
|
964
1068
|
|
965
|
-
|
1069
|
+
if toplevel == self
|
1070
|
+
@tables.each_value {|t| t.bootstrap}
|
1071
|
+
@lattices.each_value {|l| l.bootstrap}
|
1072
|
+
end
|
966
1073
|
@done_bootstrap = true
|
967
1074
|
end
|
968
1075
|
|
1076
|
+
def do_invalidate_rescan
|
1077
|
+
@default_rescan.each {|elem| elem.rescan = true}
|
1078
|
+
@default_invalidate.each {|elem|
|
1079
|
+
elem.invalidated = true
|
1080
|
+
# Call tick on tables here itself. The rest below
|
1081
|
+
elem.invalidate_cache unless elem.class <= PushElement
|
1082
|
+
}
|
1083
|
+
|
1084
|
+
# The following loop invalidates additional (non-default) elements and
|
1085
|
+
# tables that depend on the run-time invalidation state of a table. Loop
|
1086
|
+
# once to set the flags.
|
1087
|
+
each_scanner do |scanner, stratum|
|
1088
|
+
if scanner.rescan
|
1089
|
+
scanner.rescan_set.each {|e| e.rescan = true}
|
1090
|
+
scanner.invalidate_set.each {|e|
|
1091
|
+
e.invalidated = true
|
1092
|
+
e.invalidate_cache unless e.class <= PushElement
|
1093
|
+
}
|
1094
|
+
end
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
# Loop a second time to actually call invalidate_cache. We can't merge this
|
1098
|
+
# with the loops above because some versions of invalidate_cache (e.g.,
|
1099
|
+
# join) depend on the rescan state of other elements.
|
1100
|
+
@num_strata.times do |stratum|
|
1101
|
+
@push_sorted_elems[stratum].each {|e| e.invalidate_cache if e.invalidated}
|
1102
|
+
end
|
1103
|
+
end
|
1104
|
+
|
969
1105
|
# One timestep of Bloom execution. This MUST be invoked from the EventMachine
|
970
1106
|
# thread; it is not intended to be called directly by client code.
|
971
1107
|
def tick_internal
|
@@ -985,32 +1121,7 @@ module Bud
|
|
985
1121
|
else
|
986
1122
|
# inform tables and elements about beginning of tick.
|
987
1123
|
@app_tables.each {|t| t.tick}
|
988
|
-
|
989
|
-
@default_invalidate.each {|elem|
|
990
|
-
elem.invalidated = true
|
991
|
-
# Call tick on tables here itself. The rest below
|
992
|
-
elem.invalidate_cache unless elem.class <= PushElement
|
993
|
-
}
|
994
|
-
|
995
|
-
num_strata = @push_sorted_elems.size
|
996
|
-
# The following loop invalidates additional (non-default) elements and
|
997
|
-
# tables that depend on the run-time invalidation state of a table.
|
998
|
-
# Loop once to set the flags.
|
999
|
-
num_strata.times do |stratum|
|
1000
|
-
@scanners[stratum].each_value do |scanner|
|
1001
|
-
if scanner.rescan
|
1002
|
-
scanner.rescan_set.each {|e| e.rescan = true}
|
1003
|
-
scanner.invalidate_set.each {|e|
|
1004
|
-
e.invalidated = true
|
1005
|
-
e.invalidate_cache unless e.class <= PushElement
|
1006
|
-
}
|
1007
|
-
end
|
1008
|
-
end
|
1009
|
-
end
|
1010
|
-
# Loop a second time to actually call invalidate_cache
|
1011
|
-
num_strata.times do |stratum|
|
1012
|
-
@push_sorted_elems[stratum].each {|e| e.invalidate_cache if e.invalidated}
|
1013
|
-
end
|
1124
|
+
do_invalidate_rescan
|
1014
1125
|
end
|
1015
1126
|
|
1016
1127
|
receive_inbound
|
@@ -1074,14 +1185,14 @@ module Bud
|
|
1074
1185
|
end
|
1075
1186
|
|
1076
1187
|
# Return the stratum number of the given collection.
|
1077
|
-
# NB: if a collection
|
1078
|
-
# assigned to a strata.
|
1188
|
+
# NB: if a collection does not appear on the lhs or rhs of any rules, it is
|
1189
|
+
# not currently assigned to a strata.
|
1079
1190
|
def collection_stratum(collection)
|
1080
1191
|
t_stratum.each do |t|
|
1081
1192
|
return t.stratum if t.predicate == collection
|
1082
1193
|
end
|
1083
1194
|
|
1084
|
-
|
1195
|
+
return nil
|
1085
1196
|
end
|
1086
1197
|
|
1087
1198
|
private
|
@@ -1099,25 +1210,26 @@ module Bud
|
|
1099
1210
|
@periodics = table :periodics_tbl, [:pername] => [:period]
|
1100
1211
|
|
1101
1212
|
# for BUD reflection
|
1102
|
-
table :
|
1103
|
-
table :t_depends, [:bud_obj, :rule_id, :lhs, :op, :body] => [:nm]
|
1213
|
+
table :t_cycle, [:predicate, :via, :neg, :temporal]
|
1214
|
+
table :t_depends, [:bud_obj, :rule_id, :lhs, :op, :body] => [:nm, :in_body]
|
1104
1215
|
table :t_provides, [:interface] => [:input]
|
1105
|
-
table :
|
1216
|
+
table :t_rule_stratum, [:bud_obj, :rule_id] => [:stratum]
|
1217
|
+
table :t_rules, [:bud_obj, :rule_id] => [:lhs, :op, :src, :orig_src, :unsafe_funcs_called]
|
1106
1218
|
table :t_stratum, [:predicate] => [:stratum]
|
1107
|
-
table :t_cycle, [:predicate, :via, :neg, :temporal]
|
1108
1219
|
table :t_table_info, [:tab_name, :tab_type]
|
1109
1220
|
table :t_table_schema, [:tab_name, :col_name, :ord, :loc]
|
1221
|
+
table :t_underspecified, t_provides.schema
|
1110
1222
|
|
1111
1223
|
# Identify builtin tables as such
|
1112
1224
|
@builtin_tables = @tables.clone if toplevel
|
1113
1225
|
end
|
1114
1226
|
|
1115
|
-
# Handle
|
1116
|
-
# directly into the storage of the appropriate local
|
1117
|
-
# queue is cleared at the end of the tick.
|
1227
|
+
# Handle external inputs: channels, terminals, and periodics. Received
|
1228
|
+
# messages are placed directly into the storage of the appropriate local
|
1229
|
+
# collection. The inbound queue is cleared at the end of the tick.
|
1118
1230
|
def receive_inbound
|
1119
1231
|
@inbound.each do |tbl_name, msg_buf|
|
1120
|
-
puts "
|
1232
|
+
puts "recv via #{tbl_name}: #{msg_buf}" if $BUD_DEBUG
|
1121
1233
|
msg_buf.each do |b|
|
1122
1234
|
tables[tbl_name] << b
|
1123
1235
|
end
|
@@ -1142,17 +1254,20 @@ module Bud
|
|
1142
1254
|
# of PushElements
|
1143
1255
|
@this_stratum = strat_num
|
1144
1256
|
rules.each_with_index do |rule, i|
|
1145
|
-
|
1257
|
+
# user-supplied code blocks will be evaluated in this context at run-time
|
1258
|
+
@this_rule_context = rule.bud_obj
|
1146
1259
|
begin
|
1147
1260
|
eval_rule(rule.bud_obj, rule.src)
|
1148
1261
|
rescue Exception => e
|
1149
|
-
err_msg = "** Exception while wiring rule: #{rule.
|
1262
|
+
err_msg = "** Exception while wiring rule: #{rule.orig_src}\n ****** #{e}"
|
1150
1263
|
# Create a new exception for accomodating err_msg, but reuse original backtrace
|
1151
1264
|
new_e = (e.class <= Bud::Error) ? e.class.new(err_msg) : Bud::Error.new(err_msg)
|
1152
1265
|
new_e.set_backtrace(e.backtrace)
|
1153
1266
|
raise new_e
|
1154
1267
|
end
|
1155
1268
|
end
|
1269
|
+
@this_rule_context = nil
|
1270
|
+
@this_stratum = -1
|
1156
1271
|
end
|
1157
1272
|
|
1158
1273
|
######## ids and timers
|
@@ -1183,10 +1298,10 @@ module Bud
|
|
1183
1298
|
EventMachine::release_machine
|
1184
1299
|
EventMachine::instance_variable_set('@reactor_running', false)
|
1185
1300
|
end
|
1301
|
+
|
1186
1302
|
# Shutdown all the Bud instances inherited from the parent process, but
|
1187
1303
|
# don't invoke their shutdown callbacks
|
1188
1304
|
Bud.shutdown_all_instances(false)
|
1189
|
-
|
1190
1305
|
$got_shutdown_signal = false
|
1191
1306
|
$signal_handler_setup = false
|
1192
1307
|
|
@@ -1206,16 +1321,16 @@ module Bud
|
|
1206
1321
|
end
|
1207
1322
|
|
1208
1323
|
# Signal handling. If multiple Bud instances are running inside a single
|
1209
|
-
# process, we want a SIGINT or SIGTERM signal to cleanly shutdown all of
|
1324
|
+
# process, we want a SIGINT or SIGTERM signal to cleanly shutdown all of
|
1325
|
+
# them. Note that we don't try to do any significant work in the signal
|
1326
|
+
# handlers themselves: we just set a flag that is checked by a periodic timer.
|
1210
1327
|
def self.init_signal_handlers(b)
|
1211
1328
|
$signal_lock.synchronize {
|
1212
|
-
#
|
1213
|
-
# reinitialize the signal handler in the child process.
|
1329
|
+
# Initialize or re-initialize signal handlers if necessary.
|
1214
1330
|
unless b.options[:signal_handling] == :none || $signal_handler_setup
|
1215
1331
|
EventMachine::PeriodicTimer.new(SIGNAL_CHECK_PERIOD) do
|
1216
1332
|
if $got_shutdown_signal
|
1217
1333
|
Bud.shutdown_all_instances
|
1218
|
-
Bud.stop_em_loop
|
1219
1334
|
$got_shutdown_signal = false
|
1220
1335
|
end
|
1221
1336
|
end
|