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.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b32f15d7b24be749e57fd000f914ce2c6bebe627
4
- data.tar.gz: 9b385eeed01567244a1a1840dbe0a85dbce02e09
2
+ SHA256:
3
+ metadata.gz: 1f9116f29d28ed7c32ceda7ae576d7339b5d10d25232b15d785468823622e2b1
4
+ data.tar.gz: 186e7299d4e36079bd527dd6829ec5cb882ba26447be7ec126605fde7c6c8de9
5
5
  SHA512:
6
- metadata.gz: 81c8f2c46312e393ee8d226d353292e4cd83c56b209fe73f25165a42468ac55c4f8511b9afba0f66d075199b928259c56a2545a2c8f00584ca0909c9b158496b
7
- data.tar.gz: 11e302e24b88f989c80e750c3f2da37ba8720d51c68bc4da93b473186dba9df800fb3d15bc5c02f3d4f175ed7fe79a1e07a8d185cdf9bf00a3d12a25b6c532ce
6
+ metadata.gz: 2d0229f1b32971be57e3b1164af63c3fdbfbb46cc29bd277ad7a95341e880d32a8056f7a917b5178ddf5319d58c7856f9c599475f5508cd1da27c02b6f6e5744
7
+ data.tar.gz: c960a2492da91dc3595658a48e86cae0ac9e627c15d156fbbcf615a24c2d49ea14e5b01512748b50bece8c06013686c680234a25bd2d11952c66aa6d528b16cb
data/Gemfile CHANGED
@@ -1,8 +1,11 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'birling'
3
+ gem 'birling', '>= 0.2.0'
4
4
 
5
5
  group :development, :test do
6
+ gem 'bunny', platforms: :ruby
7
+ gem 'march_hare', platforms: :jruby
8
+
6
9
  gem 'rake'
7
10
  gem 'jeweler'
8
11
  gem 'test-unit'
data/Gemfile.lock CHANGED
@@ -1,57 +1,68 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- addressable (2.5.0)
5
- public_suffix (~> 2.0, >= 2.0.2)
6
- birling (0.1.3)
7
- builder (3.2.3)
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.3.0)
13
- github_api (0.11.3)
14
- addressable (~> 2.3)
15
- descendants_tracker (~> 0.0.1)
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 (>= 1.2)
18
- multi_json (>= 1.7.5, < 2.0)
19
- nokogiri (~> 1.6.0)
20
- oauth2
21
- hashie (3.5.5)
22
- highline (1.7.8)
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 (>= 1.0)
28
+ bundler
26
29
  git (>= 1.2.5)
27
- github_api (~> 0.11.0)
30
+ github_api (~> 0.16.0)
28
31
  highline (>= 1.6.15)
29
32
  nokogiri (>= 1.5.10)
30
- psych (~> 2.2)
33
+ psych
31
34
  rake
32
35
  rdoc
33
36
  semver2
34
- jwt (1.5.6)
35
- mini_portile2 (2.1.0)
36
- multi_json (1.12.1)
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.0.0)
39
- nokogiri (1.6.8.1)
40
- mini_portile2 (~> 2.1.0)
41
- oauth2 (1.3.1)
42
- faraday (>= 0.8, < 0.12)
43
- jwt (~> 1.0)
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 (1.0.1)
48
- psych (2.2.4)
49
- public_suffix (2.0.5)
50
- rack (2.0.1)
51
- rake (12.0.0)
52
- rdoc (5.1.0)
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
- test-unit (3.2.3)
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
- 1.14.6
81
+ 2.2.27
data/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2016 Scott Tadman, PostageApp
1
+ Copyright (c) 2016-2019 Scott Tadman, PostageApp Ltd.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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 standard
4
- and implementation for Ruby that defines how to dispatch
5
- [JSON-RPC](http://json-rpc.org) jobs over AMQP.
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 jRuby and MRI Ruby are supported.
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.3.7
1
+ 0.8.1
data/bin/skein CHANGED
@@ -128,7 +128,7 @@ when 'subscribe'
128
128
  end
129
129
  when 'echo'
130
130
  rescue_safely(options) do
131
- results = Queue.new
131
+ results = TimeoutQueue.new
132
132
 
133
133
  start = Time.now
134
134
 
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(queue_name, connection: nil, context: nil)
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(queue_name)
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
@@ -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
- case (callback)
33
- when Queue
34
- callback << response['result']
35
- when Proc
36
- callback.call
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 and @consumer.cancel
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 do
76
- yield
77
- end
133
+ EventMachine.next_tick(&block)
78
134
  else
79
- lambda do
80
- yield
81
- end
135
+ block
82
136
  end
83
137
  elsif (blocking)
84
- queue = Queue.new
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