asir 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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