object-stream 0.1

Sign up to get free protection for your applications and to get access to all the features.
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,9 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ desc "Run tests"
5
+ Rake::TestTask.new :test do |t|
6
+ t.libs << "lib"
7
+ t.libs << "ext"
8
+ t.test_files = FileList["test/**/*.rb"]
9
+ end
@@ -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
@@ -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
@@ -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
@@ -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