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.
Files changed (82) hide show
  1. data/Gemfile +1 -2
  2. data/README.textile +4 -2
  3. data/VERSION +1 -1
  4. data/asir.gemspec +1 -4
  5. data/asir.riterate.yml +1 -0
  6. data/bin/asir +2 -1
  7. data/example/asir_control.sh +63 -1
  8. data/example/asir_control_client_http.rb +2 -2
  9. data/example/asir_control_client_resque.rb +16 -0
  10. data/example/asir_control_client_zmq.rb +3 -3
  11. data/example/config/asir_config.rb +20 -8
  12. data/example/ex02.rb +1 -1
  13. data/example/ex03.rb +2 -2
  14. data/example/ex04.rb +2 -2
  15. data/example/ex05.rb +1 -1
  16. data/example/ex06.rb +6 -5
  17. data/example/ex07.rb +2 -2
  18. data/example/ex08.rb +2 -2
  19. data/example/ex09.rb +2 -2
  20. data/example/ex10.rb +2 -2
  21. data/example/ex11.rb +5 -5
  22. data/example/ex12.rb +6 -6
  23. data/example/ex13.rb +4 -4
  24. data/example/ex14.rb +4 -4
  25. data/example/ex15.rb +2 -2
  26. data/example/ex16.rb +8 -8
  27. data/example/ex17.rb +12 -11
  28. data/example/ex18.rb +5 -5
  29. data/example/ex19.rb +3 -3
  30. data/example/ex20.rb +3 -3
  31. data/example/ex21.rb +3 -3
  32. data/example/ex22.rb +1 -1
  33. data/example/ex23.rb +2 -2
  34. data/example/ex24.rb +4 -4
  35. data/example/ex25.rb +41 -0
  36. data/example/example_helper.rb +38 -3
  37. data/example/sample_service.rb +4 -4
  38. data/hack_night/exercise/prob-3.rb +3 -3
  39. data/hack_night/exercise/prob-6.rb +2 -2
  40. data/hack_night/exercise/prob-7.rb +2 -2
  41. data/hack_night/solution/prob-2.rb +2 -2
  42. data/hack_night/solution/prob-3.rb +3 -3
  43. data/hack_night/solution/prob-6.rb +7 -6
  44. data/hack_night/solution/prob-7.rb +6 -6
  45. data/{spec → lab}/const_get_speed_spec.rb +0 -0
  46. data/lib/asir.rb +29 -7
  47. data/lib/asir/additional_data.rb +25 -0
  48. data/lib/asir/channel.rb +4 -5
  49. data/lib/asir/client.rb +29 -13
  50. data/lib/asir/config.rb +8 -0
  51. data/lib/asir/description.rb +34 -0
  52. data/lib/asir/environment.rb +96 -0
  53. data/lib/asir/error.rb +4 -1
  54. data/lib/asir/invoker.rb +14 -0
  55. data/lib/asir/main.rb +84 -103
  56. data/lib/asir/message.rb +1 -1
  57. data/lib/asir/poll_throttle.rb +53 -0
  58. data/lib/asir/retry_behavior.rb +1 -1
  59. data/lib/asir/thread_variable.rb +183 -0
  60. data/lib/asir/transport.rb +36 -23
  61. data/lib/asir/transport/beanstalk.rb +18 -52
  62. data/lib/asir/transport/conduit.rb +42 -0
  63. data/lib/asir/transport/connection_oriented.rb +32 -56
  64. data/lib/asir/transport/delegation.rb +5 -5
  65. data/lib/asir/transport/demux.rb +33 -0
  66. data/lib/asir/transport/file.rb +5 -3
  67. data/lib/asir/transport/payload_io.rb +8 -4
  68. data/lib/asir/transport/resque.rb +212 -0
  69. data/lib/asir/transport/stream.rb +19 -9
  70. data/lib/asir/transport/tcp_socket.rb +3 -2
  71. data/lib/asir/transport/zmq.rb +14 -17
  72. data/lib/asir/uri_config.rb +51 -0
  73. data/lib/asir/version.rb +1 -1
  74. data/spec/client_spec.rb +48 -0
  75. data/spec/demux_spec.rb +38 -0
  76. data/spec/json_spec.rb +0 -2
  77. data/spec/message_spec.rb +68 -0
  78. data/spec/performance_spec.rb +66 -0
  79. data/spec/spec_helper.rb +34 -0
  80. data/spec/thread_variable_spec.rb +135 -0
  81. data/spec/transport_spec.rb +82 -0
  82. 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
@@ -0,0 +1,14 @@
1
+ module ASIR
2
+ # !SLIDE
3
+ # Invoker
4
+ #
5
+ # Invokes the Message or Exception on behalf of a Transport.
6
+ class Invoker
7
+ include Initialization, AdditionalData
8
+
9
+ def invoke! message, transport
10
+ message.invoke!
11
+ end
12
+ end
13
+ end
14
+
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 :verb, :adjective, :object, :identifier
8
- attr_accessor :config_rb, :config
9
- attr_accessor :log_dir, :pid_dir
10
- attr_accessor :verbose
11
- attr_accessor :exit_code
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
- def initialize
14
- @verbose = 0
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 find_writable_directory kind, *list
30
- list.
31
- reject { | p | ! p }.
32
- map { | p | File.expand_path(p) }.
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
- @args = args
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
- @verb, @adjective, @object, @identifier = args.map{|x| x.to_sym}
51
- @identifier ||= :'0'
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
- "#{@progname} #{@verb} #{@adjective} #{@object} #{@identifier}"
59
+ "#{progname} #{verb} #{adjective} #{object} #{identifier}"
61
60
  end
62
61
 
63
62
  def run!
64
63
  unless verb && adjective && object
65
- @exit_code = 1
64
+ self.exit_code = 1
66
65
  return usage!
67
66
  end
68
- config!(: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
- @exit_code += 1
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 @verbose >= 3
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
- @exit_code += 1
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 @verbose >= 3
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
- puts "#{pid_file} #{File.read(pid_file) rescue nil}"
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
- fork_server! "beanstalkd"
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
- @transport = transport
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 ||= { })[@adjective] ||= { }
285
+ (@worker_pids ||= { })[adjective] ||= { }
312
286
  end
313
287
 
314
288
  def _run_workers!
315
- $0 = "#{@progname} #{@adjective} #{@object} #{@identifier}"
289
+ $0 = "#{progname} #{adjective} #{object} #{identifier}"
316
290
 
317
291
  worker_id = 0
318
- @transport.prepare_server!
319
- worker_processes = @transport[:worker_processes] || 1
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 #{@transport.class} #{wid}"
311
+ log "running transport worker #{transport.class} #{wid}"
338
312
  config!(:start)
339
- $0 += " #{wid} #{@transport.uri rescue nil}"
313
+ $0 += " #{wid} #{transport.uri rescue nil}"
340
314
  old_arg0 = $0.dup
341
- after_receive_message = @transport.after_receive_message || lambda { | transport, message | nil }
342
- @transport.after_receive_message = lambda do | transport, message |
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
- @transport.run_server!
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 @force or process_running? pid
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
- Process.kill(0, pid)
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 processing.
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
+
@@ -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