object-stream 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +22 -0
- data/README.md +7 -0
- data/Rakefile +9 -0
- data/bench/pipe-socket.rb +52 -0
- data/examples/basic-usage.rb +43 -0
- data/examples/custom-class.rb +73 -0
- data/examples/maxbuf.rb +17 -0
- data/examples/outbox.rb +19 -0
- data/examples/pipe.rb +42 -0
- data/examples/read-without-block.rb +25 -0
- data/examples/slow-sender.rb +76 -0
- data/examples/socket.rb +43 -0
- data/examples/udp.rb +35 -0
- data/lib/object-stream-wrapper.rb +114 -0
- data/lib/object-stream.rb +324 -0
- data/test/test-basic.rb +176 -0
- data/test/test-consume.rb +51 -0
- data/test/test-expect.rb +80 -0
- data/test/test-inbox.rb +65 -0
- data/test/test-maxbuf.rb +22 -0
- data/test/test-outbox.rb +35 -0
- data/test/test-slow-sender.rb +79 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 40d4571e5abdd3d97b76a1b8694b7cdeabab60b9
|
4
|
+
data.tar.gz: 33e6108c55492c43b65c3ce6d9eecad2d9686cd7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1db1cb56ff1db7d59d90ebf8d619674c72d8dd1945e2134e62a86b7ff262d7ab3765e747d80ddfd12a4fd2a44c7cf781f1cd48d7948b255730ae1debf92248fe
|
7
|
+
data.tar.gz: 9ce4e08d1c58c07d64172cc9cee30e7fcb43f77492184070872269728f59f8776d26339a118fd9809b36352f9f46661af4f5814b3a9cedcb31968b8fdaa65b1f
|
data/COPYING
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013, Joel VanderWerf, vjoel@users.sourceforge.net
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
14
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
15
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
17
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
18
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
19
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
20
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
22
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
object-stream
|
2
|
+
=============
|
3
|
+
|
4
|
+
Stream objects over IO using Marshal, JSON, YAML, or Msgpack.
|
5
|
+
|
6
|
+
Note that JSON (using Yajl) and Msgpack both permit suspending partial reads when other input is available, while Marshal and YAML need to read complete objects. So, using Marshal or YAML in a single thread (such as a select loop) may lead to blocking if one stream has not produced a complete object. See examples/slow-sender.rb.
|
7
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'object-stream'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
def run(
|
6
|
+
rd: nil,
|
7
|
+
wr: nil,
|
8
|
+
objects: 1..10,
|
9
|
+
type: ObjectStream::MSGPACK_TYPE)
|
10
|
+
|
11
|
+
pid = fork do
|
12
|
+
stream = ObjectStream.new(wr, type: type)
|
13
|
+
objects.each do |object|
|
14
|
+
stream << object
|
15
|
+
end
|
16
|
+
end
|
17
|
+
wr.close
|
18
|
+
|
19
|
+
stream = ObjectStream.new(rd, type: type)
|
20
|
+
count = stream.inject(0) do |n, object|
|
21
|
+
n + 1
|
22
|
+
end
|
23
|
+
raise unless count == objects.size
|
24
|
+
stream.close
|
25
|
+
|
26
|
+
ensure
|
27
|
+
Process.wait pid if pid
|
28
|
+
end
|
29
|
+
|
30
|
+
begin # warmup
|
31
|
+
rd, wr = IO.pipe
|
32
|
+
run rd: rd, wr: wr
|
33
|
+
|
34
|
+
rd, wr = UNIXSocket.pair
|
35
|
+
run rd: rd, wr: wr
|
36
|
+
end
|
37
|
+
|
38
|
+
tuples = (0...10_000).map {|i|
|
39
|
+
[i, 42.42, true, "foo", {"bar" => "baz"}]
|
40
|
+
}
|
41
|
+
|
42
|
+
Benchmark.bm(10) do |bench|
|
43
|
+
bench.report "pipe" do
|
44
|
+
rd, wr = IO.pipe
|
45
|
+
run rd: rd, wr: wr, objects: tuples
|
46
|
+
end
|
47
|
+
|
48
|
+
bench.report "socket" do
|
49
|
+
rd, wr = UNIXSocket.pair
|
50
|
+
run rd: rd, wr: wr, objects: tuples
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
case ARGV[0]
|
2
|
+
when "marshal", "yaml", "json", "msgpack"
|
3
|
+
else
|
4
|
+
abort "Usage: #$0 marshal|yaml|json|msgpack"
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'object-stream'
|
8
|
+
require 'tmpdir'
|
9
|
+
|
10
|
+
begin
|
11
|
+
dir = Dir.mktmpdir "stream-"
|
12
|
+
dumpfile = File.join(dir, "dump")
|
13
|
+
|
14
|
+
type = ARGV.shift
|
15
|
+
|
16
|
+
File.open(dumpfile, "w") do |f|
|
17
|
+
stream = ObjectStream.new(f, type: type)
|
18
|
+
p stream
|
19
|
+
stream << "foo" << [:bar, 42] << {"String" => "string"}
|
20
|
+
end
|
21
|
+
|
22
|
+
data = File.read(dumpfile)
|
23
|
+
puts "===== #{data.size} bytes:"
|
24
|
+
case type
|
25
|
+
when ObjectStream::MARSHAL_TYPE, ObjectStream::MSGPACK_TYPE
|
26
|
+
puts data.inspect
|
27
|
+
when ObjectStream::YAML_TYPE, ObjectStream::JSON_TYPE
|
28
|
+
puts data
|
29
|
+
end
|
30
|
+
puts "====="
|
31
|
+
|
32
|
+
a = File.open(dumpfile, "r") do |f|
|
33
|
+
stream = ObjectStream.new(f, type: type)
|
34
|
+
stream.map do |obj|
|
35
|
+
#p obj
|
36
|
+
obj
|
37
|
+
end
|
38
|
+
end
|
39
|
+
p a
|
40
|
+
|
41
|
+
ensure
|
42
|
+
FileUtils.remove_entry dir
|
43
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
case ARGV[0]
|
2
|
+
when "marshal", "yaml", "json", "msgpack"
|
3
|
+
else
|
4
|
+
abort "Usage: #$0 marshal|yaml|json|msgpack"
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'object-stream'
|
8
|
+
require 'tmpdir'
|
9
|
+
|
10
|
+
begin
|
11
|
+
dir = Dir.mktmpdir "stream-"
|
12
|
+
dumpfile = File.join(dir, "dump")
|
13
|
+
|
14
|
+
type = ARGV.shift
|
15
|
+
|
16
|
+
class A
|
17
|
+
def initialize x, y
|
18
|
+
@x, @y = x, y
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_msgpack pk = nil
|
22
|
+
case pk
|
23
|
+
when MessagePack::Packer
|
24
|
+
pk.write_array_header(2)
|
25
|
+
pk.write @x
|
26
|
+
pk.write @y
|
27
|
+
return pk
|
28
|
+
|
29
|
+
else # nil or IO
|
30
|
+
MessagePack.pack(self, pk)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_json
|
35
|
+
[@x, @y].to_json
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.from_serialized ary
|
39
|
+
new *ary
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
File.open(dumpfile, "w") do |f|
|
44
|
+
stream = ObjectStream.new(f, type: type)
|
45
|
+
p stream
|
46
|
+
stream << "foo" << [:bar, 42] << {"String" => "string"} << "A" << A.new(3,6)
|
47
|
+
end
|
48
|
+
|
49
|
+
data = File.read(dumpfile)
|
50
|
+
puts "===== #{data.size} bytes:"
|
51
|
+
case type
|
52
|
+
when ObjectStream::MARSHAL_TYPE, ObjectStream::MSGPACK_TYPE
|
53
|
+
puts data.inspect
|
54
|
+
when ObjectStream::YAML_TYPE, ObjectStream::JSON_TYPE
|
55
|
+
puts data
|
56
|
+
end
|
57
|
+
puts "====="
|
58
|
+
|
59
|
+
a = File.open(dumpfile, "r") do |f|
|
60
|
+
stream = ObjectStream.new(f, type: type)
|
61
|
+
stream.map do |obj|
|
62
|
+
#p obj
|
63
|
+
stream.expect {obj == "A" and A}
|
64
|
+
# code inside block won't be executed in cases where object class
|
65
|
+
# is encoded in the data itself (marshal, yaml)
|
66
|
+
obj
|
67
|
+
end
|
68
|
+
end
|
69
|
+
p a
|
70
|
+
|
71
|
+
ensure
|
72
|
+
FileUtils.remove_entry dir
|
73
|
+
end
|
data/examples/maxbuf.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# The MsgpackStream can detect when too many bytes have been read without
|
2
|
+
# complete parsing of an object.
|
3
|
+
|
4
|
+
require 'object-stream'
|
5
|
+
require 'stringio'
|
6
|
+
|
7
|
+
sio = StringIO.new
|
8
|
+
stream = ObjectStream.new(sio, type: ObjectStream::MSGPACK_TYPE)
|
9
|
+
stream << "This is a very long sentence, and possibly a denial-of-service attack!"
|
10
|
+
|
11
|
+
sio.rewind
|
12
|
+
stream = ObjectStream.new(sio, type: ObjectStream::MSGPACK_TYPE, maxbuf: 20)
|
13
|
+
begin
|
14
|
+
stream.to_a
|
15
|
+
rescue ObjectStream::OverflowError => ex
|
16
|
+
puts ex # => Exceeded buffer limit by 53 bytes.
|
17
|
+
end
|
data/examples/outbox.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'object-stream'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
sio = StringIO.new
|
5
|
+
stream = ObjectStream.new(sio, type: ObjectStream::MSGPACK_TYPE)
|
6
|
+
stream.write_to_outbox "and now for something"
|
7
|
+
stream << "completely different"
|
8
|
+
|
9
|
+
sio.rewind
|
10
|
+
stream = ObjectStream.new(sio, type: ObjectStream::MSGPACK_TYPE)
|
11
|
+
puts stream.to_a
|
12
|
+
|
13
|
+
__END__
|
14
|
+
|
15
|
+
Output:
|
16
|
+
|
17
|
+
and now for something
|
18
|
+
completely different
|
19
|
+
|
data/examples/pipe.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
case ARGV[0]
|
2
|
+
when "marshal", "yaml", "json", "msgpack"
|
3
|
+
else
|
4
|
+
abort "Usage: #$0 marshal|yaml|json|msgpack"
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'object-stream'
|
8
|
+
|
9
|
+
begin
|
10
|
+
type = ARGV.shift
|
11
|
+
|
12
|
+
rd, wr = IO.pipe
|
13
|
+
|
14
|
+
pid = fork do
|
15
|
+
stream = ObjectStream.new(wr, type: type)
|
16
|
+
10.times do |i|
|
17
|
+
stream << [i] # box the int because json needs it
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
wr.close
|
22
|
+
stream = ObjectStream.new(rd, type: type)
|
23
|
+
|
24
|
+
puts "try to select"
|
25
|
+
until stream.eof? # Otherwise, EOFError is raised at end.
|
26
|
+
select([stream]) # Note stream usable as IO.
|
27
|
+
|
28
|
+
# Use #read instead of #each or #map so that, in the case of msgpack and
|
29
|
+
# yajl, only the available bytes in io are copied to the stream's buffer
|
30
|
+
# and parsed to objects.
|
31
|
+
stream.read do |obj|
|
32
|
+
p obj
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "select again"
|
36
|
+
end
|
37
|
+
puts "rd.eof? = #{rd.eof?}"
|
38
|
+
stream.close
|
39
|
+
|
40
|
+
ensure
|
41
|
+
Process.wait pid if pid
|
42
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'object-stream'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
begin
|
5
|
+
type = ObjectStream::MSGPACK_TYPE
|
6
|
+
|
7
|
+
s, t = UNIXSocket.pair
|
8
|
+
|
9
|
+
pid = fork do
|
10
|
+
stream = ObjectStream.new(s, type: type)
|
11
|
+
10.times do |i|
|
12
|
+
stream << [i]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
s.close
|
17
|
+
stream = ObjectStream.new(t, type: type)
|
18
|
+
|
19
|
+
until stream.eof?
|
20
|
+
p stream.read
|
21
|
+
end
|
22
|
+
|
23
|
+
ensure
|
24
|
+
Process.wait pid if pid
|
25
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
case ARGV[0]
|
2
|
+
when "marshal", "yaml", "json", "msgpack"
|
3
|
+
else
|
4
|
+
abort "Usage: #$0 marshal|yaml|json|msgpack"
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'object-stream'
|
8
|
+
require 'socket'
|
9
|
+
require 'stringio'
|
10
|
+
|
11
|
+
begin
|
12
|
+
type = ARGV.shift
|
13
|
+
|
14
|
+
s, t = UNIXSocket.pair
|
15
|
+
|
16
|
+
pid = fork do
|
17
|
+
sio = StringIO.new
|
18
|
+
stream = ObjectStream.new(sio, type: type)
|
19
|
+
10.times do |i|
|
20
|
+
stream << "foo bar #{i}"
|
21
|
+
end
|
22
|
+
|
23
|
+
sio.rewind
|
24
|
+
stream2 = ObjectStream.new(sio, type: type)
|
25
|
+
stream2.each_with_index do |x, i|
|
26
|
+
raise unless x == "foo bar #{i}"
|
27
|
+
end
|
28
|
+
|
29
|
+
sio.rewind
|
30
|
+
data = sio.read
|
31
|
+
pos = data.index "bar 5"
|
32
|
+
raise unless pos < data.size - 10 # assume strings not munged
|
33
|
+
s.write data[0...pos]
|
34
|
+
puts "simulating a slow sender -- " +
|
35
|
+
"see if receiver blocks or stops reading and goes back to select"
|
36
|
+
sleep 0.1
|
37
|
+
s.write data[pos...pos+1]
|
38
|
+
sleep 0.1
|
39
|
+
s.write data[pos+1...pos+2]
|
40
|
+
sleep 0.1
|
41
|
+
s.write data[pos+2..-1]
|
42
|
+
end
|
43
|
+
|
44
|
+
s.close
|
45
|
+
stream = ObjectStream.new(t, type: type)
|
46
|
+
|
47
|
+
select_count = 0
|
48
|
+
empty_read_count = 0
|
49
|
+
until stream.eof? # Otherwise, EOFError is raised at end.
|
50
|
+
select_count += 1
|
51
|
+
puts "select #{select_count}"
|
52
|
+
select([stream]) # Note stream usable as IO.
|
53
|
+
|
54
|
+
# Use #read instead of #each or #map so that, in the case of msgpack and
|
55
|
+
# yajl, only the available bytes in io are copied to the stream's buffer
|
56
|
+
# and parsed to objects.
|
57
|
+
obj_count = 0
|
58
|
+
stream.read do |obj|
|
59
|
+
p obj
|
60
|
+
obj_count += 1
|
61
|
+
end
|
62
|
+
if obj_count == 0
|
63
|
+
empty_read_count += 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
stream.close
|
68
|
+
if select_count > 1 and empty_read_count > 0
|
69
|
+
puts "For #{type}, slow sender did not block the receiver!"
|
70
|
+
else
|
71
|
+
puts "For #{type}, slow sender blocked the receiver!"
|
72
|
+
end
|
73
|
+
|
74
|
+
ensure
|
75
|
+
Process.wait pid if pid
|
76
|
+
end
|
data/examples/socket.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
case ARGV[0]
|
2
|
+
when "marshal", "yaml", "json", "msgpack"
|
3
|
+
else
|
4
|
+
abort "Usage: #$0 marshal|yaml|json|msgpack"
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'object-stream'
|
8
|
+
require 'socket'
|
9
|
+
|
10
|
+
begin
|
11
|
+
type = ARGV.shift
|
12
|
+
|
13
|
+
s, t = UNIXSocket.pair
|
14
|
+
|
15
|
+
pid = fork do
|
16
|
+
stream = ObjectStream.new(s, type: type)
|
17
|
+
10.times do |i|
|
18
|
+
stream << [i] # box the int because json needs it
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
s.close
|
23
|
+
stream = ObjectStream.new(t, type: type)
|
24
|
+
|
25
|
+
puts "try to select"
|
26
|
+
until stream.eof? # Otherwise, EOFError is raised at end.
|
27
|
+
select([stream]) # Note stream usable as IO.
|
28
|
+
|
29
|
+
# Use #read instead of #each or #map so that, in the case of msgpack and
|
30
|
+
# yajl, only the available bytes in io are copied to the stream's buffer
|
31
|
+
# and parsed to objects.
|
32
|
+
stream.read do |obj|
|
33
|
+
p obj
|
34
|
+
end
|
35
|
+
|
36
|
+
puts "select again"
|
37
|
+
end
|
38
|
+
puts "t.eof? = #{t.eof?}"
|
39
|
+
stream.close
|
40
|
+
|
41
|
+
ensure
|
42
|
+
Process.wait pid if pid
|
43
|
+
end
|