skein 0.3.7 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +4 -1
  3. data/Gemfile.lock +49 -36
  4. data/LICENSE.md +1 -1
  5. data/README.md +9 -4
  6. data/RELEASES.md +2 -0
  7. data/VERSION +1 -1
  8. data/bin/skein +1 -1
  9. data/examples/echo +66 -0
  10. data/examples/echo-server +77 -0
  11. data/lib/skein/adapter.rb +3 -0
  12. data/lib/skein/client/publisher.rb +10 -2
  13. data/lib/skein/client/rpc.rb +78 -19
  14. data/lib/skein/client/subscriber.rb +27 -4
  15. data/lib/skein/client/worker.rb +221 -63
  16. data/lib/skein/client.rb +20 -11
  17. data/lib/skein/config.rb +3 -1
  18. data/lib/skein/connected.rb +88 -17
  19. data/lib/skein/context.rb +5 -2
  20. data/lib/skein/handler/async.rb +6 -2
  21. data/lib/skein/handler.rb +131 -20
  22. data/lib/skein/rabbitmq.rb +1 -0
  23. data/lib/skein/timeout_queue.rb +43 -0
  24. data/lib/skein.rb +18 -2
  25. data/skein.gemspec +21 -24
  26. data/test/helper.rb +29 -0
  27. data/test/unit/test_skein_client.rb +4 -1
  28. data/test/unit/test_skein_client_publisher.rb +1 -1
  29. data/test/unit/test_skein_client_rpc.rb +37 -0
  30. data/test/unit/test_skein_client_subscriber.rb +29 -12
  31. data/test/unit/test_skein_client_worker.rb +22 -9
  32. data/test/unit/test_skein_connected.rb +21 -0
  33. data/test/unit/test_skein_rpc_timeout.rb +19 -0
  34. data/test/unit/test_skein_worker.rb +4 -0
  35. metadata +41 -16
  36. data/lib/skein/rpc/base.rb +0 -23
  37. data/lib/skein/rpc/error.rb +0 -34
  38. data/lib/skein/rpc/notification.rb +0 -2
  39. data/lib/skein/rpc/request.rb +0 -62
  40. data/lib/skein/rpc/response.rb +0 -38
  41. data/lib/skein/rpc.rb +0 -24
  42. data/test/unit/test_skein_rpc_error.rb +0 -10
  43. data/test/unit/test_skein_rpc_request.rb +0 -93
data/lib/skein/context.rb CHANGED
@@ -33,12 +33,15 @@ class Skein::Context
33
33
  end
34
34
 
35
35
  def exception!(*args)
36
- @reporter and @reporter.exception!(*args)
36
+ @reporter&.exception!(*args)
37
37
  end
38
38
 
39
39
  def trap
40
40
  yield
41
- rescue => e
41
+
42
+ rescue SystemExit
43
+ raise
44
+ rescue Object => e
42
45
  self.exception!(e)
43
46
  end
44
47
  end
@@ -2,8 +2,12 @@ class Skein::Handler::Async < Skein::Handler
2
2
  # == Instance Methods =====================================================
3
3
 
4
4
  def delegate(*args)
5
- @target.send(*args) do |response, error|
6
- yield(response, error)
5
+ fiber = Fiber.new do
6
+ @target.send(*args) do |*response|
7
+ Fiber.yield(*response)
8
+ end
7
9
  end
10
+
11
+ yield(fiber.resume)
8
12
  end
9
13
  end
data/lib/skein/handler.rb CHANGED
@@ -3,6 +3,12 @@ class Skein::Handler
3
3
 
4
4
  attr_reader :context
5
5
 
6
+ # == Constants ============================================================
7
+
8
+ RPC_BASE = {
9
+ jsonrpc: '2.0'
10
+ }.freeze
11
+
6
12
  # == Class Methods ========================================================
7
13
 
8
14
  def self.for(target)
@@ -18,10 +24,19 @@ class Skein::Handler
18
24
 
19
25
  def initialize(target, context = nil)
20
26
  @target = target
21
- @context = context
27
+ @context = context || target.context
28
+ end
29
+
30
+ def json_rpc(contents)
31
+ JSON.dump(RPC_BASE.merge(contents))
22
32
  end
23
33
 
24
- def handle(message_json)
34
+ def handle(message_json, metrics, state)
35
+ state[:started] = Time.now
36
+ state[:finished] = nil
37
+ state[:thread] = Thread.current
38
+ state[:method] = :handle
39
+
25
40
  request =
26
41
  begin
27
42
  JSON.load(message_json)
@@ -29,9 +44,16 @@ class Skein::Handler
29
44
  rescue Object => e
30
45
  @context and @context.exception!(e, message_json)
31
46
 
32
- return yield(JSON.dump(
33
- result: nil,
34
- error: '[%s] %s' % [ e.class, e ],
47
+ metrics[:failed] += 1
48
+ metrics[:errors][:parsing] += 1
49
+ metrics[:time] += state[:finished] - state[:started]
50
+ state[:method] = nil
51
+
52
+ return yield(rpc_json(
53
+ error: {
54
+ code: -32700,
55
+ message: 'Parse error'
56
+ },
35
57
  id: nil
36
58
  ))
37
59
  end
@@ -40,9 +62,16 @@ class Skein::Handler
40
62
  when Hash
41
63
  # Acceptable
42
64
  else
43
- return yield(JSON.dump(
44
- result: nil,
45
- error: 'Request does not conform to the JSON-RPC format.',
65
+ metrics[:failed] += 1
66
+ metrics[:errors][:non_jsonrpc] += 1
67
+ metrics[:time] += state[:finished] - state[:started]
68
+ state[:method] = nil
69
+
70
+ return yield(json_rpc(
71
+ error: {
72
+ code: -32600,
73
+ message: 'Request does not conform to the JSON-RPC format.'
74
+ },
46
75
  id: nil
47
76
  ))
48
77
  end
@@ -58,27 +87,109 @@ class Skein::Handler
58
87
  end
59
88
 
60
89
  unless (request['method'] and request['method'].is_a?(String) and request['method'].match(/\S/))
61
- return yield(JSON.dump(
62
- result: nil,
63
- error: 'Request does not conform to the JSON-RPC format, missing valid method.',
90
+ metrics[:failed] += 1
91
+ metrics[:errors][:no_method_property] += 1
92
+ metrics[:time] += state[:finished] - state[:started]
93
+ state[:method] = nil
94
+
95
+ return yield(json_rpc(
96
+ error: {
97
+ code: -32600,
98
+ message: 'Request does not conform to the JSON-RPC format, missing valid method.'
99
+ },
64
100
  id: request['id']
65
101
  ))
66
102
  end
67
103
 
68
104
  begin
69
- delegate(request['method'], *request['params']) do |result, error = nil|
70
- yield(JSON.dump(
71
- result: result,
72
- error: error,
73
- id: request['id']
74
- ))
105
+ method_name = request['method'].to_sym
106
+ @target.before_execution(method_name)
107
+ state[:method] = method_name
108
+
109
+ delegate(method_name, *request['params']) do |result|
110
+ @target.after_execution(method_name)
111
+ state[:finished] = Time.now
112
+ metrics[:time] += state[:finished] - state[:started]
113
+ state[:method] = nil
114
+
115
+ case (result)
116
+ when Exception
117
+ metrics[:failed] += 1
118
+ metrics[:errors][:exception] += 1
119
+
120
+ yield(json_rpc(
121
+ error: {
122
+ code: -32603,
123
+ message: result.to_s
124
+ },
125
+ id: request['id']
126
+ ))
127
+ else
128
+ metrics[:succeeded] += 1
129
+
130
+ yield(json_rpc(
131
+ result: result,
132
+ id: request['id']
133
+ ))
134
+ end
75
135
  end
136
+
137
+ rescue ArgumentError => e
138
+ # REFACTOR: Make these exception catchers only trap immediate errors,
139
+ # not those that occur within the delegated code.
140
+ @context and @context.exception!(e, message_json)
141
+
142
+ metrics[:failed] += 1
143
+ metrics[:errors][:invalid_arguments] += 1
144
+ state[:finished] = Time.now
145
+ metrics[:time] += state[:finished] - state[:started]
146
+ state[:method] = nil
147
+
148
+ yield(json_rpc(
149
+ error: {
150
+ code: -32602,
151
+ message: 'Invalid params',
152
+ data: {
153
+ method: request['method'],
154
+ params: request['params'],
155
+ message: e.to_s
156
+ }
157
+ },
158
+ id: request['id']
159
+ ))
160
+ rescue NoMethodError => e
161
+ @context and @context.exception!(e, message_json)
162
+
163
+ metrics[:failed] += 1
164
+ metrics[:errors][:invalid_arguments] += 1
165
+ state[:finished] = Time.now
166
+ metrics[:time] += state[:finished] - state[:started]
167
+ state[:method] = nil
168
+
169
+ yield(json_rpc(
170
+ error: {
171
+ code: -32601,
172
+ message: e.to_s,
173
+ data: {
174
+ method: request['method']
175
+ }
176
+ },
177
+ id: request['id']
178
+ ))
76
179
  rescue Object => e
77
180
  @context and @context.exception!(e, message_json)
78
181
 
79
- yield(JSON.dump(
80
- result: nil,
81
- error: '[%s] %s' % [ e.class, e ],
182
+ metrics[:failed] += 1
183
+ metrics[:errors][:exception] += 1
184
+ state[:finished] = Time.now
185
+ metrics[:time] += state[:finished] - state[:started]
186
+ state[:method] = nil
187
+
188
+ yield(json_rpc(
189
+ error: {
190
+ code: -32063,
191
+ message: '[%s] %s' % [ e.class, e ]
192
+ },
82
193
  id: request['id']
83
194
  ))
84
195
  end
@@ -23,6 +23,7 @@ module Skein::RabbitMQ
23
23
  end
24
24
  end
25
25
 
26
+ # REFACTOR: These should be moved to an abstract adapter
26
27
  def self.connect(config = nil)
27
28
  config ||= Skein.config
28
29
 
@@ -0,0 +1,43 @@
1
+ class Skein::TimeoutQueue
2
+ # == Instance Methods =====================================================
3
+
4
+ def initialize(blocking: true, timeout: nil)
5
+ @response = [ ]
6
+ @blocking = blocking
7
+ @timeout = timeout&.to_f
8
+ @mutex = Mutex.new
9
+ @cond_var = ConditionVariable.new
10
+ end
11
+
12
+ def <<(result)
13
+ @mutex.synchronize do
14
+ @response << result
15
+
16
+ @cond_var.signal
17
+ end
18
+ end
19
+
20
+ def pop
21
+ @mutex.synchronize do
22
+ if (@blocking)
23
+ if (@timeout)
24
+ timeout_time = Time.now.to_f + @timeout
25
+
26
+ while (@response.empty? and (remaining_time = timeout_time - Time.now.to_f) > 0)
27
+ @cond_var.wait(@mutex, remaining_time)
28
+ end
29
+ else
30
+ while (@response.empty?)
31
+ @cond_var.wait(@mutex)
32
+ end
33
+ end
34
+ end
35
+
36
+ if (@response.empty?)
37
+ raise Skein::TimeoutException, 'Queue Empty: Time Out'
38
+ end
39
+
40
+ @response.shift
41
+ end
42
+ end
43
+ end
data/lib/skein.rb CHANGED
@@ -1,12 +1,28 @@
1
1
  require 'json'
2
2
 
3
3
  module Skein
4
- VERSION = File.read(File.expand_path('../VERSION', File.dirname(__FILE__))).chomp.freeze
4
+ # == Constants ============================================================
5
+
6
+ VERSION = File.read(File.expand_path('../VERSION', __dir__)).chomp.freeze
7
+
8
+ # == Exceptions ===========================================================
9
+
10
+ class Exception < ::StandardError
11
+ end
12
+
13
+ class TimeoutException < Exception
14
+ end
15
+
16
+ # == Module Methods =======================================================
5
17
 
6
18
  def self.version
7
19
  VERSION
8
20
  end
9
21
 
22
+ def self.config=(config)
23
+ @config = config
24
+ end
25
+
10
26
  def self.config
11
27
  @config ||= Skein::Config.new
12
28
  end
@@ -21,5 +37,5 @@ require_relative './skein/context'
21
37
  require_relative './skein/handler'
22
38
  require_relative './skein/rabbitmq'
23
39
  require_relative './skein/reporter'
24
- require_relative './skein/rpc'
25
40
  require_relative './skein/support'
41
+ require_relative './skein/timeout_queue'
data/skein.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: skein 0.3.7 ruby lib
5
+ # stub: skein 0.8.1 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "skein".freeze
9
- s.version = "0.3.7"
9
+ s.version = "0.8.1"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Scott Tadman".freeze]
14
- s.date = "2017-03-20"
14
+ s.date = "2020-03-04"
15
15
  s.description = "Wrapper for RabbitMQ that makes blocking RPC calls and handles pub-sub broadcasts.".freeze
16
16
  s.email = "tadman@postageapp.com".freeze
17
17
  s.executables = ["skein".freeze]
@@ -30,6 +30,8 @@ Gem::Specification.new do |s|
30
30
  "bin/skein",
31
31
  "config/.gitignore",
32
32
  "config/skein.yml.example",
33
+ "examples/echo",
34
+ "examples/echo-server",
33
35
  "lib/skein.rb",
34
36
  "lib/skein/adapter.rb",
35
37
  "lib/skein/client.rb",
@@ -45,52 +47,47 @@ Gem::Specification.new do |s|
45
47
  "lib/skein/handler/threaded.rb",
46
48
  "lib/skein/rabbitmq.rb",
47
49
  "lib/skein/reporter.rb",
48
- "lib/skein/rpc.rb",
49
- "lib/skein/rpc/base.rb",
50
- "lib/skein/rpc/error.rb",
51
- "lib/skein/rpc/notification.rb",
52
- "lib/skein/rpc/request.rb",
53
- "lib/skein/rpc/response.rb",
54
50
  "lib/skein/support.rb",
51
+ "lib/skein/timeout_queue.rb",
55
52
  "skein.gemspec",
56
53
  "test/data/sample_config.yml",
57
54
  "test/helper.rb",
58
55
  "test/script/em_example",
59
56
  "test/unit/test_skein_client.rb",
60
57
  "test/unit/test_skein_client_publisher.rb",
58
+ "test/unit/test_skein_client_rpc.rb",
61
59
  "test/unit/test_skein_client_subscriber.rb",
62
60
  "test/unit/test_skein_client_worker.rb",
63
61
  "test/unit/test_skein_config.rb",
62
+ "test/unit/test_skein_connected.rb",
64
63
  "test/unit/test_skein_context.rb",
65
64
  "test/unit/test_skein_rabbitmq.rb",
66
65
  "test/unit/test_skein_reporter.rb",
67
- "test/unit/test_skein_rpc_error.rb",
68
- "test/unit/test_skein_rpc_request.rb",
66
+ "test/unit/test_skein_rpc_timeout.rb",
69
67
  "test/unit/test_skein_support.rb",
70
68
  "test/unit/test_skein_worker.rb",
71
69
  "tmp/.gitignore"
72
70
  ]
73
71
  s.homepage = "http://github.com/postageapp/skein".freeze
74
72
  s.licenses = ["MIT".freeze]
75
- s.rubygems_version = "2.5.2".freeze
73
+ s.rubygems_version = "3.1.2".freeze
76
74
  s.summary = "RabbitMQ RPC/PubSub Library".freeze
77
75
 
78
76
  if s.respond_to? :specification_version then
79
77
  s.specification_version = 4
78
+ end
80
79
 
81
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
82
- s.add_runtime_dependency(%q<birling>.freeze, [">= 0"])
83
- s.add_development_dependency(%q<rake>.freeze, [">= 0"])
84
- s.add_development_dependency(%q<jeweler>.freeze, [">= 0"])
85
- s.add_development_dependency(%q<test-unit>.freeze, [">= 0"])
86
- else
87
- s.add_dependency(%q<birling>.freeze, [">= 0"])
88
- s.add_dependency(%q<rake>.freeze, [">= 0"])
89
- s.add_dependency(%q<jeweler>.freeze, [">= 0"])
90
- s.add_dependency(%q<test-unit>.freeze, [">= 0"])
91
- end
80
+ if s.respond_to? :add_runtime_dependency then
81
+ s.add_runtime_dependency(%q<birling>.freeze, [">= 0.2.0"])
82
+ s.add_development_dependency(%q<bunny>.freeze, [">= 0"])
83
+ s.add_development_dependency(%q<march_hare>.freeze, [">= 0"])
84
+ s.add_development_dependency(%q<rake>.freeze, [">= 0"])
85
+ s.add_development_dependency(%q<jeweler>.freeze, [">= 0"])
86
+ s.add_development_dependency(%q<test-unit>.freeze, [">= 0"])
92
87
  else
93
- s.add_dependency(%q<birling>.freeze, [">= 0"])
88
+ s.add_dependency(%q<birling>.freeze, [">= 0.2.0"])
89
+ s.add_dependency(%q<bunny>.freeze, [">= 0"])
90
+ s.add_dependency(%q<march_hare>.freeze, [">= 0"])
94
91
  s.add_dependency(%q<rake>.freeze, [">= 0"])
95
92
  s.add_dependency(%q<jeweler>.freeze, [">= 0"])
96
93
  s.add_dependency(%q<test-unit>.freeze, [">= 0"])
data/test/helper.rb CHANGED
@@ -39,4 +39,33 @@ class Test::Unit::TestCase
39
39
  def data_path(name)
40
40
  File.expand_path(File.join('data', name), File.dirname(__FILE__))
41
41
  end
42
+
43
+ def wait_for(timeout = 1.0, message = nil)
44
+ sleep_interval = 0.001
45
+ start_time = Time.now
46
+
47
+ while ((Time.now - start_time) < timeout)
48
+ return if (yield)
49
+
50
+ sleep(sleep_interval)
51
+ end
52
+
53
+ fail(message || 'Timed out waiting for condition (%.1fms elapsed)' % [ (Time.now - start_time) * 1000 ])
54
+ end
55
+
56
+ def in_thread
57
+ Thread.new do
58
+ Thread.abort_on_exception = true
59
+
60
+ begin
61
+ yield
62
+
63
+ rescue => e
64
+ $stderr.puts("[%s] %s", [ e.class, e ])
65
+ $stderr.puts(e.backtrace.join("\n"))
66
+
67
+ raise e
68
+ end
69
+ end
70
+ end
42
71
  end