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
@@ -55,22 +55,23 @@ module ASIR
55
55
  def serve_file!
56
56
  ::File.open(file, "r") do | stream |
57
57
  @running = true
58
- serve_stream! stream, nil # One-way: no result stream.
58
+ _serve_stream! stream, nil # One-way: no result stream.
59
59
  end
60
60
  end
61
61
 
62
62
  # !SLIDE
63
63
  # Named Pipe Server
64
64
 
65
- def prepare_pipe_server!
65
+ def prepare_server!
66
66
  # _log [ :prepare_pipe_server!, file ]
67
67
  unless ::File.exist? file
68
68
  system(cmd = "mkfifo #{file.inspect}") or raise "cannot run #{cmd.inspect}"
69
69
  ::File.chmod(perms, file) rescue nil if perms
70
70
  end
71
71
  end
72
+ alias :prepare_pipe_server! :prepare_server!
72
73
 
73
- def run_pipe_server!
74
+ def run_server!
74
75
  # _log [ :run_pipe_server!, file ]
75
76
  with_server_signals! do
76
77
  @running = true
@@ -79,6 +80,7 @@ module ASIR
79
80
  end
80
81
  end
81
82
  end
83
+ alias :run_pipe_server! :run_server!
82
84
 
83
85
  # !SLIDE END
84
86
  end
@@ -4,25 +4,29 @@ module ASIR
4
4
  # Payload IO for Transport
5
5
  #
6
6
  # Framing
7
- # * Line containing the number of bytes in the payload.
7
+ # * Header line containing the number of bytes in the payload.
8
8
  # * The payload bytes.
9
9
  # * Blank line.
10
+ # * Footer.
10
11
  module PayloadIO
11
12
  class UnexpectedResponse < Error; end
12
13
 
13
- NEWLINE = "\n"
14
+ HEADER = "# asir_payload_size: "
15
+ FOOTER = "\n# asir_payload_end"
14
16
 
15
17
  def _write payload, stream
18
+ stream.write HEADER
16
19
  stream.puts payload.size
17
20
  stream.write payload
18
- stream.write NEWLINE
21
+ stream.puts FOOTER
19
22
  stream.flush
20
23
  stream
21
24
  end
22
25
 
23
26
  def _read stream
24
- size = stream.readline.chomp.to_i
27
+ size = /\d+$/.match(stream.readline.chomp)[0].to_i # HEADER (size)
25
28
  payload = stream.read(size)
29
+ stream.readline # FOOTER
26
30
  stream.readline
27
31
  payload
28
32
  end
@@ -0,0 +1,212 @@
1
+ require 'asir/transport/connection_oriented'
2
+ require 'resque'
3
+ require 'asir/poll_throttle'
4
+
5
+ module ASIR
6
+ class Transport
7
+ # !SLIDE
8
+ # Resque Transport
9
+ class Resque < ConnectionOriented
10
+ include PollThrottle
11
+
12
+ attr_accessor :queues, :queue, :namespace, :throttle
13
+
14
+ def initialize *args
15
+ @port_default = 6379
16
+ @scheme_default = 'redis'.freeze
17
+ super
18
+ self.one_way = true
19
+ # Reraise exception, let Resque::Worker handle it.
20
+ @on_exeception ||= lambda do | trans, exc, type, message |
21
+ raise exc, exc.backtrace
22
+ end
23
+ end
24
+
25
+ # !SLIDE
26
+ # Resque client.
27
+ def _client_connect!
28
+ # $stderr.puts " #{$$} #{self} _client_connect!"
29
+ resque_connect!
30
+ rescue ::Exception => exc
31
+ raise exc.class, "#{self.class} #{uri}: #{exc.message}", exc.backtrace
32
+ end
33
+
34
+ # !SLIDE
35
+ # Resque server (worker).
36
+ def _server!
37
+ resque_connect!
38
+ resque_worker
39
+ rescue ::Exception => exc
40
+ raise exc.class, "#{self.class} #{uri}: #{exc.message}", exc.backtrace
41
+ end
42
+
43
+ def _receive_result message, opaque_result
44
+ return nil if one_way || message.one_way
45
+ super
46
+ end
47
+
48
+ def _send_result message, result, result_payload, stream, message_state
49
+ return nil if one_way || message.one_way
50
+ super
51
+ end
52
+
53
+ def _send_message message, message_payload
54
+ stream.with_stream! do | io | # Force connect
55
+ $stderr.puts " #{self} _send_message #{message_payload.inspect} to queue=#{queue.inspect} as #{self.class} :process_job" if @verbose >= 2
56
+ ::Resque.enqueue_to(queue, self.class, message_payload)
57
+ end
58
+ end
59
+
60
+ def queues
61
+ @queues ||=
62
+ (
63
+ x = nil
64
+ x = path if @uri
65
+ x ||= ""
66
+ root, x = x.split('/')
67
+ x ||= ""
68
+ x = x.split(/(\s+|\s*,\s*)/)
69
+ x.each(&:freeze)
70
+ x.freeze
71
+ )
72
+ end
73
+
74
+ # Defaults to [ 'asir' ].
75
+ def queues_
76
+ @queues_ ||=
77
+ queues.empty? ? [ DEFAULT_QUEUE ] : queues.freeze
78
+ end
79
+
80
+ # Defaults to 'asir'.
81
+ def queue
82
+ @queue ||= queues_.first || DEFAULT_QUEUE
83
+ end
84
+
85
+ # Defaults to 'asir'.
86
+ def namespace_
87
+ @namespace_ ||= namespace || DEFAULT_QUEUE
88
+ end
89
+
90
+ DEFAULT_QUEUE = 'asir'.freeze
91
+
92
+ def _server_accept_connection! server
93
+ [ server, server ]
94
+ end
95
+
96
+ # Resque is message-oriented, process only one message per "connection".
97
+ def stream_eof? stream
98
+ false
99
+ end
100
+
101
+ # Nothing to be closed for Resque.
102
+ def _server_close_connection! in_stream, out_stream
103
+ # NOTHING
104
+ end
105
+
106
+ def serve_stream_message! in_stream, out_stream # ignored
107
+ save = Thread.current[:asir_transport_resque_instance]
108
+ Thread.current[:asir_transport_resque_instance] = self
109
+ poll_throttle throttle do
110
+ # $stderr.puts " #{self} resque_worker = #{resque_worker} on queues #{resque_worker.queues}"
111
+ if job = resque_worker.reserve
112
+ $stderr.puts " #{self} serve_stream_message! job=#{job.class}:#{job.inspect}" if @verbose >= 2
113
+ resque_worker.process(job)
114
+ end
115
+ job
116
+ end
117
+ self
118
+ ensure
119
+ Thread.current[:asir_transport_resque_instance] = save
120
+ end
121
+
122
+ # Class method entry point from Resque::Job.perform.
123
+ def self.perform payload
124
+ # $stderr.puts " #{self} process_job payload=#{payload.inspect}"
125
+ t = Thread.current[:asir_transport_resque_instance]
126
+ # Pass payload as in_stream; _receive_message will return it.
127
+ t.serve_message! payload, nil
128
+ end
129
+
130
+ def _receive_message payload, additional_data # is actual payload
131
+ # $stderr.puts " #{self} _receive_message payload=#{payload.inspect}"
132
+ [ payload, nil ]
133
+ end
134
+
135
+ ####################################
136
+
137
+ def resque_uri
138
+ @resque_uri ||=
139
+ (
140
+ unless scheme == 'redis'
141
+ raise ArgumentError, "Invalid resque URI: #{uri.inspect}"
142
+ end
143
+ _uri
144
+ )
145
+ end
146
+
147
+ def resque_connect!
148
+ @redis =
149
+ ::Redis.new({
150
+ :host => address || '127.0.0.1',
151
+ :port => port,
152
+ :thread_safe => true,
153
+ })
154
+ if namespace_
155
+ ::Resque.redis =
156
+ @redis =
157
+ ::Redis::Namespace.new(namespace_, :redis => @redis)
158
+ ::Resque.redis.namespace = namespace_
159
+ else
160
+ ::Resque.redis = @redis
161
+ end
162
+ # $stderr.puts " *** #{$$} #{self} resque_connect! #{@redis.inspect}"
163
+ @redis
164
+ end
165
+
166
+ def resque_disconnect!
167
+ ::Resque.redis = nil
168
+ end
169
+
170
+ def resque_worker
171
+ @resque_worker ||= ::Resque::Worker.new(queues_)
172
+ end
173
+
174
+ def server_on_start!
175
+ # prune_dead_workers expects processes to have "resque " in the name.
176
+ @save_progname ||= $0.dup
177
+ $0 = "resque #{$0}"
178
+ if worker = resque_worker
179
+ worker.prune_dead_workers
180
+ worker.register_worker
181
+ end
182
+ self
183
+ end
184
+
185
+ def server_on_stop!
186
+ $0 = @save_progname if @save_progname
187
+ if worker = @resque_worker
188
+ worker.unregister_worker
189
+ end
190
+ self
191
+ end
192
+
193
+ #########################################
194
+
195
+ def _start_conduit!
196
+ @redis_dir ||= "/tmp"
197
+ @redis_conf ||= "#{@redis_dir}/asir-redis-#{port}.conf"
198
+ @redis_log ||= "#{@redis_dir}/asir-redis-#{port}.log"
199
+ ::File.open(@redis_conf, "w+") do | out |
200
+ out.puts "daemonize no"
201
+ out.puts "port #{port}"
202
+ out.puts "loglevel warning"
203
+ out.puts "logfile #{@redis_log}"
204
+ end
205
+ exec "redis-server", @redis_conf
206
+ end
207
+ end
208
+ # !SLIDE END
209
+ end # class
210
+ end # module
211
+
212
+
@@ -11,19 +11,29 @@ module ASIR
11
11
  # Serve all Messages from a stream.
12
12
  def serve_stream! in_stream, out_stream
13
13
  with_server_signals! do
14
- while @running && ! in_stream.eof?
15
- begin
16
- serve_stream_message! in_stream, out_stream
17
- rescue Error::Terminate => err
18
- @running = false
19
- _log [ :serve_stream_terminate, err ]
20
- rescue ::Exception => err
21
- _log [ :serve_stream_error, err ]
22
- end
14
+ @running = true
15
+ _serve_stream! in_stream, out_stream
16
+ end
17
+ end
18
+
19
+ def _serve_stream! in_stream, out_stream
20
+ while @running && ! stream_eof?(in_stream)
21
+ begin
22
+ serve_stream_message! in_stream, out_stream
23
+ rescue Error::Terminate => err
24
+ @running = false
25
+ _log [ :serve_stream_terminate, err ]
26
+ rescue ::Exception => err
27
+ _log [ :serve_stream_error, err ]
23
28
  end
24
29
  end
25
30
  end
26
31
 
32
+ # Subclasses can override this method.
33
+ def stream_eof? stream
34
+ stream.eof?
35
+ end
36
+
27
37
  # !SLIDE
28
38
  # Serve a Message from a stream.
29
39
  def serve_stream_message! in_stream, out_stream
@@ -20,10 +20,11 @@ module ASIR
20
20
  end
21
21
 
22
22
  def _server_accept_connection! server
23
- server.accept
23
+ socket = server.accept
24
+ [ socket, socket ] # Use same socket for in_stream and out_stream
24
25
  end
25
26
 
26
- def _server_close_connection! stream
27
+ def _server_close_connection! stream, out_stream
27
28
  stream.close rescue nil
28
29
  end
29
30
  end
@@ -50,7 +50,6 @@ module ASIR
50
50
  stream.recv 0
51
51
  end
52
52
 
53
- # def scheme; SCHEME; end; SCHEME = 'tcp'.freeze
54
53
  def queue
55
54
  @queue ||=
56
55
  (
@@ -69,23 +68,21 @@ module ASIR
69
68
  (queue.empty? ? queue : queue + " ").freeze
70
69
  end
71
70
 
71
+ # server represents a receiving ZMQ endpoint.
72
+ def _server_accept_connection! server
73
+ [ server, @one_way ? nil : server ]
74
+ end
72
75
 
73
- def run_server!
74
- _log { "run_server! #{uri}" } if @verbose >= 1
75
- with_server_signals! do
76
- @running = true
77
- while @running
78
- begin
79
- serve_stream_message!(@server, @one_way ? nil : @server)
80
- rescue Error::Terminate => err
81
- @running = false
82
- _log [ :run_server_terminate, err ]
83
- end
84
- end
85
- end
86
- self
87
- ensure
88
- _server_close!
76
+ # ZMQ is message-oriented, process only one message per "connection".
77
+ alias :_server_serve_stream :serve_message!
78
+
79
+ def stream_eof? stream
80
+ false
81
+ end
82
+
83
+ # Nothing to be closed for ZMQ.
84
+ def _server_close_connection! in_stream, out_stream
85
+ # NOTHING
89
86
  end
90
87
 
91
88
  def zmq_uri
@@ -0,0 +1,51 @@
1
+ require 'asir'
2
+ require 'uri'
3
+
4
+ module ASIR
5
+ module UriConfig
6
+ attr_accessor :uri, :scheme, :host, :port, :path
7
+ attr_accessor :scheme_default, :host_default, :port_default, :path_default
8
+ alias :protocol :scheme
9
+ alias :protocol= :scheme=
10
+ alias :address :host
11
+ alias :address= :host=
12
+
13
+ def uri
14
+ @uri ||= "#{scheme}://#{host}:#{port}"
15
+ end
16
+
17
+ def _uri
18
+ @_uri ||=
19
+ URI === @uri ? @uri : URI.parse(uri)
20
+ end
21
+
22
+ def scheme
23
+ @scheme ||=
24
+ (@uri && _uri.scheme) ||
25
+ @scheme_default ||
26
+ S_TCP
27
+ end
28
+ S_TCP = 'tcp'.freeze
29
+
30
+ def host
31
+ @host ||=
32
+ (@uri && _uri.host) ||
33
+ @host_default ||
34
+ S_LOCALHOST
35
+ end
36
+ S_LOCALHOST = '127.0.0.1'.freeze
37
+
38
+ def port
39
+ @port ||=
40
+ (@uri && _uri.port) ||
41
+ @port_default ||
42
+ (raise Error, "#{self.class}: port not set.")
43
+ end
44
+
45
+ def path
46
+ @path ||=
47
+ (@uri && _uri.path) ||
48
+ @path_default
49
+ end
50
+ end
51
+ end
data/lib/asir/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ASIR
2
- VERSION = "0.2.0"
2
+ VERSION = "1.0.1"
3
3
  end
@@ -0,0 +1,48 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ require 'asir'
4
+
5
+ describe "ASIR::Client" do
6
+ attr_accessor :client, :data, :object
7
+
8
+ before(:each) do
9
+ self.data = { }
10
+ transport = ASIR::Transport::Local.new
11
+ self.object = ASIR::Test::TestObject.new(self)
12
+ self.client = object.class.asir
13
+ client.transport = transport
14
+ client.transport.should == transport
15
+ end
16
+
17
+ it 'should return the same Proxy instance for a Module.' do
18
+ object.class.asir.object_id.should == object.class.asir.object_id
19
+ end
20
+
21
+ it 'should return a cloned Proxy instances for each object.' do
22
+ object.asir.object_id.should_not == client.object_id
23
+ object.asir.object_id.should_not == object.asir.object_id
24
+ object.asir.transport.object_id.should == client.transport.object_id
25
+ end
26
+
27
+ it 'should return a cloned Proxy for class.asir._configure.' do
28
+ client._configure { | message, proxy | }.object_id.should_not == client.object_id
29
+ end
30
+
31
+ it 'should not return a cloned Proxy for object.asir._configure.' do
32
+ c = object.asir
33
+ c._configure { | message, proxy | }.object_id.should == c.object_id
34
+ end
35
+
36
+ it 'should handle _configure blocks' do
37
+ proxy = object.asir._configure { | message, proxy |
38
+ message[:test_proxy] = proxy
39
+ message[:test_data] = :test
40
+ }
41
+ proxy.object_id.should_not == client.object_id
42
+ proxy.object_id.should_not == object.asir.object_id
43
+ proxy.return_argument :foo
44
+ object.message[:test_data].should == :test
45
+ object.message[:test_proxy].should == proxy
46
+ end
47
+ end
48
+