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
@@ -81,9 +81,9 @@ begin
81
81
 
82
82
  # system("curl http://localhost:#{port}/")
83
83
 
84
- MathService.client.transport = t
84
+ MathService.asir.transport = t
85
85
 
86
- MathService.client.sum([1, 2, 3])
86
+ MathService.asir.sum([1, 2, 3])
87
87
 
88
88
  rescue Exception => err
89
89
  $stderr.puts "ERROR: #{err.inspect}\n#{err.backtrace * "\n"}"
@@ -21,8 +21,8 @@ begin
21
21
  end
22
22
  sleep 1 # wait for server to start
23
23
 
24
- MathService.client.transport = t
25
- MathService.client.sum([1, 2, 3])
24
+ MathService.asir.transport = t
25
+ MathService.asir.sum([1, 2, 3])
26
26
 
27
27
  rescue Exception => err
28
28
  $stderr.puts "ERROR: #{err.inspect}\n#{err.backtrace * "\n"}"
@@ -10,6 +10,6 @@ MathService.send(:include, ASIR::Client)
10
10
  # Driver:
11
11
 
12
12
  begin
13
- MathService.client.transport._log_enabled = true
14
- puts MathService.client.sum([1, 2, 3])
13
+ MathService.asir.transport._log_enabled = true
14
+ puts MathService.asir.sum([1, 2, 3])
15
15
  end
@@ -11,7 +11,7 @@ MathService.send(:include, ASIR::Client)
11
11
  # Driver:
12
12
 
13
13
  begin
14
- MathService.client.transport = ASIR::Transport::Subprocess.new
15
- MathService.client.transport._log_enabled = true
16
- puts MathService.client.sum([1, 2, 3])
14
+ MathService.asir.transport = ASIR::Transport::Subprocess.new
15
+ MathService.asir.transport._log_enabled = true
16
+ puts MathService.asir.sum([1, 2, 3])
17
17
  end
@@ -1,7 +1,8 @@
1
1
  # Write a ASIR::Transport::HTTP class that uses HTTP::Client for transport send_result and receive_result.
2
+ # And WEBrick to handle requests.
2
3
 
3
4
  $: << File.expand_path("../../../lib", __FILE__)
4
- require 'asir/transport/http'
5
+ require 'asir/transport/webrick'
5
6
  require 'asir/coder/marshal'
6
7
 
7
8
  require 'math_service'
@@ -9,19 +10,19 @@ MathService.send(:include, ASIR::Client)
9
10
 
10
11
  port = 3001
11
12
  begin
12
- t = ASIR::Transport::HTTP.new(:uri => "http://localhost:#{port}/")
13
+ t = ASIR::Transport::Webrick.new(:uri => "http://localhost:#{port}/")
13
14
  t._log_enabled = true
14
15
  c = t.encoder = ASIR::Coder::Marshal.new
15
16
  c._log_enabled = true
16
17
 
17
18
  server_pid = Process.fork do
18
- t.setup_webrick_server!
19
- t.start_webrick_server!
19
+ t.prepare_server!
20
+ t.run_server!
20
21
  end
21
22
  sleep 1 # wait for server to start
22
23
 
23
- MathService.client.transport = t
24
- MathService.client.sum([1, 2, 3])
24
+ MathService.asir.transport = t
25
+ MathService.asir.sum([1, 2, 3])
25
26
 
26
27
  rescue Exception => err
27
28
  $stderr.puts "ERROR: #{err.inspect}\n#{err.backtrace * "\n"}"
@@ -1,7 +1,7 @@
1
1
  # Use the Marshal and Base64 coders in prob-4.rb
2
2
 
3
3
  $: << File.expand_path("../../../lib", __FILE__)
4
- require 'asir/transport/http'
4
+ require 'asir/transport/webrick'
5
5
  require 'asir/coder/marshal'
6
6
  require 'asir/coder/base64'
7
7
  require 'asir/coder/chain'
@@ -11,7 +11,7 @@ MathService.send(:include, ASIR::Client)
11
11
 
12
12
  port = 3001
13
13
  begin
14
- t = ASIR::Transport::HTTP.new(:uri => "http://localhost:#{port}/")
14
+ t = ASIR::Transport::Webrick.new(:uri => "http://localhost:#{port}/")
15
15
  t._log_enabled = true
16
16
  c = t.encoder = ASIR::Coder::Chain.new(:encoders =>
17
17
  [
@@ -21,13 +21,13 @@ begin
21
21
  c._log_enabled = true
22
22
 
23
23
  server_pid = Process.fork do
24
- t.setup_webrick_server!
25
- t.start_webrick_server!
24
+ t.prepare_server!
25
+ t.run_server!
26
26
  end
27
27
  sleep 1 # wait for server to start
28
28
 
29
- MathService.client.transport = t
30
- MathService.client.sum([1, 2, 3])
29
+ MathService.asir.transport = t
30
+ MathService.asir.sum([1, 2, 3])
31
31
  rescue Exception => err
32
32
  $stderr.puts "ERROR: #{err.inspect}\n#{err.backtrace * "\n"}"
33
33
  ensure
File without changes
data/lib/asir.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # * Kurt Stephens
5
5
  # * Enova Financial
6
- # * 2012/04/02
6
+ # * 2012/07/18
7
7
  # * Slides -- "":http://kurtstephens.com/pub/ruby/abstracting_services_in_ruby/asir.slides/
8
8
  # * Code -- "":http://kurtstephens.com/pub/ruby/abstracting_services_in_ruby/
9
9
  # * Git -- "":http://github.com/kstephens/abstracting_services_in_ruby
@@ -34,7 +34,7 @@
34
34
  # * Directionality: One-way, Two-way
35
35
  # * Synchronicity: Synchronous, Asynchronous, Delayed, Buffered
36
36
  # * Distribution: Local Thread, Local Process, Distributed
37
- # * Transport: File, IPC, Pipe, Network
37
+ # * Transport: File, IPC, Pipe, Network, Beanstalk, ZMQ, Resque
38
38
  # * Robustness: Retry, Replay, Fallback
39
39
  # * Encoding: XML, JSON, YAML, Base64, Compression
40
40
  #
@@ -73,11 +73,11 @@
73
73
  # !SLIDE
74
74
  # Messaging
75
75
  #
76
- # * "Call a Method", "Call a Function" are all the same, in *all* languages.
76
+ # * "Send a Message", "Call a Function" are all the same, in *all* languages.
77
77
  # ** Decomposed into lookup() and apply().
78
- # * "Send Message", not "Call a Method".
78
+ # * "Send a Message", not "Call a Method".
79
79
  # * Messaging abstracts:
80
- # ** Object use from its implementation.
80
+ # ** Behavior from Implementation.
81
81
  # ** Transfer of control (method, function invocation, RPC, etc).
82
82
 
83
83
  # !SLIDE
@@ -111,8 +111,8 @@
111
111
  # * Proxy
112
112
  # * Message -> Just a Ruby message.
113
113
  # * Result, Exception (two-way) -> Return value or else.
114
- # * Transport -> (file, pipe, http, queue, ActiveResource)
115
- # * Encoder, Decoder -> Coder (Marshal, XML, JSON, ActiveResource)
114
+ # * Transport -> (file, pipe, http, queue, socket, ZMQ).
115
+ # * Encoder, Decoder -> Coder (Marshal, XML, JSON, zlib).
116
116
  #
117
117
  # !SLIDE END
118
118
 
@@ -127,6 +127,25 @@
127
127
  #
128
128
  # !SLIDE END
129
129
 
130
+ # !SLIDE
131
+ # Abstraction Leads to Rich Features
132
+ #
133
+ # * One-way and two-way requests as Module or instance methods.
134
+ # * Message support:
135
+ # ** Delayed Messages.
136
+ # * Transports:
137
+ # ** Null, Local, File, Named Pipe, TCP.
138
+ # ** HTTP under WEBrick or as Rack application.
139
+ # ** Beanstalkd, ZeroMQ, Resque.
140
+ # ** Buffered, Broadcast, Fallback, Demux transports.
141
+ # ** Time-decaying retry logic.
142
+ # * Encodings:
143
+ # ** Marshal, XML, JSON, YAML, Base64, ZLib.
144
+ # ** Chained encodings.
145
+ # ** Signed payloads.
146
+ #
147
+ # !SLIDE END
148
+
130
149
  # !SLIDE
131
150
  # Simple
132
151
  #
@@ -211,11 +230,13 @@ end
211
230
  Asir = ASIR
212
231
 
213
232
  require "asir/version"
233
+ require 'asir/config'
214
234
  require 'asir/error'
215
235
  require 'asir/log'
216
236
  require 'asir/initialization'
217
237
  require 'asir/additional_data'
218
238
  require 'asir/object_resolving'
239
+ require 'asir/thread_variable'
219
240
  require 'asir/identity'
220
241
  require 'asir/code_block'
221
242
  require 'asir/code_more'
@@ -226,6 +247,7 @@ require 'asir/coder'
226
247
  require 'asir/coder/null'
227
248
  require 'asir/coder/identity'
228
249
  require 'asir/transport'
250
+ require 'asir/invoker'
229
251
  require 'asir/channel'
230
252
  require 'asir/transport/local'
231
253
 
@@ -19,6 +19,31 @@ module ASIR
19
19
  def []= key, value
20
20
  (@additional_data ||= { })[key] = value
21
21
  end
22
+
23
+ def self.included target
24
+ super
25
+ target.extend(ModuleMethods)
26
+ end
27
+
28
+ module ModuleMethods
29
+ # Provide a getter method that delegates to addtional_data[...].
30
+ def addr_getter *names
31
+ names.each do | name |
32
+ name = name.to_sym
33
+ define_method(name) { | | addtional_data[name] }
34
+ end
35
+ end
36
+
37
+ # Provide getter and setter methods that delegate to addtional_data[...].
38
+ def addr_accessor *names
39
+ addr_getter *names
40
+ names.each do | name |
41
+ name = name.to_sym
42
+ define_method(:"#{name}=") { | v | addtional_data[name] = v }
43
+ end
44
+ end
45
+ end
46
+
22
47
  end
23
48
  # !SLIDE END
24
49
  end
data/lib/asir/channel.rb CHANGED
@@ -10,11 +10,11 @@ module ASIR
10
10
  attr_accessor :on_connect, :on_close, :on_retry, :on_error
11
11
 
12
12
  ON_ERROR = lambda do | channel, exc, action, stream |
13
- channel.close rescue nil if stream
13
+ channel.close rescue nil
14
14
  raise exc
15
15
  end
16
16
  ON_CLOSE = lambda do | channel, stream |
17
- stream.close
17
+ stream.close rescue nil if stream
18
18
  end
19
19
  ON_RETRY = lambda do | channel, exc, action |
20
20
  end
@@ -101,9 +101,8 @@ module ASIR
101
101
  # Maps from Channel objects to actual stream.
102
102
  def _streams
103
103
  streams = Thread.current[:'ASIR::Channel._streams'] ||= { }
104
- if ! @owning_process ||
105
- @owning_process != $$ || # child process?
106
- @owning_process > $$ # PIDs wrapped around?
104
+ if @owning_process != $$ || # child process?
105
+ @owning_process > $$ # PIDs wrapped around?
107
106
  @owning_process = $$
108
107
  streams.clear
109
108
  end
data/lib/asir/client.rb CHANGED
@@ -13,27 +13,32 @@ module ASIR
13
13
  end
14
14
 
15
15
  module CommonMethods
16
- def client_options &blk
17
- client._configure(&blk)
16
+ def asir_options &blk
17
+ asir._configure(&blk)
18
18
  end
19
+ alias_method :"#{ASIR::Config.client_method}_options", :asir_options if ASIR::Config.client_method
19
20
  end
20
21
 
21
22
  module ModuleMethods
22
23
  include CommonMethods
23
24
  # Proxies are cached for Module/Class methods because serialization will not include
24
25
  # Transport.
25
- def client
26
- @_asir_client ||= ASIR::Client::Proxy.new(self)
26
+ def asir
27
+ @_asir ||= ASIR::Client::Proxy.new(self, self)
27
28
  end
29
+ alias_method ASIR::Config.client_method, :asir if ASIR::Config.client_method
28
30
  end
29
31
 
30
32
  module InstanceMethods
31
33
  include CommonMethods
32
34
  # Proxies are not cached in instances because receiver is to be serialized by
33
35
  # its Transport's Coder.
34
- def client
35
- ASIR::Client::Proxy.new(self)
36
+ def asir
37
+ proxy = self.class.asir.dup
38
+ proxy.receiver = self
39
+ proxy
36
40
  end
41
+ alias_method ASIR::Config.client_method, :asir if ASIR::Config.client_method
37
42
  end
38
43
 
39
44
  # !SLIDE
@@ -41,14 +46,14 @@ module ASIR
41
46
  #
42
47
  # Provide client interface proxy to a service.
43
48
  class Proxy
44
- attr_accessor :receiver
49
+ attr_accessor :receiver, :receiver_class
45
50
 
46
51
  # Accept messages as a proxy for the receiver.
47
52
  # Blocks are used represent a "continuation" for the Result.
48
53
  def send selector, *arguments, &block
49
54
  message = Message.new(@receiver, selector, arguments, block, self)
50
55
  message = @before_send_message.call(message) if @before_send_message
51
- @__configure.call(message) if @__configure
56
+ @__configure.call(message, self) if @__configure
52
57
  result = transport.send_message(message)
53
58
  result
54
59
  end
@@ -80,8 +85,12 @@ module ASIR
80
85
  # Returns a new Client Proxy with a block to be called with the Message.
81
86
  # This block can configure additional options of the Message before
82
87
  # it is sent to the Transport.
88
+ #
89
+ # Call sequence:
90
+ #
91
+ # proxy.__configure.call(message, proxy).
83
92
  def _configure &blk
84
- proxy = self.dup
93
+ proxy = @receiver == @receiver_class ? self.dup : self
85
94
  proxy.__configure = blk
86
95
  proxy
87
96
  end
@@ -90,12 +99,19 @@ module ASIR
90
99
  # !SLIDE
91
100
  # Configuration Callbacks
92
101
 
93
- def initialize rcvr
94
- key = Module === (@receiver = rcvr) ? @receiver : @receiver.class
95
- (@@config_callbacks[key] ||
96
- @@config_callbacks[key.name] ||
102
+ def initialize rcvr, rcvr_class
103
+ @receiver = rcvr
104
+ @receiver_class = rcvr_class
105
+ _configure!
106
+ end
107
+
108
+ def _configure!
109
+ key = @receiver_class
110
+ (@@config_callbacks[key] ||
111
+ @@config_callbacks[key.name] ||
97
112
  @@config_callbacks[nil] ||
98
113
  IDENTITY_LAMBDA).call(self)
114
+ self
99
115
  end
100
116
 
101
117
  @@config_callbacks ||= { }
@@ -0,0 +1,8 @@
1
+ module ASIR
2
+ # Low-level ASIR configuration
3
+ module Config
4
+ @@client_method = nil
5
+ def self.client_method; @@client_method; end
6
+ def self.client_method= x; @@client_method = x; end
7
+ end
8
+ end
@@ -0,0 +1,34 @@
1
+ module ASIR
2
+ module Description
3
+ def describe obj, indent = "", more = nil
4
+ case obj
5
+ when nil, ASIR::Coder::Identity
6
+ s = ""; more = nil
7
+ when ASIR::Transport
8
+ s = "#{describe(obj.encoder, indent, "->\n")}#{indent}#{obj.class.name}"
9
+ opts = [ :file, :uri ].
10
+ select { | x | obj.respond_to?(x) && obj.send(x) != nil }.
11
+ map { | x | "#{x}: #{obj.send(x).inspect}" } * ","
12
+ s << "(" << opts << ")" unless opts.empty?
13
+ case
14
+ when obj.respond_to?(:transports)
15
+ s << "->[\n"
16
+ s << obj.transports.map { | x | describe(x, indent + " ") } * ",\n"
17
+ s << "]"
18
+ when obj.respond_to?(:transport)
19
+ s << "->\n" << describe(obj.transport, indent + " ")
20
+ end
21
+ when ASIR::Coder::Chain
22
+ s = "#{indent}Chain(\n"
23
+ s << obj.encoders.map { | x | describe(x, indent + " ") } * "->\n"
24
+ s << ")"
25
+ else
26
+ s = "#{indent}#{obj.class.name}"
27
+ end
28
+ s << more if more
29
+ s
30
+ end
31
+
32
+ extend self
33
+ end
34
+ end
@@ -0,0 +1,96 @@
1
+ require 'asir'
2
+
3
+ module ASIR; class Environment
4
+ attr_accessor :phase
5
+ attr_accessor :verb, :adjective, :object, :identifier
6
+ attr_accessor :config_rb, :config
7
+ attr_accessor :log_dir, :log_file, :pid_dir
8
+ attr_accessor :verbose
9
+
10
+ def initialize
11
+ @verbose = 0
12
+ @exit_code = 0
13
+ end
14
+
15
+ def log_dir
16
+ @log_dir ||= find_writable_directory :log_dir,
17
+ ENV['ASIR_LOG_DIR'],
18
+ '/var/log/asir',
19
+ '~/asir/log',
20
+ '/tmp'
21
+ end
22
+
23
+ def pid_dir
24
+ @pid_dir ||= find_writable_directory :pid_dir,
25
+ ENV['ASIR_PID_DIR'],
26
+ '/var/run/asir',
27
+ '~/asir/run',
28
+ '/tmp'
29
+ end
30
+
31
+ def find_writable_directory kind, *list
32
+ list.
33
+ reject { | p | ! p }.
34
+ map { | p | File.expand_path(p) }.
35
+ find { | p | File.writable?(p) } or
36
+ raise "Cannot find writable directory for #{kind}"
37
+ end
38
+
39
+ def pid_file
40
+ @pid_file ||=
41
+ "#{pid_dir}/#{asir_basename}.pid"
42
+ end
43
+
44
+ def log_file
45
+ @log_file ||=
46
+ "#{log_dir}/#{asir_basename}.log"
47
+ end
48
+
49
+ def asir_basename
50
+ "asir-#{adjective}-#{object}-#{identifier}"
51
+ end
52
+
53
+ def config_rb
54
+ @config_rb ||=
55
+ File.expand_path(ENV['ASIR_CONFIG_RB'] || 'config/asir_config.rb')
56
+ end
57
+
58
+ def config! phase, opts = { }
59
+ ((@config ||= { })[phase] ||= [
60
+ begin
61
+ opts[:phase] = phase
62
+ save = { }
63
+ opts.each do | k, v |
64
+ save[k] = send(k)
65
+ send(:"#{k}=", v)
66
+ end
67
+ $stderr.puts "#{self.class} calling #{config_rb} asir.phase=#{phase.inspect} ..." if @verbose >= 1
68
+ value = config_lambda.call(self)
69
+ $stderr.puts "#{self.class} calling #{config_rb} asir.phase=#{phase.inspect} DONE" if @verbose >= 1
70
+ value
71
+ ensure
72
+ save.each do | k , v |
73
+ send(:"#{k}=", v)
74
+ end
75
+ end
76
+ ]).first
77
+ end
78
+
79
+ def config_lambda
80
+ @config_lambda ||=
81
+ (
82
+ file = config_rb
83
+ $stderr.puts "#{self.class} loading #{file} ..." if @verbose >= 1
84
+ expr = File.read(file)
85
+ expr = "begin; lambda do | asir |; #{expr}\n end; end"
86
+ cfg = Object.new.send(:eval, expr, binding, file, 1)
87
+ # cfg = load file
88
+ # $stderr.puts "#{self.class} loading #{file} DONE" if @verbose >= 1
89
+ raise "#{file} did not return a Proc, returned a #{cfg.class}" unless Proc === cfg
90
+ cfg
91
+ )
92
+ end
93
+
94
+ end; end
95
+
96
+