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
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