async-io 1.12.3 → 1.13.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: c10de5ece88f5f8366c2560647ab2cc061c12d49790cf1f3aafb6516d0fd7ad4
4
- data.tar.gz: c970a916f8e637098bf85a842d27709cb34dae68ded5386451be3f433673d12e
3
+ metadata.gz: 71c04e110e0ca7bf28d0066c8ea5d9efc5286a61533a8d713a0b27ce2721af57
4
+ data.tar.gz: 675aa3d7916f0da52fffb46d2d36879501b060bb4e362a3d4bdbd12a9b196044
5
5
  SHA512:
6
- metadata.gz: ab731c4a8ec3e2f9a3b459e62391a5294b2234ca0a62cf066f435ba33324f174f60676660924cba2ef25c8fae00fb85bb2b4556c4c96cecdbdd1bd7e9c798f35
7
- data.tar.gz: 12c30f60cc9e1859664a79d9d6378c690d0756234410699ae79d5fbd837f014ccc10811ff4feb7ec38cda025fe300cac95051aaff5015808ec595a7107c2d426
6
+ metadata.gz: ca06ea7ac6ccc5d9f957789dee7f6d5ba4d87a727123ae9b57d676ab1d921658e1657cb10362adcba2fd749249d8be419369b49213e17af329acc6aaecbd2d3a
7
+ data.tar.gz: 59bc63d72e63db1f555d5753f3f3d7c497749a619b0acc1e42c32e926ffc4125409571d9f3cad249f2f69a34cdaf3bdf8366efc94b2f0084e479a8fd5b2797a6
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.has_rdoc = "yard"
18
18
 
19
19
  spec.add_dependency "async", "~> 1.3"
20
- spec.add_development_dependency "async-rspec", "~> 1.7"
20
+ spec.add_development_dependency "async-rspec", "~> 1.10"
21
21
 
22
22
  spec.required_ruby_version = '~> 2.3'
23
23
 
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative 'memory'
4
+
5
+ string = nil
6
+
7
+ measure_memory("Initial allocation") do
8
+ string = "a" * 5*1024*1024
9
+ string.freeze
10
+ end # => 5.0 MB
11
+
12
+ measure_memory("Byteslice from start to middle") do
13
+ # Why does this need to allocate memory? Surely it can share the original allocation?
14
+ x = string.byteslice(0, string.bytesize / 2)
15
+ end # => 2.5 MB
16
+
17
+ measure_memory("Byteslice from middle to end") do
18
+ string.byteslice(string.bytesize / 2, string.bytesize)
19
+ end # => 0.0 MB
20
+
21
+ measure_memory("Slice! from start to middle") do
22
+ string.dup.slice!(0, string.bytesize / 2)
23
+ end # => 7.5 MB
24
+
25
+ measure_memory("Byte slice into two halves") do
26
+ head = string.byteslice(0, string.bytesize / 2) # 2.5 MB
27
+ remainder = string.byteslice(string.bytesize / 2, string.bytesize) # Shared
28
+ end # 2.5 MB
@@ -0,0 +1,15 @@
1
+
2
+ def measure_memory(annotation = "Memory allocated")
3
+ GC.disable
4
+
5
+ start_memory = `ps -p #{Process::pid} -o rss`.split("\n")[1].chomp.to_i
6
+
7
+ yield
8
+
9
+ ensure
10
+ end_memory = `ps -p #{Process::pid} -o rss`.split("\n")[1].chomp.to_i
11
+ memory_usage = (end_memory - start_memory).to_f / 1024
12
+
13
+ puts "#{memory_usage.round(1)} MB: #{annotation}"
14
+ GC.enable
15
+ end
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative 'memory'
4
+
5
+ require_relative "../../lib/async/io/stream"
6
+ require "stringio"
7
+
8
+ measure_memory("Stream setup") do
9
+ @io = StringIO.new("a" * (50*1024*1024))
10
+ @stream = Async::IO::Stream.new(@io)
11
+ end # 50.0 MB
12
+
13
+ measure_memory("Read all chunks") do
14
+ while chunk = @stream.read_partial
15
+ chunk.clear
16
+ end
17
+ end # 0.5 MB
@@ -18,22 +18,11 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require_relative 'buffer'
22
+
21
23
  module Async
22
24
  module IO
23
- class BinaryString < String
24
- def initialize(*args)
25
- super
26
-
27
- force_encoding(Encoding::BINARY)
28
- end
29
-
30
- def << string
31
- super
32
-
33
- force_encoding(Encoding::BINARY)
34
- end
35
-
36
- alias concat <<
37
- end
25
+ # This is deprecated.
26
+ BinaryString = Buffer
38
27
  end
39
28
  end
@@ -0,0 +1,39 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Async
22
+ module IO
23
+ class Buffer < String
24
+ def initialize
25
+ super
26
+
27
+ force_encoding(Encoding::BINARY)
28
+ end
29
+
30
+ def << string
31
+ super
32
+
33
+ force_encoding(Encoding::BINARY)
34
+ end
35
+
36
+ alias concat <<
37
+ end
38
+ end
39
+ end
@@ -18,7 +18,7 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require_relative 'binary_string'
21
+ require_relative 'buffer'
22
22
  require_relative 'generic'
23
23
 
24
24
  module Async
@@ -37,11 +37,11 @@ module Async
37
37
 
38
38
  @block_size = block_size
39
39
 
40
- @read_buffer = BinaryString.new
41
- @write_buffer = BinaryString.new
40
+ @read_buffer = Buffer.new
41
+ @write_buffer = Buffer.new
42
42
 
43
43
  # Used as destination buffer for underlying reads.
44
- @input_buffer = BinaryString.new
44
+ @input_buffer = Buffer.new
45
45
  end
46
46
 
47
47
  attr :io
@@ -52,9 +52,17 @@ module Async
52
52
  return '' if size == 0
53
53
 
54
54
  if size
55
- fill_read_buffer until @eof or @read_buffer.size >= size
55
+ until @eof or @read_buffer.size >= size
56
+ # Compute the amount of data we need to read from the underlying stream:
57
+ read_size = size - @read_buffer.bytesize
58
+
59
+ # Don't read less than @block_size to avoid lots of small reads:
60
+ fill_read_buffer(read_size > @block_size ? read_size : @block_size)
61
+ end
56
62
  else
57
- fill_read_buffer until @eof
63
+ until @eof
64
+ fill_read_buffer
65
+ end
58
66
  end
59
67
 
60
68
  return consume_read_buffer(size)
@@ -81,14 +89,15 @@ module Async
81
89
  return unless fill_read_buffer
82
90
  end
83
91
 
84
- matched = @read_buffer.slice!(0, index)
85
- @read_buffer.slice!(0, pattern.bytesize)
92
+ @read_buffer.freeze
93
+ matched = @read_buffer.byteslice(0, index)
94
+ @read_buffer = @read_buffer.byteslice(index+pattern.bytesize, @read_buffer.bytesize)
86
95
 
87
96
  return matched
88
97
  end
89
98
 
90
99
  def peek
91
- until yield(@read_buffer) || @eof
100
+ until yield(@read_buffer) or @eof
92
101
  fill_read_buffer
93
102
  end
94
103
  end
@@ -182,10 +191,10 @@ module Async
182
191
  private
183
192
 
184
193
  # Fills the buffer from the underlying stream.
185
- def fill_read_buffer
186
- if @read_buffer.empty? and @io.read(@block_size, @read_buffer)
194
+ def fill_read_buffer(size = @block_size)
195
+ if @read_buffer.empty? and @io.read(size, @read_buffer)
187
196
  return true
188
- elsif chunk = @io.read(@block_size, @input_buffer)
197
+ elsif chunk = @io.read(size, @input_buffer)
189
198
  @read_buffer << chunk
190
199
  return true
191
200
  else
@@ -203,13 +212,20 @@ module Async
203
212
 
204
213
  result = nil
205
214
 
206
- if size == nil || size >= @read_buffer.size
215
+ if size.nil? or size >= @read_buffer.size
207
216
  # Consume the entire read buffer:
208
217
  result = @read_buffer
209
- @read_buffer = BinaryString.new
218
+ @read_buffer = Buffer.new
210
219
  else
211
- # Consume only part of the read buffer:
212
- result = @read_buffer.slice!(0, size)
220
+ # This approach uses more memory.
221
+ # result = @read_buffer.slice!(0, size)
222
+
223
+ # We know that we are not going to reuse the original buffer.
224
+ # But byteslice will generate a hidden copy. So let's freeze it first:
225
+ @read_buffer.freeze
226
+
227
+ result = @read_buffer.byteslice(0, size)
228
+ @read_buffer = @read_buffer.byteslice(size, @read_buffer.bytesize)
213
229
  end
214
230
 
215
231
  return result
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module IO
23
- VERSION = "1.12.3"
23
+ VERSION = "1.13.0"
24
24
  end
25
25
  end
@@ -0,0 +1,15 @@
1
+
2
+ # This is useful for specs, but I hesitate to monkey patch a core class in the library itself.
3
+ class Addrinfo
4
+ def == other
5
+ self.to_s == other.to_s
6
+ end
7
+
8
+ def != other
9
+ self.to_s != other.to_s
10
+ end
11
+
12
+ def <=> other
13
+ self.to_s <=> other.to_s
14
+ end
15
+ end
@@ -19,12 +19,15 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require 'async/io/stream'
22
+ require 'async/rspec/buffer'
22
23
 
23
24
  RSpec.describe Async::IO::Stream do
25
+ include_context Async::RSpec::Buffer
24
26
  include_context Async::RSpec::Memory
27
+ include_context Async::RSpec::Reactor
25
28
 
26
- let(:io) {StringIO.new}
27
- let(:stream) {Async::IO::Stream.new(io)}
29
+ let!(:stream) {Async::IO::Stream.new(buffer)}
30
+ let(:io) {stream.io}
28
31
 
29
32
  describe '#read' do
30
33
  it "should read everything" do
@@ -51,12 +54,20 @@ RSpec.describe Async::IO::Stream do
51
54
  end
52
55
 
53
56
  context "with large content" do
54
- let!(:io) { StringIO.new("a" * 5*1024*1024) }
55
-
56
57
  it "allocates expected amount of bytes" do
58
+ io.write("." * 16*1024)
59
+ io.seek(0)
60
+
61
+ buffer = nil
62
+
57
63
  expect do
58
- stream.read(16*1024).clear until stream.eof?
59
- end.to limit_allocations(size: 100*1024)
64
+ # The read buffer is already allocated, and it will be resized to fit the incoming data. It will be swapped with an empty buffer.
65
+ buffer = stream.read(16*1024)
66
+ end.to limit_allocations.of(String, count: 1, size: 0)
67
+
68
+ expect(buffer.size).to be == 16*1024
69
+
70
+ io.close
60
71
  end
61
72
  end
62
73
  end
@@ -72,12 +83,10 @@ RSpec.describe Async::IO::Stream do
72
83
  end
73
84
 
74
85
  context "with large content" do
75
- let!(:io) { StringIO.new("a" * 5*1024*1024 + "b") }
76
-
77
86
  it "allocates expected amount of bytes" do
78
87
  expect do
79
- stream.read_until("b").clear
80
- end.to limit_allocations(size: 100*1024)
88
+ stream.read_until("b")
89
+ end.to limit_allocations.of(String, size: 0, count: 1)
81
90
  end
82
91
  end
83
92
  end
@@ -99,22 +108,38 @@ RSpec.describe Async::IO::Stream do
99
108
  end
100
109
 
101
110
  describe '#read_partial' do
102
- it "should avoid calling read" do
103
- io.write "Hello World" * 1024
111
+ before(:each) do
112
+ io.write "Hello World!" * 1024
104
113
  io.seek(0)
105
-
114
+ end
115
+
116
+ it "should avoid calling read" do
106
117
  expect(io).to receive(:read).and_call_original.once
107
118
 
108
- expect(stream.read_partial(11)).to be == "Hello World"
119
+ expect(stream.read_partial(12)).to be == "Hello World!"
109
120
  end
110
121
 
111
122
  context "with large content" do
112
- let!(:io) { StringIO.new("a" * 5*1024*1024) }
123
+ it "allocates only the amount required" do
124
+ expect do
125
+ stream.read(4*1024)
126
+ end.to limit_allocations.of(String, count: 2, size: 4*1024+1)
127
+ end
128
+
129
+ it "allocates exact number of bytes being read" do
130
+ expect do
131
+ stream.read(16*1024)
132
+ end.to limit_allocations.of(String, count: 1, size: 0)
133
+ end
113
134
 
114
135
  it "allocates expected amount of bytes" do
136
+ buffer = nil
137
+
115
138
  expect do
116
- stream.read_partial(16*1024).clear until stream.eof?
117
- end.to limit_allocations(size: 100*1024)
139
+ buffer = stream.read_partial
140
+ end.to limit_allocations.of(String, count: 1)
141
+
142
+ expect(buffer.size).to be == stream.block_size
118
143
  end
119
144
  end
120
145
  end
@@ -126,7 +151,9 @@ RSpec.describe Async::IO::Stream do
126
151
  stream.write "Hello World\n"
127
152
  stream.flush
128
153
 
129
- expect(io.string).to be == "Hello World\n"
154
+ io.seek(0)
155
+
156
+ expect(stream.read).to be == "Hello World\n"
130
157
  end
131
158
  end
132
159
 
@@ -19,24 +19,11 @@ end
19
19
  require "bundler/setup"
20
20
  require "async/io"
21
21
 
22
- # This is useful for specs, but I hesitate to monkey patch a core class in the library itself.
23
- class Addrinfo
24
- def == other
25
- self.to_s == other.to_s
26
- end
27
-
28
- def != other
29
- self.to_s != other.to_s
30
- end
31
-
32
- def <=> other
33
- self.to_s <=> other.to_s
34
- end
35
- end
36
-
37
22
  # Shared rspec helpers:
38
23
  require "async/rspec"
39
24
 
25
+ require_relative 'addrinfo'
26
+
40
27
  RSpec.configure do |config|
41
28
  # Enable flags like --only-failures and --next-failure
42
29
  config.example_status_persistence_file_path = ".rspec_status"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-io
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.3
4
+ version: 1.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-07 00:00:00.000000000 Z
11
+ date: 2018-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.7'
33
+ version: '1.10'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.7'
40
+ version: '1.10'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -95,7 +95,9 @@ files:
95
95
  - README.md
96
96
  - Rakefile
97
97
  - async-io.gemspec
98
- - examples/allocations.rb
98
+ - examples/allocations/byteslice.rb
99
+ - examples/allocations/memory.rb
100
+ - examples/allocations/read_chunks.rb
99
101
  - examples/chat/client.rb
100
102
  - examples/chat/server.rb
101
103
  - examples/issues/broken_ssl.rb
@@ -103,6 +105,7 @@ files:
103
105
  - lib/async/io/address.rb
104
106
  - lib/async/io/address_endpoint.rb
105
107
  - lib/async/io/binary_string.rb
108
+ - lib/async/io/buffer.rb
106
109
  - lib/async/io/endpoint.rb
107
110
  - lib/async/io/endpoint/each.rb
108
111
  - lib/async/io/generic.rb
@@ -121,6 +124,7 @@ files:
121
124
  - lib/async/io/udp_socket.rb
122
125
  - lib/async/io/unix_socket.rb
123
126
  - lib/async/io/version.rb
127
+ - spec/addrinfo.rb
124
128
  - spec/async/io/c10k_spec.rb
125
129
  - spec/async/io/echo_spec.rb
126
130
  - spec/async/io/endpoint_spec.rb
@@ -165,6 +169,7 @@ signing_key:
165
169
  specification_version: 4
166
170
  summary: Provides support for asynchonous TCP, UDP, UNIX and SSL sockets.
167
171
  test_files:
172
+ - spec/addrinfo.rb
168
173
  - spec/async/io/c10k_spec.rb
169
174
  - spec/async/io/echo_spec.rb
170
175
  - spec/async/io/endpoint_spec.rb
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require_relative "../lib/async/io/stream"
4
- # require "async/io/stream"
5
- require "stringio"
6
-
7
- io = StringIO.new("a" * (50*1024*1024))
8
- stream = Async::IO::Stream.new(io)
9
-
10
- GC.disable
11
-
12
- start_memory = `ps -p #{Process::pid} -o rss`.split("\n")[1].chomp.to_i
13
-
14
- while (chunk = stream.read_partial)
15
- chunk.clear
16
- end
17
-
18
- end_memory = `ps -p #{Process::pid} -o rss`.split("\n")[1].chomp.to_i
19
- puts "#{(end_memory - start_memory).to_f / 1024} MB"