skein 0.3.7 → 0.8.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.
- checksums.yaml +5 -5
- data/Gemfile +4 -1
- data/Gemfile.lock +49 -36
- data/LICENSE.md +1 -1
- data/README.md +9 -4
- data/RELEASES.md +2 -0
- data/VERSION +1 -1
- data/bin/skein +1 -1
- data/examples/echo +66 -0
- data/examples/echo-server +77 -0
- data/lib/skein/adapter.rb +3 -0
- data/lib/skein/client/publisher.rb +10 -2
- data/lib/skein/client/rpc.rb +78 -19
- data/lib/skein/client/subscriber.rb +27 -4
- data/lib/skein/client/worker.rb +221 -63
- data/lib/skein/client.rb +20 -11
- data/lib/skein/config.rb +3 -1
- data/lib/skein/connected.rb +88 -17
- data/lib/skein/context.rb +5 -2
- data/lib/skein/handler/async.rb +6 -2
- data/lib/skein/handler.rb +131 -20
- data/lib/skein/rabbitmq.rb +1 -0
- data/lib/skein/timeout_queue.rb +43 -0
- data/lib/skein.rb +18 -2
- data/skein.gemspec +21 -24
- data/test/helper.rb +29 -0
- data/test/unit/test_skein_client.rb +4 -1
- data/test/unit/test_skein_client_publisher.rb +1 -1
- data/test/unit/test_skein_client_rpc.rb +37 -0
- data/test/unit/test_skein_client_subscriber.rb +29 -12
- data/test/unit/test_skein_client_worker.rb +22 -9
- data/test/unit/test_skein_connected.rb +21 -0
- data/test/unit/test_skein_rpc_timeout.rb +19 -0
- data/test/unit/test_skein_worker.rb +4 -0
- metadata +41 -16
- data/lib/skein/rpc/base.rb +0 -23
- data/lib/skein/rpc/error.rb +0 -34
- data/lib/skein/rpc/notification.rb +0 -2
- data/lib/skein/rpc/request.rb +0 -62
- data/lib/skein/rpc/response.rb +0 -38
- data/lib/skein/rpc.rb +0 -24
- data/test/unit/test_skein_rpc_error.rb +0 -10
- 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
|
36
|
+
@reporter&.exception!(*args)
|
37
37
|
end
|
38
38
|
|
39
39
|
def trap
|
40
40
|
yield
|
41
|
-
|
41
|
+
|
42
|
+
rescue SystemExit
|
43
|
+
raise
|
44
|
+
rescue Object => e
|
42
45
|
self.exception!(e)
|
43
46
|
end
|
44
47
|
end
|
data/lib/skein/handler/async.rb
CHANGED
@@ -2,8 +2,12 @@ class Skein::Handler::Async < Skein::Handler
|
|
2
2
|
# == Instance Methods =====================================================
|
3
3
|
|
4
4
|
def delegate(*args)
|
5
|
-
|
6
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
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
|
data/lib/skein/rabbitmq.rb
CHANGED
@@ -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
|
-
|
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.
|
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.
|
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 = "
|
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/
|
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 = "
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|