grumlin 0.6.2 → 0.7.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: 6633f34f12f74200b56d3447e7b1cd5c2c4185cd4013a27157d809b2f97f01b2
4
- data.tar.gz: a5b0c1b0f4a8ef813a48144b79b17a71c6385802ff77b46ef0fa703643790ac1
3
+ metadata.gz: 4260297c3d75ec7bc1f987d8c7bad4efd75c81627814fdf984a0aaac0045332b
4
+ data.tar.gz: 1cb521906a08cf5390f058a28f380492a2999e55ef023c473fc6bae64533b53b
5
5
  SHA512:
6
- metadata.gz: 6824448759e26af95f8753a1461fac6b0b66677e1cab723b8e79c76333c4e791b5364da81f87844b624796238a3e02b13065a7afb64d7a5ef7ca83a6e9c3e8be
7
- data.tar.gz: d7ce1a01b2b50f90a317d270ad005561220fcb0f69da5036b97d20c88b2458e5dd7ea285c384cef5d5e9521ff077f2d43650985c1e928942e463cfc29bbdd247
6
+ metadata.gz: 992d2192402f3fe282203a3fd0eff1e6c886470899cb37f00ca2abf1392e4d946c7391be0eb7d6560c9941f7a17a3552682a8a47f56273f4299c06551c1e9ffc
7
+ data.tar.gz: d8695e2485915cb7b4d37a755e42d20b6d7f575d906ae5a0aca5f508c01a104c08f7bb51a2285c9dac48a0091771882afeb405fd26e7802c4d48513274e2895f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- grumlin (0.6.2)
4
+ grumlin (0.7.0)
5
5
  async-pool (~> 0.3)
6
6
  async-websocket (~> 0.19)
7
7
 
data/lib/async/channel.rb CHANGED
@@ -32,7 +32,7 @@ module Async
32
32
  end
33
33
 
34
34
  def close
35
- raise(ChannelClosedError, "Cannot close a closed channel") if closed?
35
+ return if closed?
36
36
 
37
37
  @queue << [:close]
38
38
  @closed = true
@@ -13,6 +13,7 @@ module Grumlin
13
13
  def initialize(url, client_factory:, concurrency: 1, parent: Async::Task.current)
14
14
  super(concurrency)
15
15
  @client = client_factory.call(url, parent).tap(&:connect)
16
+ @parent = parent
16
17
  end
17
18
 
18
19
  def closed?
@@ -25,6 +26,8 @@ module Grumlin
25
26
 
26
27
  def write(*args)
27
28
  @client.write(*args)
29
+ ensure
30
+ @count += 1
28
31
  end
29
32
 
30
33
  def viable?
@@ -36,33 +39,54 @@ module Grumlin
36
39
  end
37
40
  end
38
41
 
42
+ include Console
43
+
44
+ # Client is not reusable. Once closed should be recreated.
39
45
  def initialize(url, parent: Async::Task.current, **client_options)
40
46
  @url = url
41
47
  @client_options = client_options
42
48
  @parent = parent
43
- reset!
49
+ @request_dispatcher = nil
50
+ @transport = nil
44
51
  end
45
52
 
46
53
  def connect
54
+ raise "ClientClosed" if @closed
55
+
47
56
  @transport = build_transport
48
57
  response_channel = @transport.connect
49
58
  @request_dispatcher = RequestDispatcher.new
50
- @parent.async do
59
+ @response_task = @parent.async do
51
60
  response_channel.each do |response|
52
61
  @request_dispatcher.add_response(response)
53
62
  end
54
- rescue StandardError
55
- close
63
+ rescue Async::Stop, Async::TimeoutError, StandardError
64
+ close(check_requests: false)
56
65
  end
66
+ logger.debug(self, "Connected")
57
67
  end
58
68
 
59
- def close
69
+ # Before calling close the user must ensure that:
70
+ # 1) There are no ongoing requests
71
+ # 2) There will be no new writes after
72
+ def close(check_requests: true) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
73
+ return if @closed
74
+
75
+ @closed = true
76
+
60
77
  @transport&.close
61
- if @request_dispatcher&.requests&.any?
62
- raise ResourceLeakError, "Request list is not empty: #{@request_dispatcher.requests}"
63
- end
78
+ @transport&.wait
64
79
 
65
- reset!
80
+ @response_task&.stop
81
+ @response_task&.wait
82
+
83
+ return if @request_dispatcher&.requests&.empty?
84
+
85
+ @request_dispatcher.clear unless check_requests
86
+
87
+ raise ResourceLeakError, "Request list is not empty: #{@request_dispatcher.requests}" if check_requests
88
+ ensure
89
+ logger.debug(self, "Closed")
66
90
  end
67
91
 
68
92
  def connected?
@@ -80,9 +104,9 @@ module Grumlin
80
104
 
81
105
  begin
82
106
  channel.dequeue.flat_map { |item| Typing.cast(item) }
83
- rescue Async::Stop
84
- retry if @request_dispatcher.ongoing_request?(request_id)
85
- raise Grumlin::UnknownRequestStoppedError, "#{request_id} is not in the ongoing requests list"
107
+ rescue Async::Stop, Async::TimeoutError
108
+ close(check_requests: false)
109
+ raise
86
110
  end
87
111
  end
88
112
 
@@ -106,11 +130,6 @@ module Grumlin
106
130
  }
107
131
  end
108
132
 
109
- def reset!
110
- @request_dispatcher = nil
111
- @transport = nil
112
- end
113
-
114
133
  def build_transport
115
134
  Transport.new(@url, parent: @parent, **@client_options)
116
135
  end
@@ -22,6 +22,8 @@ module Grumlin
22
22
  498 => ClientSideError
23
23
  }.freeze
24
24
 
25
+ include Console
26
+
25
27
  def initialize
26
28
  @requests = {}
27
29
  end
@@ -69,6 +71,10 @@ module Grumlin
69
71
  @requests.key?(request_id)
70
72
  end
71
73
 
74
+ def clear
75
+ @requests.clear
76
+ end
77
+
72
78
  private
73
79
 
74
80
  def check_errors!(status)
@@ -5,47 +5,33 @@ module Grumlin
5
5
  # A transport based on https://github.com/socketry/async
6
6
  # and https://github.com/socketry/async-websocket
7
7
 
8
+ include Console
9
+
8
10
  attr_reader :url
9
11
 
12
+ # Transport is not reusable. Once closed should be recreated.
10
13
  def initialize(url, parent: Async::Task.current, **client_options)
11
14
  @url = url
12
15
  @parent = parent
13
16
  @client_options = client_options
14
17
  @request_channel = Async::Channel.new
15
18
  @response_channel = Async::Channel.new
16
- reset!
17
19
  end
18
20
 
19
21
  def connected?
20
- @connected
22
+ !@connection.nil?
21
23
  end
22
24
 
23
- def connect # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
25
+ def connect
26
+ raise "ClientClosed" if @closed
24
27
  raise AlreadyConnectedError if connected?
25
28
 
26
29
  @connection = Async::WebSocket::Client.connect(Async::HTTP::Endpoint.parse(@url), **@client_options)
30
+ logger.debug(self) { "Connected to #{@url}." }
27
31
 
28
- @response_task = @parent.async do
29
- loop do
30
- data = @connection.read
31
- @response_channel << data
32
- end
33
- rescue Async::Stop
34
- @response_channel.close
35
- rescue StandardError => e
36
- @response_channel.exception(e)
37
- end
32
+ @response_task = @parent.async { run_response_task }
38
33
 
39
- @request_task = @parent.async do
40
- @request_channel.each do |message|
41
- @connection.write(message)
42
- @connection.flush
43
- end
44
- rescue StandardError => e
45
- @response_channel.exception(e)
46
- end
47
-
48
- @connected = true
34
+ @request_task = @parent.async { run_request_task }
49
35
 
50
36
  @response_channel
51
37
  end
@@ -57,30 +43,59 @@ module Grumlin
57
43
  end
58
44
 
59
45
  def close
60
- return unless connected?
46
+ return if @closed
61
47
 
62
- @request_channel.close
63
- @request_task.wait
48
+ @closed = true
64
49
 
65
- @response_task.stop
66
- @response_task.wait
50
+ @request_channel.close
51
+ @response_channel.close
67
52
 
68
53
  begin
69
54
  @connection.close
70
- rescue Errno::EPIPE
55
+ rescue StandardError
71
56
  nil
72
57
  end
58
+ @connection = nil
59
+
60
+ @request_task&.stop(true)
61
+ @response_task&.stop(true)
62
+ end
73
63
 
74
- reset!
64
+ def wait
65
+ @request_task.wait
66
+ @response_task.wait
75
67
  end
76
68
 
77
69
  private
78
70
 
79
- def reset!
80
- @connected = false
81
- @connection = nil
82
- @response_task = nil
83
- @request_task = nil
71
+ def run_response_task
72
+ with_guard do
73
+ loop do
74
+ data = @connection.read
75
+ @response_channel << data
76
+ end
77
+ end
78
+ end
79
+
80
+ def run_request_task
81
+ with_guard do
82
+ @request_channel.each do |message|
83
+ @connection.write(message)
84
+ @connection.flush
85
+ end
86
+ end
87
+ end
88
+
89
+ def with_guard
90
+ yield
91
+ rescue Async::Stop, Async::TimeoutError, StandardError => e
92
+ logger.debug(self) { "Guard error, closing." }
93
+ begin
94
+ @response_channel.exception(e)
95
+ rescue Async::Channel::ChannelClosedError
96
+ nil
97
+ end
98
+ close
84
99
  end
85
100
  end
86
101
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grumlin
4
- VERSION = "0.6.2"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/grumlin.rb CHANGED
@@ -45,7 +45,7 @@ module Grumlin
45
45
 
46
46
  def initialize
47
47
  @pool_size = 10
48
- @client_concurrency = 2
48
+ @client_concurrency = 5
49
49
  @client_factory = ->(url, parent) { Grumlin::Client.new(url, parent: parent) }
50
50
  end
51
51
 
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.6.2
4
+ version: 0.7.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-09-03 00:00:00.000000000 Z
11
+ date: 2021-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-pool