asir 0.2.0

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 (121) hide show
  1. data/.gitignore +11 -0
  2. data/Gemfile +16 -0
  3. data/README.textile +50 -0
  4. data/Rakefile +83 -0
  5. data/VERSION +1 -0
  6. data/asir.gemspec +36 -0
  7. data/asir.riterate.yml +114 -0
  8. data/bin/asir +6 -0
  9. data/doc/Rakefile +8 -0
  10. data/doc/asir-sequence.pic +84 -0
  11. data/doc/asir-sequence.svg +1559 -0
  12. data/doc/sequence.pic +430 -0
  13. data/example/asir_control.sh +24 -0
  14. data/example/asir_control_client_http.rb +14 -0
  15. data/example/asir_control_client_zmq.rb +15 -0
  16. data/example/config/asir_config.rb +63 -0
  17. data/example/delayed_service.rb +15 -0
  18. data/example/ex01.rb +12 -0
  19. data/example/ex02.rb +12 -0
  20. data/example/ex03.rb +19 -0
  21. data/example/ex04.rb +33 -0
  22. data/example/ex05.rb +16 -0
  23. data/example/ex06.rb +26 -0
  24. data/example/ex07.rb +28 -0
  25. data/example/ex08.rb +30 -0
  26. data/example/ex09.rb +25 -0
  27. data/example/ex10.rb +24 -0
  28. data/example/ex11.rb +48 -0
  29. data/example/ex12.rb +34 -0
  30. data/example/ex13.rb +35 -0
  31. data/example/ex14.rb +30 -0
  32. data/example/ex15.rb +13 -0
  33. data/example/ex16.rb +33 -0
  34. data/example/ex17.rb +41 -0
  35. data/example/ex18.rb +62 -0
  36. data/example/ex19.rb +32 -0
  37. data/example/ex20.rb +28 -0
  38. data/example/ex21.rb +28 -0
  39. data/example/ex22.rb +15 -0
  40. data/example/ex23.rb +20 -0
  41. data/example/ex24.rb +35 -0
  42. data/example/example_helper.rb +51 -0
  43. data/example/sample_service.rb +162 -0
  44. data/example/unsafe_service.rb +12 -0
  45. data/hack_night/README.txt +18 -0
  46. data/hack_night/exercise/prob-1.rb +18 -0
  47. data/hack_night/exercise/prob-2.rb +21 -0
  48. data/hack_night/exercise/prob-3.rb +16 -0
  49. data/hack_night/exercise/prob-4.rb +36 -0
  50. data/hack_night/exercise/prob-5.rb +36 -0
  51. data/hack_night/exercise/prob-6.rb +95 -0
  52. data/hack_night/exercise/prob-7.rb +34 -0
  53. data/hack_night/solution/math_service.rb +11 -0
  54. data/hack_night/solution/prob-1.rb +12 -0
  55. data/hack_night/solution/prob-2.rb +15 -0
  56. data/hack_night/solution/prob-3.rb +17 -0
  57. data/hack_night/solution/prob-4.rb +37 -0
  58. data/hack_night/solution/prob-5.rb +21 -0
  59. data/hack_night/solution/prob-6.rb +33 -0
  60. data/hack_night/solution/prob-7.rb +36 -0
  61. data/lab/phony_proc.rb +31 -0
  62. data/lib/asir.rb +253 -0
  63. data/lib/asir/additional_data.rb +25 -0
  64. data/lib/asir/channel.rb +130 -0
  65. data/lib/asir/client.rb +111 -0
  66. data/lib/asir/code_block.rb +57 -0
  67. data/lib/asir/code_more.rb +50 -0
  68. data/lib/asir/coder.rb +26 -0
  69. data/lib/asir/coder/base64.rb +19 -0
  70. data/lib/asir/coder/chain.rb +30 -0
  71. data/lib/asir/coder/identity.rb +23 -0
  72. data/lib/asir/coder/json.rb +30 -0
  73. data/lib/asir/coder/marshal.rb +17 -0
  74. data/lib/asir/coder/null.rb +23 -0
  75. data/lib/asir/coder/proc.rb +22 -0
  76. data/lib/asir/coder/sign.rb +48 -0
  77. data/lib/asir/coder/xml.rb +213 -0
  78. data/lib/asir/coder/yaml.rb +33 -0
  79. data/lib/asir/coder/zlib.rb +21 -0
  80. data/lib/asir/configuration.rb +32 -0
  81. data/lib/asir/error.rb +34 -0
  82. data/lib/asir/identity.rb +36 -0
  83. data/lib/asir/initialization.rb +23 -0
  84. data/lib/asir/log.rb +82 -0
  85. data/lib/asir/main.rb +396 -0
  86. data/lib/asir/message.rb +31 -0
  87. data/lib/asir/message/delay.rb +35 -0
  88. data/lib/asir/object_resolving.rb +15 -0
  89. data/lib/asir/result.rb +39 -0
  90. data/lib/asir/retry_behavior.rb +54 -0
  91. data/lib/asir/transport.rb +241 -0
  92. data/lib/asir/transport/beanstalk.rb +217 -0
  93. data/lib/asir/transport/broadcast.rb +34 -0
  94. data/lib/asir/transport/buffer.rb +115 -0
  95. data/lib/asir/transport/composite.rb +19 -0
  96. data/lib/asir/transport/connection_oriented.rb +180 -0
  97. data/lib/asir/transport/delay.rb +38 -0
  98. data/lib/asir/transport/delegation.rb +53 -0
  99. data/lib/asir/transport/fallback.rb +36 -0
  100. data/lib/asir/transport/file.rb +88 -0
  101. data/lib/asir/transport/http.rb +54 -0
  102. data/lib/asir/transport/local.rb +21 -0
  103. data/lib/asir/transport/null.rb +14 -0
  104. data/lib/asir/transport/payload_io.rb +52 -0
  105. data/lib/asir/transport/rack.rb +73 -0
  106. data/lib/asir/transport/retry.rb +41 -0
  107. data/lib/asir/transport/stream.rb +35 -0
  108. data/lib/asir/transport/subprocess.rb +30 -0
  109. data/lib/asir/transport/tcp_socket.rb +34 -0
  110. data/lib/asir/transport/webrick.rb +50 -0
  111. data/lib/asir/transport/zmq.rb +110 -0
  112. data/lib/asir/uuid.rb +32 -0
  113. data/lib/asir/version.rb +3 -0
  114. data/spec/const_get_speed_spec.rb +33 -0
  115. data/spec/debug_helper.rb +20 -0
  116. data/spec/example_spec.rb +88 -0
  117. data/spec/json_spec.rb +128 -0
  118. data/spec/spec_helper.rb +3 -0
  119. data/spec/xml_spec.rb +144 -0
  120. data/stylesheets/slides.css +105 -0
  121. metadata +173 -0
@@ -0,0 +1,396 @@
1
+ require 'asir'
2
+ require 'time'
3
+
4
+
5
+ module ASIR
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
12
+
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
28
+
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}"
35
+ end
36
+
37
+ def parse_args! args = ARGV.dup
38
+ @args = args
39
+ until args.empty?
40
+ case args.first
41
+ when /^([a-z0-9_]+=)(.*)/i
42
+ k, v = $1.to_sym, $2
43
+ args.shift
44
+ v = v.to_i if v == v.to_i.to_s
45
+ send(k, v)
46
+ else
47
+ break
48
+ end
49
+ end
50
+ @verb, @adjective, @object, @identifier = args.map{|x| x.to_sym}
51
+ @identifier ||= :'0'
52
+ self
53
+ end
54
+
55
+ def log_str
56
+ "#{Time.now.gmtime.iso8601(4)} #{$$} #{log_str_no_time}"
57
+ end
58
+
59
+ def log_str_no_time
60
+ "#{@progname} #{@verb} #{@adjective} #{@object} #{@identifier}"
61
+ end
62
+
63
+ def run!
64
+ unless verb && adjective && object
65
+ @exit_code = 1
66
+ return usage!
67
+ end
68
+ config!(:config)
69
+ # $stderr.puts "log_file = #{log_file.inspect}"
70
+ case self.verb
71
+ when :restart
72
+ self.verb = :stop
73
+ _run_verb! && sleep(1)
74
+ self.verb = :start
75
+ _run_verb!
76
+ else
77
+ _run_verb!
78
+ end
79
+ self
80
+ rescue ::Exception => exc
81
+ $stderr.puts "#{log_str} ERROR\n#{exc.inspect}\n #{exc.backtrace * "\n "}"
82
+ @exit_code += 1
83
+ self
84
+ end
85
+
86
+ def _run_verb!
87
+ 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}"
93
+ end
94
+ send(sel)
95
+ rescue ::Exception => exc
96
+ $stderr.puts "#{log_str} ERROR\n#{exc.inspect}\n #{exc.backtrace * "\n "}"
97
+ @exit_code += 1
98
+ raise
99
+ nil
100
+ end
101
+
102
+ def method_missing sel, *args
103
+ log "method_missing #{sel}" if @verbose >= 3
104
+ case sel.to_s
105
+ when /^start_([^_]+)_worker!$/
106
+ _start_worker!
107
+ when /^status_([^_]+)_([^_]+)!$/
108
+ pid = server_pid
109
+ puts "#{log_str} pid #{pid}"
110
+ system("ps -fw -p #{pid}")
111
+ when /^log_([^_]+)_([^_]+)!$/
112
+ puts log_file
113
+ when /^taillog_([^_]+)_([^_]+)!$/
114
+ exec "tail -f #{log_file.inspect}"
115
+ when /^pid_([^_]+)_([^_]+)!$/
116
+ puts "#{pid_file} #{File.read(pid_file) rescue nil}"
117
+ when /^stop_([^_]+)_([^_]+)!$/
118
+ kill_server!
119
+ else
120
+ super
121
+ end
122
+ end
123
+
124
+ def usage!
125
+ $stderr.puts <<"END"
126
+ SYNOPSIS:
127
+ asir [ <<options>> ... ] <<verb>> <<adjective>> <<object>> [ <<identifier>> ]
128
+
129
+ OPTIONS:
130
+ config_rb=file.rb ($ASIR_LOG_DIR)
131
+ pid_dir=dir/ ($ASIR_PID_DIR)
132
+ log_dir=dir/ ($ASIR_LOG_DIR)
133
+ verbose=[0-9]
134
+
135
+ VERBS:
136
+ start
137
+ stop
138
+ restart
139
+ status
140
+ log
141
+ pid
142
+
143
+ ADJECTIVE-OBJECTs:
144
+ beanstalk conduit
145
+ beanstalk worker
146
+ zmq worker
147
+ webrick worker
148
+
149
+ EXAMPLES:
150
+
151
+ export ASIR_CONFIG_RB="some_system/asir_config.rb"
152
+ asir start beanstalk conduit
153
+ asir status beanstalk conduit
154
+
155
+ asir start webrick worker
156
+
157
+ asir start beanstalk worker 1
158
+ asir start beanstalk worker 2
159
+
160
+ asir start zmq worker
161
+ asir start zmq worker 1
162
+ asir start zmq worker 2
163
+ END
164
+ end
165
+
166
+ def start_beanstalk_conduit!
167
+ fork_server! "beanstalkd"
168
+ end
169
+
170
+ def _start_worker! type = adjective
171
+ log "start_worker! #{type}"
172
+ type = type.to_s
173
+ fork_server! do
174
+ transport_file = "asir/transport/#{type}"
175
+ log "loading #{transport_file}"
176
+ require transport_file
177
+ _create_transport ASIR::Transport.const_get(type[0..0].upcase + type[1..-1])
178
+ _run_workers!
179
+ end
180
+ end
181
+
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
+ def fork_server! cmd = nil, &blk
231
+ pid = Process.fork do
232
+ run_server! cmd, &blk
233
+ end
234
+ log "forked pid #{pid}"
235
+ Process.detach(pid) # Forks a Thread? We are gonna exit anyway.
236
+ File.open(pid_file, "w+") { | o | o.puts pid }
237
+ File.chmod(0666, pid_file) rescue nil
238
+
239
+ # Wait and check if process still exists.
240
+ sleep 3
241
+ unless process_running? pid
242
+ raise "Server process #{pid} died to soon?"
243
+ end
244
+
245
+ self
246
+ end
247
+
248
+ def run_server! cmd = nil
249
+ lf = File.open(log_file, "a+")
250
+ File.chmod(0666, log_file) rescue nil
251
+ $stdin.close rescue nil
252
+ STDIN.close rescue nil
253
+ STDOUT.reopen(lf)
254
+ $stdout.reopen(lf) if $stdout.object_id != STDOUT.object_id
255
+ STDERR.reopen(lf)
256
+ $stderr.reopen(lf) if $stderr.object_id != STDERR.object_id
257
+ # Process.daemon rescue nil # Ruby 1.9.x only.
258
+ lf.puts "#{log_str} starting pid #{$$}"
259
+ begin
260
+ if cmd
261
+ exec(cmd)
262
+ else
263
+ yield
264
+ end
265
+ ensure
266
+ lf.puts "#{log_str} finished pid #{$$}"
267
+ File.unlink(pid_file) rescue nil
268
+ end
269
+ self
270
+ rescue ::Exception => exc
271
+ msg = "ERROR pid #{$$}\n#{exc.inspect}\n #{exc.backtrace * "\n "}"
272
+ log msg, :stderr
273
+ raise
274
+ self
275
+ end
276
+
277
+ def kill_server!
278
+ log "#{log_str} kill"
279
+ pid = server_pid
280
+ stop_pid! pid
281
+ rescue ::Exception => exc
282
+ log "#{log_str} ERROR\n#{exc.inspect}\n #{exc.backtrace * "\n "}", :stderr
283
+ raise
284
+ end
285
+
286
+ def log msg, to_stderr = false
287
+ if to_stderr
288
+ $stderr.puts "#{log_str_no_time} #{msg}"
289
+ end
290
+ File.open(log_file, "a+") do | log |
291
+ log.puts "#{log_str} #{msg}"
292
+ end
293
+ end
294
+
295
+ def server_pid
296
+ pid = File.read(pid_file).chomp!
297
+ pid.to_i
298
+ end
299
+
300
+ def _create_transport default_class
301
+ config!(:environment)
302
+ case transport = config!(:transport)
303
+ when default_class
304
+ @transport = transport
305
+ else
306
+ raise "Expected config to return a #{default_class}, not a #{transport.class}"
307
+ end
308
+ end
309
+
310
+ def worker_pids
311
+ (@worker_pids ||= { })[@adjective] ||= { }
312
+ end
313
+
314
+ def _run_workers!
315
+ $0 = "#{@progname} #{@adjective} #{@object} #{@identifier}"
316
+
317
+ worker_id = 0
318
+ @transport.prepare_server!
319
+ worker_processes = @transport[:worker_processes] || 1
320
+ (worker_processes - 1).times do
321
+ wid = worker_id += 1
322
+ pid = Process.fork do
323
+ _run_transport_server! wid
324
+ end
325
+ Process.setgprp(pid, 0) rescue nil
326
+ worker_pids[wid] = pid
327
+ log "forked #{wid} pid #{pid}"
328
+ end
329
+
330
+ _run_transport_server!
331
+ ensure
332
+ log "worker 0 stopped"
333
+ _stop_workers!
334
+ end
335
+
336
+ def _run_transport_server! wid = 0
337
+ log "running transport worker #{@transport.class} #{wid}"
338
+ config!(:start)
339
+ $0 += " #{wid} #{@transport.uri rescue nil}"
340
+ 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 |
343
+ $0 = "#{old_arg0} #{transport.message_count} #{message.identifier}"
344
+ after_receive_message.call(transport, message)
345
+ end
346
+ @transport.run_server!
347
+ self
348
+ end
349
+
350
+ def _stop_workers!
351
+ workers = worker_pids.dup
352
+ worker_pids.clear
353
+ workers.each do | wid, pid |
354
+ config!(:stop)
355
+ stop_pid! pid, "wid #{wid} "
356
+ end
357
+ workers.each do | wid, pid |
358
+ wr = Process.waitpid(pid) rescue nil
359
+ log "stopped #{wid} pid #{pid} => #{wr.inspect}", :stderr
360
+ end
361
+ ensure
362
+ worker_pids.clear
363
+ end
364
+
365
+ def stop_pid! pid, msg = nil
366
+ log "stopping #{msg}pid #{pid}", :stderr
367
+ if process_running? pid
368
+ log "TERM pid #{pid}"
369
+ Process.kill('TERM', pid) rescue nil
370
+ sleep 3
371
+ if @force or process_running? pid
372
+ log "KILL pid #{pid}", :stderr
373
+ Process.kill('KILL', pid) rescue nil
374
+ end
375
+ if process_running? pid
376
+ log "cant-stop pid #{pid}", :stderr
377
+ end
378
+ else
379
+ log "not-running? pid #{pid}", :stderr
380
+ end
381
+ end
382
+
383
+ def process_running? pid
384
+ Process.kill(0, pid)
385
+ true
386
+ rescue ::Errno::ESRCH
387
+ false
388
+ rescue ::Exception => exc
389
+ $stderr.puts " DEBUG: process_running? #{pid} => #{exc.inspect}"
390
+ false
391
+ end
392
+
393
+ end # class
394
+ end # module
395
+
396
+
@@ -0,0 +1,31 @@
1
+
2
+ module ASIR
3
+ # !SLIDE
4
+ # Message
5
+ #
6
+ # Encapsulate the Ruby message from the Client to be handled by the Service.
7
+ class Message
8
+ include AdditionalData, Identity, CodeMore
9
+ attr_accessor :receiver, :receiver_class, :selector, :arguments, :block
10
+ attr_accessor :result, :one_way
11
+
12
+ def initialize r, s, a, b, p
13
+ @receiver, @selector, @arguments = r, s, a
14
+ @block = b if b
15
+ @receiver_class = @receiver.class
16
+ @one_way = p._one_way if p
17
+ end
18
+
19
+ def invoke!
20
+ @result = Result.new(self, @receiver.__send__(@selector, *@arguments))
21
+ rescue *Error::Unforwardable.unforwardable => exc
22
+ @result = Result.new(self, nil, Error::Unforwardable.new(exc))
23
+ rescue ::Exception => exc
24
+ @result = Result.new(self, nil, exc)
25
+ end
26
+
27
+ # Optional: Specifies the Numeric seconds or absolute Time to delay the Message until actual processing.
28
+ attr_accessor :delay
29
+ end
30
+ # !SLIDE END
31
+ end
@@ -0,0 +1,35 @@
1
+ module ASIR
2
+ class Message
3
+ module Delay
4
+ # Returns the number of seconds from now, that the message should be delayed.
5
+ # If message.delay is Numeric, sets message.delay to the Time to delay til.
6
+ # If message.delay is Time, returns (now - message.delay).to_f
7
+ # Returns Float if message.delay was set, or nil.
8
+ # Returns 0 if delay has already expired.
9
+ def relative_message_delay! message, now = nil
10
+ case delay = message.delay
11
+ when nil
12
+ when Numeric
13
+ now ||= Time.now
14
+ delay = delay.to_f
15
+ message.delay = (now + delay).utc
16
+ when Time
17
+ now ||= Time.now
18
+ delay = (delay - now).to_f
19
+ delay = 0 if delay < 0
20
+ else
21
+ raise TypeError, "Expected message.delay to be Numeric or Time, given #{delay.class}"
22
+ end
23
+ delay
24
+ end
25
+
26
+ def wait_for_delay! message
27
+ while (delay = relative_message_delay!(message)) && delay > 0
28
+ sleep delay
29
+ end
30
+ self
31
+ end
32
+
33
+ end
34
+ end
35
+ end