async-io 1.12.3 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
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"