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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1f9116f29d28ed7c32ceda7ae576d7339b5d10d25232b15d785468823622e2b1
|
4
|
+
data.tar.gz: 186e7299d4e36079bd527dd6829ec5cb882ba26447be7ec126605fde7c6c8de9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d0229f1b32971be57e3b1164af63c3fdbfbb46cc29bd277ad7a95341e880d32a8056f7a917b5178ddf5319d58c7856f9c599475f5508cd1da27c02b6f6e5744
|
7
|
+
data.tar.gz: c960a2492da91dc3595658a48e86cae0ac9e627c15d156fbbcf615a24c2d49ea14e5b01512748b50bece8c06013686c680234a25bd2d11952c66aa6d528b16cb
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,57 +1,68 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
-
addressable (2.
|
5
|
-
|
6
|
-
birling (0.1
|
7
|
-
builder (3.2.
|
4
|
+
addressable (2.4.0)
|
5
|
+
amq-protocol (2.3.2)
|
6
|
+
birling (0.3.1)
|
7
|
+
builder (3.2.4)
|
8
|
+
bunny (2.19.0)
|
9
|
+
amq-protocol (~> 2.3, >= 2.3.1)
|
10
|
+
sorted_set (~> 1, >= 1.0.2)
|
8
11
|
descendants_tracker (0.0.4)
|
9
12
|
thread_safe (~> 0.3, >= 0.3.1)
|
10
13
|
faraday (0.9.2)
|
11
14
|
multipart-post (>= 1.2, < 3)
|
12
|
-
git (1.
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
git (1.9.1)
|
16
|
+
rchardet (~> 1.8)
|
17
|
+
github_api (0.16.0)
|
18
|
+
addressable (~> 2.4.0)
|
19
|
+
descendants_tracker (~> 0.0.4)
|
16
20
|
faraday (~> 0.8, < 0.10)
|
17
|
-
hashie (>=
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
jeweler (2.3.3)
|
21
|
+
hashie (>= 3.4)
|
22
|
+
mime-types (>= 1.16, < 3.0)
|
23
|
+
oauth2 (~> 1.0)
|
24
|
+
hashie (4.1.0)
|
25
|
+
highline (2.0.3)
|
26
|
+
jeweler (2.3.9)
|
24
27
|
builder
|
25
|
-
bundler
|
28
|
+
bundler
|
26
29
|
git (>= 1.2.5)
|
27
|
-
github_api (~> 0.
|
30
|
+
github_api (~> 0.16.0)
|
28
31
|
highline (>= 1.6.15)
|
29
32
|
nokogiri (>= 1.5.10)
|
30
|
-
psych
|
33
|
+
psych
|
31
34
|
rake
|
32
35
|
rdoc
|
33
36
|
semver2
|
34
|
-
jwt (
|
35
|
-
|
36
|
-
|
37
|
+
jwt (2.2.3)
|
38
|
+
mime-types (2.99.3)
|
39
|
+
mini_portile2 (2.6.1)
|
40
|
+
multi_json (1.15.0)
|
37
41
|
multi_xml (0.6.0)
|
38
|
-
multipart-post (2.
|
39
|
-
nokogiri (1.
|
40
|
-
mini_portile2 (~> 2.1
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
multipart-post (2.1.1)
|
43
|
+
nokogiri (1.12.4)
|
44
|
+
mini_portile2 (~> 2.6.1)
|
45
|
+
racc (~> 1.4)
|
46
|
+
oauth2 (1.4.7)
|
47
|
+
faraday (>= 0.8, < 2.0)
|
48
|
+
jwt (>= 1.0, < 3.0)
|
44
49
|
multi_json (~> 1.3)
|
45
50
|
multi_xml (~> 0.5)
|
46
51
|
rack (>= 1.2, < 3)
|
47
|
-
power_assert (
|
48
|
-
psych (
|
49
|
-
|
50
|
-
rack (2.
|
51
|
-
rake (
|
52
|
-
|
52
|
+
power_assert (2.0.1)
|
53
|
+
psych (4.0.1)
|
54
|
+
racc (1.5.2)
|
55
|
+
rack (2.2.3)
|
56
|
+
rake (13.0.6)
|
57
|
+
rbtree (0.4.4)
|
58
|
+
rchardet (1.8.0)
|
59
|
+
rdoc (6.3.2)
|
53
60
|
semver2 (3.4.2)
|
54
|
-
|
61
|
+
set (1.0.1)
|
62
|
+
sorted_set (1.0.3)
|
63
|
+
rbtree
|
64
|
+
set (~> 1.0)
|
65
|
+
test-unit (3.4.7)
|
55
66
|
power_assert
|
56
67
|
thread_safe (0.3.6)
|
57
68
|
|
@@ -59,10 +70,12 @@ PLATFORMS
|
|
59
70
|
ruby
|
60
71
|
|
61
72
|
DEPENDENCIES
|
62
|
-
birling
|
73
|
+
birling (>= 0.2.0)
|
74
|
+
bunny
|
63
75
|
jeweler
|
76
|
+
march_hare
|
64
77
|
rake
|
65
78
|
test-unit
|
66
79
|
|
67
80
|
BUNDLED WITH
|
68
|
-
|
81
|
+
2.2.27
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Skein
|
2
2
|
|
3
|
-
[Skein](https://en.wikipedia.org/wiki/V_formation) is a RabbitMQ-based
|
4
|
-
|
5
|
-
[JSON-RPC](http://json-rpc.org)
|
3
|
+
[Skein](https://en.wikipedia.org/wiki/V_formation) is a RabbitMQ-based method
|
4
|
+
for handling remote procedure calls (RPC) and pub/sub channels over AMQP using
|
5
|
+
[JSON-RPC](http://json-rpc.org) payloads.
|
6
6
|
|
7
7
|
## Dependencies
|
8
8
|
|
@@ -10,7 +10,7 @@ This library requires an active AMQP server like [RabbitMQ](http://rabbitmq.com)
|
|
10
10
|
and a Ruby driver for AMQP like [Bunny](http://rubybunny.info) or
|
11
11
|
[March Hare](http://rubymarchhare.info).
|
12
12
|
|
13
|
-
Both
|
13
|
+
Both JRuby and MRI Ruby are supported with the appropriate driver.
|
14
14
|
|
15
15
|
## Installation
|
16
16
|
|
@@ -55,3 +55,8 @@ a `Skein::Client::Worker` instance:
|
|
55
55
|
end
|
56
56
|
|
57
57
|
Responder.new('test_queue')
|
58
|
+
|
59
|
+
## Debugging
|
60
|
+
|
61
|
+
Setting the environment variable `SKEIN_DEBUG_JSON` will show raw JSON
|
62
|
+
payloads received by both RPC workers and clients.
|
data/RELEASES.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
* 0.7.1 - Allow setting of default Skein.config configuration
|
2
|
+
* 0.7.0 - Automatic recovering from closed RabbitMQ channels
|
1
3
|
* 0.3.0 - Adding EventMachine compatibility
|
2
4
|
* 0.2.1 - Cleaning up RPC interface with blocking vs. non-blocking
|
3
5
|
* 0.2.0 - First working version in JRuby/MRI Ruby
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.8.1
|
data/bin/skein
CHANGED
data/examples/echo
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# == Imports ================================================================
|
4
|
+
|
5
|
+
require 'optparse'
|
6
|
+
require 'securerandom'
|
7
|
+
require 'bundler/setup'
|
8
|
+
|
9
|
+
Bundler.require(:default)
|
10
|
+
|
11
|
+
require_relative '../lib/skein'
|
12
|
+
|
13
|
+
# == Support Classes ========================================================
|
14
|
+
|
15
|
+
# == Main ===================================================================
|
16
|
+
|
17
|
+
config = Skein::Config.new
|
18
|
+
|
19
|
+
config.queue = 'skein-echo-test'
|
20
|
+
|
21
|
+
options = {
|
22
|
+
repeat: false
|
23
|
+
}
|
24
|
+
|
25
|
+
program = OptionParser.new do |opts|
|
26
|
+
opts.on('-q', '--queue NAME', 'Queue to listen on') do |s|
|
27
|
+
config.queue = s
|
28
|
+
end
|
29
|
+
opts.on('-e', '--exchange NAME', 'Exchange to attach to') do |s|
|
30
|
+
config.exchange = s
|
31
|
+
end
|
32
|
+
opts.on('-u', '--username NAME', 'Authenticate with username') do |s|
|
33
|
+
config.username = s
|
34
|
+
end
|
35
|
+
opts.on('-p', '--password NAME', 'Authenticate with password') do |s|
|
36
|
+
config.password = s
|
37
|
+
end
|
38
|
+
opts.on('-H', '--host NAME', 'Connect to RabbitMQ host') do |s|
|
39
|
+
config.host = s
|
40
|
+
end
|
41
|
+
opts.on('-P', '--port NAME', 'Connect to RabbitMQ port') do |s|
|
42
|
+
config.port = s.to_i
|
43
|
+
end
|
44
|
+
opts.on('-r', '--repeat', 'Repeat echo') do |s|
|
45
|
+
options[:repeat] = true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
program.parse!(ARGV)
|
50
|
+
|
51
|
+
client = Skein::Client.new(
|
52
|
+
connection: Skein::RabbitMQ.connect(config)
|
53
|
+
)
|
54
|
+
|
55
|
+
rpc = client.rpc(config.exchange, routing_key: config.queue)
|
56
|
+
threads = Hash.new { |h,k| h[k] = h.length }
|
57
|
+
|
58
|
+
loop do
|
59
|
+
puts rpc.echo(SecureRandom.uuid)
|
60
|
+
|
61
|
+
if (options[:repeat])
|
62
|
+
sleep(1)
|
63
|
+
else
|
64
|
+
break
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# == Imports ================================================================
|
4
|
+
|
5
|
+
require 'optparse'
|
6
|
+
require 'bundler/setup'
|
7
|
+
|
8
|
+
Bundler.require(:default)
|
9
|
+
|
10
|
+
require_relative '../lib/skein'
|
11
|
+
|
12
|
+
# == Support Classes ========================================================
|
13
|
+
|
14
|
+
class EchoWorker < Skein::Client::Worker
|
15
|
+
def echo(*args)
|
16
|
+
puts "Received: #{args.inspect}"
|
17
|
+
|
18
|
+
args
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# == Main ===================================================================
|
23
|
+
|
24
|
+
config = Skein::Config.new
|
25
|
+
|
26
|
+
config.queue = 'skein-echo-test'
|
27
|
+
config.concurrency = 1
|
28
|
+
|
29
|
+
program = OptionParser.new do |opts|
|
30
|
+
opts.on('-q', '--queue NAME', 'Queue to listen on') do |s|
|
31
|
+
config.queue = s
|
32
|
+
end
|
33
|
+
opts.on('-e', '--exchange NAME', 'Exchange to attach to') do |s|
|
34
|
+
config.exchange = s
|
35
|
+
end
|
36
|
+
opts.on('-u', '--username NAME', 'Authenticate with username') do |s|
|
37
|
+
config.username = s
|
38
|
+
end
|
39
|
+
opts.on('-p', '--password NAME', 'Authenticate with password') do |s|
|
40
|
+
config.password = s
|
41
|
+
end
|
42
|
+
opts.on('-H', '--host NAME', 'Connect to RabbitMQ host') do |s|
|
43
|
+
config.host = s
|
44
|
+
end
|
45
|
+
opts.on('-P', '--port NAME', 'Connect to RabbitMQ port') do |s|
|
46
|
+
config.port = s.to_i
|
47
|
+
end
|
48
|
+
opts.on('-w', '--workers COUNT', 'Set workers count') do |s|
|
49
|
+
config.concurrency = s.to_i
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
program.parse!(ARGV)
|
54
|
+
|
55
|
+
worker = EchoWorker.new(
|
56
|
+
config.queue,
|
57
|
+
exchange_name: config.exchange,
|
58
|
+
concurrency: config.concurrency,
|
59
|
+
connection: Skein::RabbitMQ.connect(config)
|
60
|
+
)
|
61
|
+
|
62
|
+
puts "EchoWorker active on #{config.queue}"
|
63
|
+
|
64
|
+
Signal.trap('INT') do |signal|
|
65
|
+
# Force quit immediately, don't wait on threads
|
66
|
+
Process.exit!(0)
|
67
|
+
end
|
68
|
+
|
69
|
+
Signal.trap('QUIT') do |signal|
|
70
|
+
puts "\r Threads: #{Thread.list.length}"
|
71
|
+
Thread.list.each do |thread|
|
72
|
+
puts '#%s %s' % [ thread.object_id, thread.name ]
|
73
|
+
puts thread.backtrace.join("\n")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
worker.join
|
data/lib/skein/adapter.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
module Skein::Adapter
|
2
2
|
# == Mixin Methods =========================================================
|
3
3
|
|
4
|
+
# REFACTOR: This should be converted into a proper subclass of the
|
5
|
+
# various drivers that does the method re-writing at a lower level.
|
6
|
+
|
4
7
|
def subscribe(queue, block: true, manual_ack: true)
|
5
8
|
case (queue.class.to_s.split(/::/)[0])
|
6
9
|
when 'Bunny'
|
@@ -1,14 +1,22 @@
|
|
1
1
|
class Skein::Client::Publisher < Skein::Connected
|
2
2
|
# == Instance Methods =====================================================
|
3
3
|
|
4
|
-
def initialize(
|
4
|
+
def initialize(exchange_name, type: nil, durable: nil, connection: nil, context: nil)
|
5
5
|
super(connection: connection, context: context)
|
6
6
|
|
7
|
-
@queue = self.channel.topic
|
7
|
+
@queue = self.channel.send(type || :topic, exchange_name, durable: durable)
|
8
8
|
end
|
9
9
|
|
10
10
|
def publish!(message, routing_key = nil)
|
11
11
|
@queue.publish(JSON.dump(message), routing_key: routing_key)
|
12
12
|
end
|
13
13
|
alias_method :<<, :publish!
|
14
|
+
|
15
|
+
def close(delete_queue: false)
|
16
|
+
if (delete_queue)
|
17
|
+
@queue.delete
|
18
|
+
end
|
19
|
+
|
20
|
+
super()
|
21
|
+
end
|
14
22
|
end
|
data/lib/skein/client/rpc.rb
CHANGED
@@ -2,6 +2,11 @@ require 'securerandom'
|
|
2
2
|
require 'fiber'
|
3
3
|
|
4
4
|
class Skein::Client::RPC < Skein::Connected
|
5
|
+
# == Exceptions ===========================================================
|
6
|
+
|
7
|
+
class RPCException < Exception
|
8
|
+
end
|
9
|
+
|
5
10
|
# == Constants ============================================================
|
6
11
|
|
7
12
|
EXCHANGE_NAME_DEFAULT = ''.freeze
|
@@ -10,30 +15,70 @@ class Skein::Client::RPC < Skein::Connected
|
|
10
15
|
|
11
16
|
# == Instance Methods =====================================================
|
12
17
|
|
13
|
-
def initialize(exchange_name = nil, routing_key: nil, connection: nil, context: nil)
|
14
|
-
super(connection: connection, context: context)
|
18
|
+
def initialize(exchange_name = nil, routing_key: nil, connection: nil, context: nil, ident: nil, expiration: nil, persistent: true, durable: true, timeout: nil)
|
19
|
+
super(connection: connection, context: context, ident: ident)
|
15
20
|
|
16
|
-
@rpc_exchange = self.channel.direct(exchange_name || EXCHANGE_NAME_DEFAULT, durable: true)
|
17
21
|
@routing_key = routing_key
|
22
|
+
@timeout = timeout
|
23
|
+
|
24
|
+
@rpc_exchange = self.channel.direct(
|
25
|
+
exchange_name || EXCHANGE_NAME_DEFAULT,
|
26
|
+
durable: durable
|
27
|
+
)
|
28
|
+
|
18
29
|
@response_queue = self.channel.queue(
|
19
30
|
@ident,
|
20
31
|
durable: false,
|
21
32
|
header: true,
|
22
33
|
auto_delete: true
|
23
34
|
)
|
35
|
+
@expiration = expiration
|
36
|
+
@persistent = !!persistent
|
24
37
|
|
25
38
|
@callback = { }
|
26
39
|
|
27
40
|
@consumer = Skein::Adapter.subscribe(@response_queue, block: false) do |payload, delivery_tag, reply_to|
|
28
41
|
self.context.trap do
|
42
|
+
if (ENV['SKEIN_DEBUG_JSON'])
|
43
|
+
$stdout.puts(payload)
|
44
|
+
end
|
45
|
+
|
29
46
|
response = JSON.load(payload)
|
30
47
|
|
31
48
|
if (callback = @callback.delete(response['id']))
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
49
|
+
if (response['error'])
|
50
|
+
exception =
|
51
|
+
case (response['error'] and response['error']['code'])
|
52
|
+
when -32601
|
53
|
+
NoMethodError.new(
|
54
|
+
"%s from `%s' RPC call" % [
|
55
|
+
response.dig('error', 'message'),
|
56
|
+
response.dig('error', 'data', 'method')
|
57
|
+
]
|
58
|
+
)
|
59
|
+
when -32602
|
60
|
+
ArgumentError.new(
|
61
|
+
response.dig('error', 'data', 'message') || 'wrong number of arguments'
|
62
|
+
)
|
63
|
+
else
|
64
|
+
RPCException.new(
|
65
|
+
response.dig('error', 'data', 'message') || response.dig('error', 'message')
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
case (callback)
|
70
|
+
when Skein::TimeoutQueue
|
71
|
+
callback << exception
|
72
|
+
when Proc
|
73
|
+
callback.call(exception)
|
74
|
+
end
|
75
|
+
else
|
76
|
+
case (callback)
|
77
|
+
when Skein::TimeoutQueue
|
78
|
+
callback << response['result']
|
79
|
+
when Proc
|
80
|
+
callback.call(response['result'])
|
81
|
+
end
|
37
82
|
end
|
38
83
|
end
|
39
84
|
|
@@ -42,20 +87,31 @@ class Skein::Client::RPC < Skein::Connected
|
|
42
87
|
end
|
43
88
|
end
|
44
89
|
|
90
|
+
# Temporarily deliver RPC calls to a different routing key. The supplied
|
91
|
+
# block is executed with this temporary routing in effect.
|
92
|
+
def reroute!(routing_key)
|
93
|
+
routing_key, @routing_key = @routing_key, routing_key
|
94
|
+
|
95
|
+
yield if (block_given?)
|
96
|
+
|
97
|
+
@routing_key = routing_key
|
98
|
+
end
|
99
|
+
|
45
100
|
def close
|
46
|
-
@consumer
|
101
|
+
@consumer&.cancel
|
47
102
|
@consumer = nil
|
48
103
|
|
49
104
|
super
|
50
105
|
end
|
51
106
|
|
52
|
-
def method_missing(name, *args)
|
107
|
+
def method_missing(name, *args, &block)
|
53
108
|
name = name.to_s
|
54
109
|
|
55
110
|
blocking = !name.sub!(/!\z/, '')
|
56
111
|
|
57
112
|
message_id = SecureRandom.uuid
|
58
113
|
request = JSON.dump(
|
114
|
+
jsonrpc: '2.0',
|
59
115
|
method: name,
|
60
116
|
params: args,
|
61
117
|
id: message_id
|
@@ -66,26 +122,29 @@ class Skein::Client::RPC < Skein::Connected
|
|
66
122
|
routing_key: @routing_key,
|
67
123
|
reply_to: blocking ? @ident : nil,
|
68
124
|
content_type: 'application/json',
|
69
|
-
message_id: message_id
|
125
|
+
message_id: message_id,
|
126
|
+
persistent: @persistent,
|
127
|
+
expiration: @expiration
|
70
128
|
)
|
71
129
|
|
72
130
|
if (block_given?)
|
73
131
|
@callback[message_id] =
|
74
132
|
if (defined?(EventMachine))
|
75
|
-
EventMachine.next_tick
|
76
|
-
yield
|
77
|
-
end
|
133
|
+
EventMachine.next_tick(&block)
|
78
134
|
else
|
79
|
-
|
80
|
-
yield
|
81
|
-
end
|
135
|
+
block
|
82
136
|
end
|
83
137
|
elsif (blocking)
|
84
|
-
queue =
|
138
|
+
queue = Skein::TimeoutQueue.new(blocking: true, timeout: @timeout)
|
85
139
|
|
86
140
|
@callback[message_id] = queue
|
87
141
|
|
88
|
-
queue.pop
|
142
|
+
case (result = queue.pop)
|
143
|
+
when Exception
|
144
|
+
raise result
|
145
|
+
else
|
146
|
+
result
|
147
|
+
end
|
89
148
|
end
|
90
149
|
end
|
91
150
|
end
|