asir 0.2.0 → 1.0.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.
- data/Gemfile +1 -2
- data/README.textile +4 -2
- data/VERSION +1 -1
- data/asir.gemspec +1 -4
- data/asir.riterate.yml +1 -0
- data/bin/asir +2 -1
- data/example/asir_control.sh +63 -1
- data/example/asir_control_client_http.rb +2 -2
- data/example/asir_control_client_resque.rb +16 -0
- data/example/asir_control_client_zmq.rb +3 -3
- data/example/config/asir_config.rb +20 -8
- data/example/ex02.rb +1 -1
- data/example/ex03.rb +2 -2
- data/example/ex04.rb +2 -2
- data/example/ex05.rb +1 -1
- data/example/ex06.rb +6 -5
- data/example/ex07.rb +2 -2
- data/example/ex08.rb +2 -2
- data/example/ex09.rb +2 -2
- data/example/ex10.rb +2 -2
- data/example/ex11.rb +5 -5
- data/example/ex12.rb +6 -6
- data/example/ex13.rb +4 -4
- data/example/ex14.rb +4 -4
- data/example/ex15.rb +2 -2
- data/example/ex16.rb +8 -8
- data/example/ex17.rb +12 -11
- data/example/ex18.rb +5 -5
- data/example/ex19.rb +3 -3
- data/example/ex20.rb +3 -3
- data/example/ex21.rb +3 -3
- data/example/ex22.rb +1 -1
- data/example/ex23.rb +2 -2
- data/example/ex24.rb +4 -4
- data/example/ex25.rb +41 -0
- data/example/example_helper.rb +38 -3
- data/example/sample_service.rb +4 -4
- data/hack_night/exercise/prob-3.rb +3 -3
- data/hack_night/exercise/prob-6.rb +2 -2
- data/hack_night/exercise/prob-7.rb +2 -2
- data/hack_night/solution/prob-2.rb +2 -2
- data/hack_night/solution/prob-3.rb +3 -3
- data/hack_night/solution/prob-6.rb +7 -6
- data/hack_night/solution/prob-7.rb +6 -6
- data/{spec → lab}/const_get_speed_spec.rb +0 -0
- data/lib/asir.rb +29 -7
- data/lib/asir/additional_data.rb +25 -0
- data/lib/asir/channel.rb +4 -5
- data/lib/asir/client.rb +29 -13
- data/lib/asir/config.rb +8 -0
- data/lib/asir/description.rb +34 -0
- data/lib/asir/environment.rb +96 -0
- data/lib/asir/error.rb +4 -1
- data/lib/asir/invoker.rb +14 -0
- data/lib/asir/main.rb +84 -103
- data/lib/asir/message.rb +1 -1
- data/lib/asir/poll_throttle.rb +53 -0
- data/lib/asir/retry_behavior.rb +1 -1
- data/lib/asir/thread_variable.rb +183 -0
- data/lib/asir/transport.rb +36 -23
- data/lib/asir/transport/beanstalk.rb +18 -52
- data/lib/asir/transport/conduit.rb +42 -0
- data/lib/asir/transport/connection_oriented.rb +32 -56
- data/lib/asir/transport/delegation.rb +5 -5
- data/lib/asir/transport/demux.rb +33 -0
- data/lib/asir/transport/file.rb +5 -3
- data/lib/asir/transport/payload_io.rb +8 -4
- data/lib/asir/transport/resque.rb +212 -0
- data/lib/asir/transport/stream.rb +19 -9
- data/lib/asir/transport/tcp_socket.rb +3 -2
- data/lib/asir/transport/zmq.rb +14 -17
- data/lib/asir/uri_config.rb +51 -0
- data/lib/asir/version.rb +1 -1
- data/spec/client_spec.rb +48 -0
- data/spec/demux_spec.rb +38 -0
- data/spec/json_spec.rb +0 -2
- data/spec/message_spec.rb +68 -0
- data/spec/performance_spec.rb +66 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/thread_variable_spec.rb +135 -0
- data/spec/transport_spec.rb +82 -0
- metadata +28 -4
data/lib/asir/error.rb
CHANGED
@@ -7,6 +7,9 @@ module ASIR
|
|
7
7
|
# Unsupported Feature.
|
8
8
|
class Unsupported < self; end
|
9
9
|
|
10
|
+
# Unimplemented Feature
|
11
|
+
class Unimplemented < self; end
|
12
|
+
|
10
13
|
# Requested Stop.
|
11
14
|
class Terminate < self; end
|
12
15
|
|
@@ -28,7 +31,7 @@ module ASIR
|
|
28
31
|
end
|
29
32
|
def self.unforwardable; @@unforwardable; end
|
30
33
|
def self.unforwardable= x; @@unforwardable = x; end
|
31
|
-
@@unforwardable ||= [ ::SystemExit, ::Interrupt ]
|
34
|
+
@@unforwardable ||= [ ::SystemExit, ::Interrupt, ::SignalException ]
|
32
35
|
end
|
33
36
|
end
|
34
37
|
end
|
data/lib/asir/invoker.rb
ADDED
data/lib/asir/main.rb
CHANGED
@@ -1,41 +1,36 @@
|
|
1
1
|
require 'asir'
|
2
|
+
require 'asir/environment'
|
2
3
|
require 'time'
|
3
4
|
|
4
|
-
|
5
5
|
module ASIR
|
6
6
|
class Main
|
7
|
-
attr_accessor :
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
attr_accessor :env, :args, :exit_code
|
8
|
+
# Delegate getter/setters to @env.
|
9
|
+
[ :verb, :adjective, :object, :identifier,
|
10
|
+
:config_rb,
|
11
|
+
:verbose,
|
12
|
+
:log_dir, :log_file,
|
13
|
+
:pid_dir, :pid_file,
|
14
|
+
].
|
15
|
+
map{|g| [ g, :"#{g}=" ]}.
|
16
|
+
flatten.each do | m |
|
17
|
+
define_method(m) { | *args | @env.send(m, *args) }
|
18
|
+
end
|
19
|
+
attr_accessor :progname
|
20
|
+
# Options:
|
21
|
+
attr_accessor :force
|
12
22
|
|
13
|
-
|
14
|
-
|
15
|
-
@progname = File.basename($0)
|
16
|
-
@log_dir = find_writable_directory :log_dir,
|
17
|
-
ENV['ASIR_LOG_DIR'],
|
18
|
-
'/var/log/asir',
|
19
|
-
'~/asir/log',
|
20
|
-
'/tmp'
|
21
|
-
@pid_dir = find_writable_directory :pid_dir,
|
22
|
-
ENV['ASIR_PID_DIR'],
|
23
|
-
'/var/run/asir',
|
24
|
-
'~/asir/run',
|
25
|
-
'/tmp'
|
26
|
-
@exit_code = 0
|
27
|
-
end
|
23
|
+
# Transport selected from asir.phase = :transport.
|
24
|
+
attr_accessor :transport
|
28
25
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
find { | p | File.writable?(p) } or
|
34
|
-
raise "Cannot find writable directory for #{kind}"
|
26
|
+
def initialize
|
27
|
+
self.env = ASIR::Environment.new
|
28
|
+
self.progname = File.basename($0)
|
29
|
+
self.exit_code = 0
|
35
30
|
end
|
36
31
|
|
37
32
|
def parse_args! args = ARGV.dup
|
38
|
-
|
33
|
+
self.args = args
|
39
34
|
until args.empty?
|
40
35
|
case args.first
|
41
36
|
when /^([a-z0-9_]+=)(.*)/i
|
@@ -47,25 +42,29 @@ class Main
|
|
47
42
|
break
|
48
43
|
end
|
49
44
|
end
|
50
|
-
|
51
|
-
|
45
|
+
self.verb, self.adjective, self.object, self.identifier = args.map{|x| x.to_sym}
|
46
|
+
self.identifier ||= :'0'
|
52
47
|
self
|
53
48
|
end
|
54
49
|
|
50
|
+
def config! *args
|
51
|
+
@env.config! *args
|
52
|
+
end
|
53
|
+
|
55
54
|
def log_str
|
56
55
|
"#{Time.now.gmtime.iso8601(4)} #{$$} #{log_str_no_time}"
|
57
56
|
end
|
58
57
|
|
59
58
|
def log_str_no_time
|
60
|
-
"#{
|
59
|
+
"#{progname} #{verb} #{adjective} #{object} #{identifier}"
|
61
60
|
end
|
62
61
|
|
63
62
|
def run!
|
64
63
|
unless verb && adjective && object
|
65
|
-
|
64
|
+
self.exit_code = 1
|
66
65
|
return usage!
|
67
66
|
end
|
68
|
-
config!(:
|
67
|
+
config!(:configure)
|
69
68
|
# $stderr.puts "log_file = #{log_file.inspect}"
|
70
69
|
case self.verb
|
71
70
|
when :restart
|
@@ -79,28 +78,28 @@ class Main
|
|
79
78
|
self
|
80
79
|
rescue ::Exception => exc
|
81
80
|
$stderr.puts "#{log_str} ERROR\n#{exc.inspect}\n #{exc.backtrace * "\n "}"
|
82
|
-
|
81
|
+
self.exit_code += 1
|
83
82
|
self
|
84
83
|
end
|
85
84
|
|
86
85
|
def _run_verb!
|
87
86
|
sel = :"#{verb}_#{adjective}_#{object}!"
|
88
|
-
if
|
89
|
-
$stderr.puts "verb = #{verb.inspect}"
|
90
|
-
$stderr.puts "adjective = #{adjective.inspect}"
|
91
|
-
$stderr.puts "object = #{object.inspect}"
|
92
|
-
$stderr.puts "sel = #{sel.inspect}"
|
87
|
+
if verbose >= 3
|
88
|
+
$stderr.puts " verb = #{verb.inspect}"
|
89
|
+
$stderr.puts " adjective = #{adjective.inspect}"
|
90
|
+
$stderr.puts " object = #{object.inspect}"
|
91
|
+
$stderr.puts " sel = #{sel.inspect}"
|
93
92
|
end
|
94
93
|
send(sel)
|
95
94
|
rescue ::Exception => exc
|
96
95
|
$stderr.puts "#{log_str} ERROR\n#{exc.inspect}\n #{exc.backtrace * "\n "}"
|
97
|
-
|
96
|
+
self.exit_code += 1
|
98
97
|
raise
|
99
98
|
nil
|
100
99
|
end
|
101
100
|
|
102
101
|
def method_missing sel, *args
|
103
|
-
log "method_missing #{sel}" if
|
102
|
+
log "method_missing #{sel}" if verbose >= 3
|
104
103
|
case sel.to_s
|
105
104
|
when /^start_([^_]+)_worker!$/
|
106
105
|
_start_worker!
|
@@ -113,7 +112,14 @@ class Main
|
|
113
112
|
when /^taillog_([^_]+)_([^_]+)!$/
|
114
113
|
exec "tail -f #{log_file.inspect}"
|
115
114
|
when /^pid_([^_]+)_([^_]+)!$/
|
116
|
-
|
115
|
+
pid = server_pid rescue nil
|
116
|
+
alive = process_running? pid
|
117
|
+
puts "#{pid_file} #{pid || :NA} #{alive}"
|
118
|
+
when /^alive_([^_]+)_([^_]+)!$/
|
119
|
+
pid = server_pid rescue nil
|
120
|
+
alive = process_running? pid
|
121
|
+
puts "#{pid_file} #{pid || :NA} #{alive}" if @verbose
|
122
|
+
self.exit_code += 1 unless alive
|
117
123
|
when /^stop_([^_]+)_([^_]+)!$/
|
118
124
|
kill_server!
|
119
125
|
else
|
@@ -139,12 +145,15 @@ VERBS:
|
|
139
145
|
status
|
140
146
|
log
|
141
147
|
pid
|
148
|
+
alive
|
142
149
|
|
143
150
|
ADJECTIVE-OBJECTs:
|
144
151
|
beanstalk conduit
|
145
152
|
beanstalk worker
|
146
153
|
zmq worker
|
147
154
|
webrick worker
|
155
|
+
resque conduit
|
156
|
+
resque worker
|
148
157
|
|
149
158
|
EXAMPLES:
|
150
159
|
|
@@ -153,6 +162,7 @@ EXAMPLES:
|
|
153
162
|
asir status beanstalk conduit
|
154
163
|
|
155
164
|
asir start webrick worker
|
165
|
+
asir pid webrick worker
|
156
166
|
|
157
167
|
asir start beanstalk worker 1
|
158
168
|
asir start beanstalk worker 2
|
@@ -164,7 +174,19 @@ END
|
|
164
174
|
end
|
165
175
|
|
166
176
|
def start_beanstalk_conduit!
|
167
|
-
|
177
|
+
_start_conduit!
|
178
|
+
end
|
179
|
+
|
180
|
+
def start_resque_conduit!
|
181
|
+
_start_conduit!
|
182
|
+
end
|
183
|
+
|
184
|
+
def _start_conduit!
|
185
|
+
config!(:environment)
|
186
|
+
self.transport = config!(:transport)
|
187
|
+
fork_server! do
|
188
|
+
transport.start_conduit! :fork => false
|
189
|
+
end
|
168
190
|
end
|
169
191
|
|
170
192
|
def _start_worker! type = adjective
|
@@ -179,54 +201,6 @@ END
|
|
179
201
|
end
|
180
202
|
end
|
181
203
|
|
182
|
-
################################################################
|
183
|
-
|
184
|
-
def config_rb
|
185
|
-
@config_rb ||=
|
186
|
-
File.expand_path(ENV['ASIR_CONFIG_RB'] || 'config/asir_config.rb')
|
187
|
-
end
|
188
|
-
|
189
|
-
def config_lambda
|
190
|
-
@config_lambda ||=
|
191
|
-
begin
|
192
|
-
file = config_rb
|
193
|
-
$stderr.puts "#{log_str} loading #{file} ..." if @verbose >= 1
|
194
|
-
expr = File.read(file)
|
195
|
-
expr = "begin; lambda do | asir |; #{expr}\n end; end"
|
196
|
-
cfg = Object.new.send(:eval, expr, binding, file, 1)
|
197
|
-
# cfg = load file
|
198
|
-
# $stderr.puts "#{log_str} loading #{file} DONE" if @verbose >= 1
|
199
|
-
raise "#{file} did not return a Proc, returned a #{cfg.class}" unless Proc === cfg
|
200
|
-
cfg
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def config! verb = @verb
|
205
|
-
(@config ||= { })[verb] ||=
|
206
|
-
begin
|
207
|
-
save_verb = @verb
|
208
|
-
@verb = verb
|
209
|
-
$stderr.puts "#{log_str} calling #{config_rb} asir.verb=#{@verb.inspect} ..." if @verbose >= 1
|
210
|
-
cfg = config_lambda.call(self)
|
211
|
-
$stderr.puts "#{log_str} calling #{config_rb} asir.verb=#{@verb.inspect} DONE" if @verbose >= 1
|
212
|
-
cfg
|
213
|
-
ensure
|
214
|
-
@verb = save_verb
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
def pid_file
|
219
|
-
"#{pid_dir}/#{asir_basename}.pid"
|
220
|
-
end
|
221
|
-
|
222
|
-
def log_file
|
223
|
-
"#{log_dir}/#{asir_basename}.log"
|
224
|
-
end
|
225
|
-
|
226
|
-
def asir_basename
|
227
|
-
"asir-#{adjective}-#{object}-#{identifier}"
|
228
|
-
end
|
229
|
-
|
230
204
|
def fork_server! cmd = nil, &blk
|
231
205
|
pid = Process.fork do
|
232
206
|
run_server! cmd, &blk
|
@@ -301,22 +275,22 @@ END
|
|
301
275
|
config!(:environment)
|
302
276
|
case transport = config!(:transport)
|
303
277
|
when default_class
|
304
|
-
|
278
|
+
self.transport = transport
|
305
279
|
else
|
306
280
|
raise "Expected config to return a #{default_class}, not a #{transport.class}"
|
307
281
|
end
|
308
282
|
end
|
309
283
|
|
310
284
|
def worker_pids
|
311
|
-
(@worker_pids ||= { })[
|
285
|
+
(@worker_pids ||= { })[adjective] ||= { }
|
312
286
|
end
|
313
287
|
|
314
288
|
def _run_workers!
|
315
|
-
$0 = "#{
|
289
|
+
$0 = "#{progname} #{adjective} #{object} #{identifier}"
|
316
290
|
|
317
291
|
worker_id = 0
|
318
|
-
|
319
|
-
worker_processes =
|
292
|
+
transport.prepare_server!
|
293
|
+
worker_processes = transport[:worker_processes] || 1
|
320
294
|
(worker_processes - 1).times do
|
321
295
|
wid = worker_id += 1
|
322
296
|
pid = Process.fork do
|
@@ -334,16 +308,16 @@ END
|
|
334
308
|
end
|
335
309
|
|
336
310
|
def _run_transport_server! wid = 0
|
337
|
-
log "running transport worker #{
|
311
|
+
log "running transport worker #{transport.class} #{wid}"
|
338
312
|
config!(:start)
|
339
|
-
$0 += " #{wid} #{
|
313
|
+
$0 += " #{wid} #{transport.uri rescue nil}"
|
340
314
|
old_arg0 = $0.dup
|
341
|
-
after_receive_message =
|
342
|
-
|
315
|
+
after_receive_message = transport.after_receive_message || lambda { | transport, message | nil }
|
316
|
+
transport.after_receive_message = lambda do | transport, message |
|
343
317
|
$0 = "#{old_arg0} #{transport.message_count} #{message.identifier}"
|
344
318
|
after_receive_message.call(transport, message)
|
345
319
|
end
|
346
|
-
|
320
|
+
transport.run_server!
|
347
321
|
self
|
348
322
|
end
|
349
323
|
|
@@ -368,7 +342,7 @@ END
|
|
368
342
|
log "TERM pid #{pid}"
|
369
343
|
Process.kill('TERM', pid) rescue nil
|
370
344
|
sleep 3
|
371
|
-
if
|
345
|
+
if force or process_running? pid
|
372
346
|
log "KILL pid #{pid}", :stderr
|
373
347
|
Process.kill('KILL', pid) rescue nil
|
374
348
|
end
|
@@ -381,7 +355,14 @@ END
|
|
381
355
|
end
|
382
356
|
|
383
357
|
def process_running? pid
|
384
|
-
|
358
|
+
case pid
|
359
|
+
when false, nil
|
360
|
+
pid
|
361
|
+
when Integer
|
362
|
+
Process.kill(0, pid)
|
363
|
+
else
|
364
|
+
raise TypeError, "expected false, nil, Integer; given #{pid.inspect}"
|
365
|
+
end
|
385
366
|
true
|
386
367
|
rescue ::Errno::ESRCH
|
387
368
|
false
|
data/lib/asir/message.rb
CHANGED
@@ -24,7 +24,7 @@ module ASIR
|
|
24
24
|
@result = Result.new(self, nil, exc)
|
25
25
|
end
|
26
26
|
|
27
|
-
# Optional: Specifies the Numeric seconds or absolute Time to delay the Message until actual
|
27
|
+
# Optional: Specifies the Numeric seconds or absolute Time for the Transport to delay the Message until actual invocation.
|
28
28
|
attr_accessor :delay
|
29
29
|
end
|
30
30
|
# !SLIDE END
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'asir'
|
2
|
+
|
3
|
+
module ASIR
|
4
|
+
module PollThrottle
|
5
|
+
# Polls block until non-nil block result.
|
6
|
+
# If non-nil, retry after sleeping for s sec, which starts at opts[:min_sleep].
|
7
|
+
# Will retry up to opts[:max_tries] times, if defined.
|
8
|
+
# s is multiplied by opts[:mul_sleep] and incremented by opts[:inc_sleep], if defined.
|
9
|
+
# s is limited by opts[:max_sleep].
|
10
|
+
# s is adjusted by s * opts[:rand_sleep], if defined.
|
11
|
+
# Returns result yield from block.
|
12
|
+
def poll_throttle opts = nil
|
13
|
+
opts ||= { }
|
14
|
+
i = 0
|
15
|
+
s = opts[:min_sleep] ||= 0.01
|
16
|
+
opts[:max_sleep] ||= 60
|
17
|
+
# opts[:inc_sleep] ||= 1
|
18
|
+
# opts[:mul_sleep] ||= 1.5
|
19
|
+
result = nil
|
20
|
+
loop do
|
21
|
+
i += 1
|
22
|
+
unless (result = yield).nil?
|
23
|
+
return result
|
24
|
+
end
|
25
|
+
if x = opts[:max_tries] and i >= x
|
26
|
+
return result
|
27
|
+
end
|
28
|
+
this_s = s
|
29
|
+
if x = opts[:rand_sleep]
|
30
|
+
this_s += s * rand * x
|
31
|
+
end
|
32
|
+
if opts[:verbose]
|
33
|
+
$stderr.puts " #{self}: poll_throttle: sleeping for #{this_s} sec"
|
34
|
+
end
|
35
|
+
sleep this_s if this_s > 0
|
36
|
+
if x = opts[:mul_sleep]
|
37
|
+
s *= x
|
38
|
+
end
|
39
|
+
if x = opts[:inc_sleep]
|
40
|
+
s += x
|
41
|
+
end
|
42
|
+
if x = opts[:max_sleep] and s > x
|
43
|
+
s = x
|
44
|
+
end
|
45
|
+
if x = opts[:min_sleep] and s < x
|
46
|
+
s = x
|
47
|
+
end
|
48
|
+
end
|
49
|
+
result
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
data/lib/asir/retry_behavior.rb
CHANGED
@@ -32,7 +32,7 @@ module ASIR
|
|
32
32
|
if ! try_max || try_max > n_try
|
33
33
|
yield :retry, exc
|
34
34
|
if sleep_secs
|
35
|
-
sleep sleep_secs
|
35
|
+
sleep sleep_secs if sleep_secs > 0
|
36
36
|
sleep_secs += try_sleep_increment if try_sleep_increment
|
37
37
|
sleep_secs = try_sleep_max if try_sleep_max && sleep_secs > try_sleep_max
|
38
38
|
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
module ASIR
|
2
|
+
|
3
|
+
# Adds Thread-based class and instance variables.
|
4
|
+
module ThreadVariable
|
5
|
+
def self.included target
|
6
|
+
super
|
7
|
+
target.instance_eval do
|
8
|
+
include CommonMethods
|
9
|
+
extend CommonMethods
|
10
|
+
extend ModuleMethods
|
11
|
+
end
|
12
|
+
end
|
13
|
+
DEBUG = false
|
14
|
+
|
15
|
+
module CommonMethods
|
16
|
+
# Yields to block while self.name = value.
|
17
|
+
# Restores self.name after yield.
|
18
|
+
def with_attr! name, value
|
19
|
+
save_value = send(name)
|
20
|
+
send(::ASIR::ThreadVariable.setter(name), value)
|
21
|
+
yield
|
22
|
+
ensure
|
23
|
+
send(::ASIR::ThreadVariable.setter(name), save_value)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.setter sym
|
28
|
+
SETTER[sym] ||= :"#{sym}="
|
29
|
+
end
|
30
|
+
SETTER = { }
|
31
|
+
|
32
|
+
module ModuleMethods
|
33
|
+
# Defines a Module or Class attribute stored in Thread.current.
|
34
|
+
def mattr_accessor_thread *names
|
35
|
+
mattr_getter_thread *names
|
36
|
+
mattr_setter_thread *names
|
37
|
+
end
|
38
|
+
alias :cattr_accessor_thread :mattr_accessor_thread
|
39
|
+
|
40
|
+
# Defines a Module or Class attribute setter stored in Thread.current.
|
41
|
+
def mattr_setter_thread *names
|
42
|
+
opts = Hash === names[-1] ? names.pop : EMPTY_HASH
|
43
|
+
|
44
|
+
transform = opts[:setter_transform]
|
45
|
+
transform = "__val = (#{transform})" if transform
|
46
|
+
|
47
|
+
names.each do | name |
|
48
|
+
instance_eval(expr = <<"END", __FILE__, __LINE__)
|
49
|
+
def self.#{name}= __val
|
50
|
+
#{transform}
|
51
|
+
Thread.current[:'#{self.name}.#{name}'] = [ __val ]
|
52
|
+
end
|
53
|
+
END
|
54
|
+
# $stderr.puts "#{expr}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
alias :cattr_setter_thread :mattr_setter_thread
|
58
|
+
|
59
|
+
# Defines a class attribute getter stored in Thread.current.
|
60
|
+
#
|
61
|
+
# Options:
|
62
|
+
#
|
63
|
+
# :initialize -- String: expression to initialize the variable is undefined.
|
64
|
+
# :default -- String: expression to return if the variable value if undefined.
|
65
|
+
# :transform -- String: expression to transform the __val variable before returning.
|
66
|
+
#
|
67
|
+
# Also defines clear_NAME method that undefines the thread variable.
|
68
|
+
def mattr_getter_thread *names
|
69
|
+
opts = Hash === names[-1] ? names.pop : EMPTY_HASH
|
70
|
+
|
71
|
+
initialize = opts[:initialize]
|
72
|
+
initialize = "||= [ #{initialize} ]" if initialize
|
73
|
+
|
74
|
+
default = opts[:default]
|
75
|
+
default = "__val ||= [ #{default} ]" if default
|
76
|
+
|
77
|
+
transform = opts[:transform]
|
78
|
+
transform = "__val = (#{transform})" if transform
|
79
|
+
|
80
|
+
names.each do | name |
|
81
|
+
instance_eval(expr = <<"END", __FILE__, __LINE__)
|
82
|
+
def self.clear_#{name}
|
83
|
+
Thread.current[:'#{self.name}.#{name}'] = nil
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.#{name}
|
88
|
+
__val = Thread.current[:'#{self.name}.#{name}'] #{initialize}
|
89
|
+
#{default}
|
90
|
+
__val &&= __val.first
|
91
|
+
#{transform}
|
92
|
+
__val
|
93
|
+
end
|
94
|
+
END
|
95
|
+
# $stderr.puts "#{expr}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
alias :cattr_getter_thread :mattr_getter_thread
|
99
|
+
|
100
|
+
# TODO: clear instance thread variables when instance is GCed.
|
101
|
+
def attr_accessor_thread *names
|
102
|
+
attr_getter_thread *names
|
103
|
+
attr_setter_thread *names
|
104
|
+
end
|
105
|
+
|
106
|
+
def attr_setter_thread *names
|
107
|
+
opts = Hash === names[-1] ? names.pop : EMPTY_HASH
|
108
|
+
|
109
|
+
transform = opts[:setter_transform]
|
110
|
+
transform = "__val = (#{transform})" if transform
|
111
|
+
|
112
|
+
names.each do | name |
|
113
|
+
expr = [ <<"END", __FILE__, __LINE__ ]
|
114
|
+
def clear_#{name}
|
115
|
+
((Thread.current[:'#{self.name}\#'] ||= { })[self.object_id] || { }).delete(:'#{name}')
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
def #{name}= __val
|
120
|
+
#{transform}
|
121
|
+
thread_attrs[:'#{name}'] = [ __val ]
|
122
|
+
end
|
123
|
+
END
|
124
|
+
$stderr.puts "expr::\n#{expr}\n====" if opts[:debug] || DEBUG
|
125
|
+
class_eval *expr
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def attr_getter_thread *names
|
130
|
+
opts = Hash === names[-1] ? names.pop : EMPTY_HASH
|
131
|
+
|
132
|
+
expr = [ <<"END", __FILE__, __LINE__ ]
|
133
|
+
def self.attr_thread_hash(obj)
|
134
|
+
thr = Thread.current
|
135
|
+
ObjectSpace.define_finalizer(obj) do | oid |
|
136
|
+
(thr[:'#{self.name}\#'] ||= { }).delete(oid)
|
137
|
+
end
|
138
|
+
{ }
|
139
|
+
end
|
140
|
+
|
141
|
+
def thread_attrs
|
142
|
+
(Thread.current[:'#{self.name}\#'] ||= { })[self.object_id] ||= #{self.name}.attr_thread_hash(self)
|
143
|
+
end
|
144
|
+
|
145
|
+
def attr_thread_clear_all! oid = self.object_id
|
146
|
+
(Thread.current[:'#{self.name}\#'] ||= { }).delete(oid)
|
147
|
+
end
|
148
|
+
END
|
149
|
+
$stderr.puts "expr::\n#{expr}\n====" if opts[:debug] || DEBUG
|
150
|
+
class_eval *expr
|
151
|
+
|
152
|
+
initialize = opts[:initialize]
|
153
|
+
if initialize
|
154
|
+
initialize = "||= { }"
|
155
|
+
end
|
156
|
+
|
157
|
+
default = opts[:default]
|
158
|
+
default = "__val ||= [ #{default} ]" if default
|
159
|
+
|
160
|
+
transform = opts[:transform]
|
161
|
+
transform = "__val = (#{transform})" if transform
|
162
|
+
|
163
|
+
names.each do | name |
|
164
|
+
expr = [ <<"END", __FILE__, __LINE__ ]
|
165
|
+
def #{name}
|
166
|
+
__val = (thread_attrs[:'#{name}'] #{initialize})
|
167
|
+
#{default}
|
168
|
+
__val &&= __val.first
|
169
|
+
#{transform}
|
170
|
+
__val
|
171
|
+
end
|
172
|
+
END
|
173
|
+
$stderr.puts "expr::\n#{expr}\n====" if opts[:debug] || DEBUG
|
174
|
+
class_eval *expr
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
end # module
|
179
|
+
|
180
|
+
EMPTY_HASH = { }.freeze
|
181
|
+
end # module
|
182
|
+
|
183
|
+
end # module
|