grumlin 0.3.0 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e532964db5d5afd11d89978a132a5f4f7d1a4d98c740f639acadac260c93be89
4
- data.tar.gz: 9015cb71bbab17895d8809dc7ff514b90e9d1ceb81ed6a9eec446a0db7ec0994
3
+ metadata.gz: 11e434ab0f5cd463d357e69dd3cbf5083f85a1bdd32e4d2c96023ce262704020
4
+ data.tar.gz: f24dd70f8b0abe9b370f77587d590802b32aa3e7b8a20380b9518004868cc6c7
5
5
  SHA512:
6
- metadata.gz: a4295b9e6041726c36c05cc0aed94a645e8d7103c99351dfadef7077f14037b81daf87a4ffb1fd48105d8c1fa888e96ce173aa3be42c9b35ec58de3e289c01ed
7
- data.tar.gz: 51824ca159be63be91fc798b40777a25d55f0ce423b07a9ce4ce9880002e94b4e4196bc0d92b4ca84bee86356f17fd90f3cc2dd9daa594f906fc2e93f11be422
6
+ metadata.gz: 5bfbd0d4db9ef46d35839b8ca5654cdc1f5038ee2c4ca004b678471380095d1fb587fa89a8df0c5a4769e3943ceb4e0b88734b701593ccf1047a05ab85baaaf5
7
+ data.tar.gz: a7d87e630e6b2076bedd079365e709018b3fc61f1577564c504593ffdeb4deab88f56d8785c30877489171b6c9d7fd245982475a75f4c639647c923e2ca296ef
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- grumlin (0.3.0)
4
+ grumlin (0.4.0)
5
5
  async-pool (~> 0.3)
6
6
  async-websocket (~> 0.19)
7
7
 
@@ -59,7 +59,7 @@ GEM
59
59
  kramdown-parser-gfm (1.1.0)
60
60
  kramdown (~> 2.0)
61
61
  minitest (5.14.4)
62
- nio4r (2.5.7)
62
+ nio4r (2.5.8)
63
63
  nokogiri (1.11.7-x86_64-linux)
64
64
  racc (~> 1.4)
65
65
  overcommit (0.57.0)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Async
4
+ # Channel is a wrapper around Async::Queue that provides
5
+ # a protocol and handy tools for passing data, exceptions and closing.
6
+ # It is designed to be used with only one publisher and one subscriber
7
+ class Channel
8
+ class ChannelError < StandardError; end
9
+
10
+ class ChannelClosedError < ChannelError; end
11
+
12
+ def initialize
13
+ @queue = Async::Queue.new
14
+ @closed = false
15
+ end
16
+
17
+ def closed?
18
+ @closed
19
+ end
20
+
21
+ # Methods for a publisher
22
+ def <<(payload)
23
+ raise(ChannelClosedError, "Cannot send to a closed channel") if @closed
24
+
25
+ @queue << [:payload, payload]
26
+ end
27
+
28
+ def exception(exception)
29
+ raise(ChannelClosedError, "Cannot send to a closed channel") if closed?
30
+
31
+ @queue << [:exception, exception]
32
+ end
33
+
34
+ def close
35
+ raise(ChannelClosedError, "Cannot close a closed channel") if closed?
36
+
37
+ @queue << [:close]
38
+ @closed = true
39
+ end
40
+
41
+ # Methods for a subscriber
42
+ def dequeue
43
+ each do |payload| # rubocop:disable Lint/UnreachableLoop this is intended
44
+ return payload
45
+ end
46
+ end
47
+
48
+ def each # rubocop:disable Metrics/MethodLength
49
+ raise(ChannelClosedError, "Cannot receive from a closed channel") if closed?
50
+
51
+ @queue.each do |type, payload|
52
+ case type
53
+ when :exception
54
+ payload.set_backtrace(caller + (payload.backtrace || [])) # A hack to preserve full backtrace
55
+ raise payload
56
+ when :payload
57
+ yield payload
58
+ when :close
59
+ break
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
data/lib/grumlin.rb CHANGED
@@ -12,6 +12,8 @@ require "async/barrier"
12
12
  require "async/http/endpoint"
13
13
  require "async/websocket/client"
14
14
 
15
+ require_relative "async/channel"
16
+
15
17
  require_relative "grumlin/version"
16
18
  require_relative "grumlin/exceptions"
17
19
 
@@ -38,12 +40,13 @@ require_relative "grumlin/sugar"
38
40
 
39
41
  module Grumlin
40
42
  class Config
41
- attr_accessor :url, :pool_size, :client_concurrency
43
+ attr_accessor :url, :pool_size, :client_concurrency, :client_factory
42
44
 
43
- # For some reason, client_concurrency must be greather pool_size
45
+ # For some reason, client_concurrency must be greater than pool_size
44
46
  def initialize
45
47
  @pool_size = 10
46
48
  @client_concurrency = 20
49
+ @client_factory = ->(url, parent) { Grumlin::Client.new(url, parent: parent) }
47
50
  end
48
51
 
49
52
  def default_pool
@@ -2,45 +2,49 @@
2
2
 
3
3
  module Grumlin
4
4
  class Client
5
- class PoolResource < self
6
- attr :concurrency, :count
5
+ class PoolResource < Async::Pool::Resource
6
+ attr_reader :client
7
7
 
8
8
  def self.call
9
- new(Grumlin.config.url, concurrency: Grumlin.config.client_concurrency).tap(&:connect)
9
+ config = Grumlin.config
10
+ new(config.url, client_factory: config.client_factory, concurrency: config.client_concurrency)
10
11
  end
11
12
 
12
- def initialize(url, concurrency: 1, parent: Async::Task.current)
13
- super(url, parent: parent)
14
- @concurrency = concurrency
15
- @count = 0
13
+ def initialize(url, client_factory:, concurrency: 1, parent: Async::Task.current)
14
+ super(concurrency)
15
+ @client = client_factory.call(url, parent).tap(&:connect)
16
16
  end
17
17
 
18
- def viable?
19
- connected?
18
+ def closed?
19
+ !@client.connected?
20
20
  end
21
21
 
22
- def closed?
23
- connected?
22
+ def close
23
+ @client.close
24
24
  end
25
25
 
26
- def reusable?
27
- true
26
+ def write(*args)
27
+ @client.write(*args)
28
28
  end
29
29
  end
30
30
 
31
- def initialize(url, parent: Async::Task.current)
31
+ def initialize(url, parent: Async::Task.current, **client_options)
32
+ @url = url
33
+ @client_options = client_options
32
34
  @parent = parent
33
- @transport = Transport.new(url)
34
35
  reset!
35
36
  end
36
37
 
37
38
  def connect
38
- response_queue = @transport.connect
39
+ @transport = build_transport
40
+ response_channel = @transport.connect
39
41
  @request_dispatcher = RequestDispatcher.new
40
42
  @parent.async do
41
- response_queue.each do |response|
43
+ response_channel.each do |response|
42
44
  @request_dispatcher.add_response(response)
43
45
  end
46
+ rescue StandardError
47
+ close
44
48
  end
45
49
  end
46
50
 
@@ -52,31 +56,28 @@ module Grumlin
52
56
  end
53
57
 
54
58
  def connected?
55
- @transport.connected?
59
+ @transport&.connected? || false
56
60
  end
57
61
 
58
62
  # TODO: support yielding
59
63
  def write(*args) # rubocop:disable Metrics/MethodLength
64
+ raise NotConnectedError unless connected?
65
+
60
66
  request_id = SecureRandom.uuid
61
67
  request = to_query(request_id, args)
62
- queue = @request_dispatcher.add_request(request)
68
+ channel = @request_dispatcher.add_request(request)
63
69
  @transport.write(request)
64
70
 
65
71
  begin
66
- msg, response = queue.dequeue
67
- raise response if msg == :error
68
-
69
- return response.flat_map { |item| Typing.cast(item) } if msg == :result
70
-
71
- raise "ERROR"
72
+ channel.dequeue.flat_map { |item| Typing.cast(item) }
72
73
  rescue Async::Stop
73
74
  retry if @request_dispatcher.ongoing_request?(request_id)
74
- raise UnknownRequestStopped, "#{request_id} is not in the ongoing requests list"
75
+ raise Grumlin::UnknownRequestStoppedError, "#{request_id} is not in the ongoing requests list"
75
76
  end
76
77
  end
77
78
 
78
79
  def inspect
79
- "<#{self.class} url=#{@transport.url}>"
80
+ "<#{self.class} url=#{@url} connected=#{connected?}>"
80
81
  end
81
82
 
82
83
  alias to_s inspect
@@ -97,6 +98,11 @@ module Grumlin
97
98
 
98
99
  def reset!
99
100
  @request_dispatcher = nil
101
+ @transport = nil
102
+ end
103
+
104
+ def build_transport
105
+ Transport.new(@url, parent: @parent, **@client_options)
100
106
  end
101
107
  end
102
108
  end
@@ -29,12 +29,12 @@ module Grumlin
29
29
  def add_request(request)
30
30
  raise "ERROR" if @requests.key?(request[:requestId])
31
31
 
32
- Async::Queue.new.tap do |queue|
33
- @requests[request[:requestId]] = { request: request, result: [], queue: queue }
32
+ Async::Channel.new.tap do |channel|
33
+ @requests[request[:requestId]] = { request: request, result: [], channel: channel }
34
34
  end
35
35
  end
36
36
 
37
- # builds a response object, when it's ready sends it to the client via a queue
37
+ # builds a response object, when it's ready sends it to the client via a channel
38
38
  # TODO: sometimes response does not include requestID, no idea how to handle it so far.
39
39
  def add_response(response) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
40
40
  request_id = response[:requestId]
@@ -46,15 +46,15 @@ module Grumlin
46
46
 
47
47
  case SUCCESS[response.dig(:status, :code)]
48
48
  when :success
49
- request[:queue] << [:result, request[:result] + [response.dig(:result, :data)]]
49
+ request[:channel] << request[:result] + [response.dig(:result, :data)]
50
50
  close_request(request_id)
51
51
  when :partial_content then request[:result] << response.dig(:result, :data)
52
52
  when :no_content
53
- request[:queue] << [:result, []]
53
+ request[:channel] << []
54
54
  close_request(request_id)
55
55
  end
56
56
  rescue StandardError => e
57
- request[:queue] << [:error, e]
57
+ request[:channel].exception(e)
58
58
  close_request(request_id)
59
59
  end
60
60
 
@@ -62,7 +62,7 @@ module Grumlin
62
62
  raise "ERROR" unless ongoing_request?(request_id)
63
63
 
64
64
  request = @requests.delete(request_id)
65
- request[:queue] << nil
65
+ request[:channel].close
66
66
  end
67
67
 
68
68
  def ongoing_request?(request_id)
@@ -4,64 +4,72 @@ module Grumlin
4
4
  class Transport
5
5
  # A transport based on https://github.com/socketry/async
6
6
  # and https://github.com/socketry/async-websocket
7
- def initialize(url, parent: Async::Task.current)
8
- @endpoint = Async::HTTP::Endpoint.parse(url)
7
+
8
+ attr_reader :url
9
+
10
+ def initialize(url, parent: Async::Task.current, **client_options)
11
+ @url = url
9
12
  @parent = parent
10
- @request_queue = Async::Queue.new
11
- @response_queue = Async::Queue.new
13
+ @client_options = client_options
14
+ @request_channel = Async::Channel.new
15
+ @response_channel = Async::Channel.new
12
16
  reset!
13
17
  end
14
18
 
15
- def url
16
- @endpoint.url
17
- end
18
-
19
19
  def connected?
20
20
  @connected
21
21
  end
22
22
 
23
- def connect # rubocop:disable Metrics/MethodLength
23
+ def connect # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
24
24
  raise AlreadyConnectedError if connected?
25
25
 
26
- @connection = Async::WebSocket::Client.connect(@endpoint)
26
+ @connection = Async::WebSocket::Client.connect(Async::HTTP::Endpoint.parse(@url), **@client_options)
27
27
 
28
28
  @response_task = @parent.async do
29
29
  loop do
30
30
  data = @connection.read
31
- @response_queue << data
31
+ @response_channel << data
32
32
  end
33
33
  rescue Async::Stop
34
- @response_queue << nil
34
+ @response_channel.close
35
+ rescue StandardError => e
36
+ @response_channel.exception(e)
35
37
  end
36
38
 
37
39
  @request_task = @parent.async do
38
- @request_queue.each do |message|
40
+ @request_channel.each do |message|
39
41
  @connection.write(message)
40
42
  @connection.flush
41
43
  end
44
+ rescue StandardError => e
45
+ @response_channel.exception(e)
42
46
  end
43
47
 
44
48
  @connected = true
45
49
 
46
- @response_queue
50
+ @response_channel
47
51
  end
48
52
 
49
53
  def write(message)
50
54
  raise NotConnectedError unless connected?
51
55
 
52
- @request_queue << message
56
+ @request_channel << message
53
57
  end
54
58
 
55
- def close
56
- raise NotConnectedError unless connected?
59
+ def close # rubocop:disable Metrics/MethodLength
60
+ return unless connected?
57
61
 
58
- @request_queue << nil
62
+ @request_channel.close
59
63
  @request_task.wait
60
64
 
61
65
  @response_task.stop
62
66
  @response_task.wait
63
67
 
64
- @connection.close
68
+ begin
69
+ @connection.close
70
+ rescue Errno::EPIPE
71
+ nil
72
+ end
65
73
 
66
74
  reset!
67
75
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grumlin
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grumlin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gleb Sinyavskiy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-29 00:00:00.000000000 Z
11
+ date: 2021-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-pool
@@ -62,6 +62,7 @@ files:
62
62
  - gremlin_server/Dockerfile
63
63
  - gremlin_server/tinkergraph-empty.properties
64
64
  - grumlin.gemspec
65
+ - lib/async/channel.rb
65
66
  - lib/grumlin.rb
66
67
  - lib/grumlin/anonymous_step.rb
67
68
  - lib/grumlin/client.rb