obuf 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,8 @@
1
1
  rvm:
2
2
  - 1.8.7
3
- - 1.9.2
4
- - 1.9.3
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.1
6
+ - 2.2.0
7
+ - jruby
8
+ - ruby-head
data/Gemfile CHANGED
@@ -1,7 +1,8 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
3
  group :development do
4
- gem "jeweler"
4
+ gem "bundler"
5
+ gem "flexmock", "~>0.8"
5
6
  gem "rake"
6
- gem "flexmock", "~> 0.8"
7
+ gem "jeweler", '1.8.4' # Last one without the stupid nokogiri dependency
7
8
  end
@@ -1,3 +1,7 @@
1
+ === 1.2.0 / 2015-04-17
2
+
3
+ * Add Obuf::Lens to provide access to externally managed IO objects and files
4
+
1
5
  === 1.1.0 / 2011-08-02
2
6
 
3
7
  * Stores every item from the Enumerable passed to the constructor
@@ -1,37 +1,42 @@
1
- = obuf
2
-
3
- * http://github.com/julik/obuf
4
-
5
- == DESCRIPTION:
1
+ # obuf
6
2
 
7
3
  A simple Ruby object buffer. Use this if you need to temporarily store alot of serializable Ruby objects.
8
4
 
9
- == FEATURES/PROBLEMS:
10
-
11
- * Thread safe (for as far as I can gauge it)
12
- * Gives both read and write access to the storage
13
-
14
- == SYNOPSIS:
15
-
16
5
  obuf = Obuf.new
17
6
  5_000_000.times{ obuf.push(compute_some_object) } # no memory inflation
18
7
  obuf.each do | stored_object | # objects are restored one by one
19
8
  # do something with stored_object
20
9
  end
21
10
 
22
- == REQUIREMENTS:
11
+ You can also write objects in one process, and recover them in another, using `Obuf::Lens`:
12
+
13
+ # In fork process
14
+ File.open('/tmp/output.bin', 'a') do |f|
15
+ Obuf::Lens.new(f) << my_object
16
+ end
17
+
18
+ # In join process after forks completed
19
+ File.open('/tmp/output.bin', 'r') do |f|
20
+ Obuf::Lens.new(f).each do | object_written_by_fork |
21
+ puts object_written_by_fork.inspect
22
+ end
23
+ end
24
+
25
+ The `Lens` is what is used under the hood in the main Obuf as well.
26
+
27
+ ## Requirements
23
28
 
24
29
  * Ruby 1.8.6+
25
30
 
26
- == INSTALL:
31
+ ## Installation
27
32
 
28
33
  * gem install obuf
29
34
 
30
- == LICENSE:
35
+ ## License
31
36
 
32
37
  (The MIT License)
33
38
 
34
- Copyright (c) 2011 Julik Tarkhanov <me@julik.nl>
39
+ Copyright (c) 2011-2015 Julik Tarkhanov <me@julik.nl>
35
40
 
36
41
  Permission is hereby granted, free of charge, to any person obtaining
37
42
  a copy of this software and associated documentation files (the
@@ -19,13 +19,12 @@ require "thread" # required for ruby 18
19
19
  #
20
20
  # Both reading and writing aim to be threadsafe
21
21
  class Obuf
22
- VERSION = "1.1.0"
22
+ VERSION = "1.2.0"
23
+ require File.dirname(__FILE__) + "/obuf/lens"
24
+ require File.dirname(__FILE__) + "/obuf/protected_lens"
23
25
 
24
26
  include Enumerable
25
27
 
26
- DELIM = "\t"
27
- END_RECORD = "\n"
28
-
29
28
  # Returns the number of objects stored so far
30
29
  attr_reader :size
31
30
 
@@ -34,13 +33,15 @@ class Obuf
34
33
  def initialize(enumerable = [])
35
34
  @sem = Mutex.new
36
35
  @store = Tempfile.new("obuf")
37
- @store.set_encoding(Encoding::BINARY) if @store.respond_to?(:set_encoding)
38
36
  @store.binmode
39
37
  @size = 0
40
38
 
39
+ @lens = Obuf::ProtectedLens.new(@store)
40
+
41
41
  # Store everything from the enumerable in self
42
- enumerable.each(&method(:push))
42
+ enumerable.each { |e| push(e) }
43
43
 
44
+ # ...and yield self for any configuration
44
45
  yield self if block_given?
45
46
  end
46
47
 
@@ -51,14 +52,10 @@ class Obuf
51
52
 
52
53
  # Store an object
53
54
  def push(object_to_store)
54
- blob = marshal_object(object_to_store)
55
- @sem.synchronize do
56
- @store.write(blob.size)
57
- @store.write(DELIM)
58
- @store.write(blob)
59
- @store.write(END_RECORD)
55
+ @sem.synchronize {
56
+ @lens << object_to_store
60
57
  @size += 1
61
- end
58
+ }
62
59
  object_to_store
63
60
  end
64
61
 
@@ -68,7 +65,8 @@ class Obuf
68
65
  # methods are also available (but be careful with Enumerable#map and to_a)
69
66
  def each
70
67
  with_separate_read_io do | iterable |
71
- @size.times { yield(recover_object_from(iterable)) }
68
+ reading_lens = Obuf::Lens.new(iterable)
69
+ @size.times { yield(reading_lens.recover_object) }
72
70
  end
73
71
  end
74
72
 
@@ -89,15 +87,8 @@ class Obuf
89
87
 
90
88
  def recover_at(idx)
91
89
  with_separate_read_io do | iterable |
92
- iterable.seek(0)
93
-
94
- # Do not unmarshal anything but wind the IO in fixed offsets
95
- idx.times do
96
- skip_bytes = iterable.gets("\t").to_i
97
- iterable.seek(iterable.pos + skip_bytes)
98
- end
99
-
100
- recover_object_from(iterable)
90
+ reading_lens = Obuf::Lens.new(iterable)
91
+ reading_lens.recover_at(idx)
101
92
  end
102
93
  end
103
94
 
@@ -105,38 +96,15 @@ class Obuf
105
96
  # and iterate through that (we will have one IO handle per loop nest)
106
97
  def with_separate_read_io
107
98
  # Ensure all data is written before we read it
108
- @sem.synchronize { @store.flush }
99
+ iterable = @sem.synchronize do
100
+ @store.flush
101
+ File.open(@store.path, "rb")
102
+ end
109
103
 
110
- iterable = File.open(@store.path, "rb")
111
104
  begin
112
105
  yield(iterable)
113
106
  ensure
114
107
  iterable.close
115
108
  end
116
109
  end
117
-
118
- def recover_object_from(io)
119
- # Up to the tab is the amount of bytes to read
120
- demarshal_bytes = io.gets("\t").to_i
121
-
122
- # When at end of IO return nil
123
- return nil if demarshal_bytes == 0
124
-
125
- blob = io.read(demarshal_bytes)
126
- demarshal_object(blob)
127
- end
128
-
129
- # This method is only used internally.
130
- # Override this if you need non-default marshalling
131
- # (don't forget to also override demarshal_object)
132
- def marshal_object(object_to_store)
133
- d = Marshal.dump(object_to_store)
134
- end
135
-
136
- # This method is only used internally.
137
- # Override this if you need non-default demarshalling
138
- # (don't forget to also override marshal_object)
139
- def demarshal_object(blob)
140
- Marshal.load(blob)
141
- end
142
110
  end
@@ -0,0 +1,70 @@
1
+ require 'thread'
2
+
3
+ # Provides a per-object iterator on top of any IO object or pipe
4
+ class Obuf::Lens
5
+ include Enumerable
6
+
7
+ DELIM = "\t"
8
+ END_RECORD = "\n"
9
+
10
+ # Creates a new Lens on top of a File object, IO object or pipe.
11
+ # The object given should support +seek+, +gets+, and +read+ to recover
12
+ # objects, and +write+ to dump objects.
13
+ def initialize(io_or_pipe)
14
+ @io = io_or_pipe
15
+ end
16
+
17
+ # Store an object
18
+ def <<(object_to_store)
19
+ blob = marshal_object(object_to_store)
20
+ @io.write(blob.size)
21
+ @io.write(DELIM)
22
+ @io.write(blob)
23
+ @io.write(END_RECORD)
24
+ object_to_store
25
+ end
26
+
27
+ # Recover Nth object
28
+ def recover_at(idx)
29
+ @io.seek(0)
30
+ # Do not unmarshal anything but wind the IO in fixed offsets
31
+ idx.times do
32
+ skip_bytes = @io.gets("\t").to_i
33
+ @io.seek(@io.pos + skip_bytes + 1)
34
+ end
35
+ recover_object
36
+ end
37
+
38
+ # Recover the object at the current position in the IO. Returns +nil+
39
+ # if there is nothing to recover or the backing buffer is empty.
40
+ def recover_object
41
+ # Up to the tab is the amount of bytes to read
42
+ demarshal_bytes = @io.gets("\t").to_i
43
+
44
+ # When at end of IO return nil
45
+ return nil if demarshal_bytes == 0
46
+
47
+ blob = @io.read(demarshal_bytes)
48
+ demarshal_object(blob)
49
+ end
50
+
51
+ def each
52
+ yield(recover_object) until @io.eof?
53
+ end
54
+
55
+ private
56
+
57
+ # This method is only used internally.
58
+ # Override this if you need non-default marshalling
59
+ # (don't forget to also override demarshal_object)
60
+ def marshal_object(object_to_store)
61
+ Marshal.dump(object_to_store)
62
+ end
63
+
64
+ # This method is only used internally.
65
+ # Override this if you need non-default demarshalling
66
+ # (don't forget to also override marshal_object)
67
+ def demarshal_object(blob)
68
+ Marshal.load(blob)
69
+ end
70
+ end
@@ -0,0 +1,23 @@
1
+ require 'thread'
2
+
3
+ # Similar to Obuf::Lens but protects all the operations that change the IO offset
4
+ # with a Mutex.
5
+ class Obuf::ProtectedLens < Obuf::Lens
6
+ def initialize(io)
7
+ super
8
+ @mutex = Mutex.new
9
+ end
10
+
11
+ # Store an object
12
+ def <<(object_to_store)
13
+ @mutex.synchronize { super }
14
+ end
15
+
16
+ def recover_at(idx)
17
+ @mutex.synchronize { super }
18
+ end
19
+
20
+ def recover_object
21
+ @mutex.synchronize { super }
22
+ end
23
+ end
@@ -5,49 +5,54 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "obuf"
8
- s.version = "1.1.0"
8
+ s.version = "1.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Julik Tarkhanov"]
12
- s.date = "2012-02-27"
12
+ s.date = "2015-04-17"
13
13
  s.description = "Stores marshaled temporary objects on-disk in a simple Enumerable"
14
14
  s.email = "me@julik.nl"
15
15
  s.extra_rdoc_files = [
16
- "README.rdoc"
16
+ "README.md"
17
17
  ]
18
18
  s.files = [
19
19
  ".autotest",
20
20
  ".travis.yml",
21
21
  "Gemfile",
22
22
  "History.txt",
23
- "README.rdoc",
23
+ "README.md",
24
24
  "Rakefile",
25
25
  "lib/obuf.rb",
26
+ "lib/obuf/lens.rb",
27
+ "lib/obuf/protected_lens.rb",
26
28
  "obuf.gemspec",
27
29
  "test/test_obuf.rb"
28
30
  ]
29
31
  s.homepage = "http://github.com/julik/obuf"
30
32
  s.licenses = ["MIT"]
31
33
  s.require_paths = ["lib"]
32
- s.rubygems_version = "1.8.15"
34
+ s.rubygems_version = "1.8.23.2"
33
35
  s.summary = "Ruby disk-backed object buffer"
34
36
 
35
37
  if s.respond_to? :specification_version then
36
38
  s.specification_version = 3
37
39
 
38
40
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
39
- s.add_development_dependency(%q<jeweler>, [">= 0"])
40
- s.add_development_dependency(%q<rake>, [">= 0"])
41
+ s.add_development_dependency(%q<bundler>, [">= 0"])
41
42
  s.add_development_dependency(%q<flexmock>, ["~> 0.8"])
43
+ s.add_development_dependency(%q<rake>, [">= 0"])
44
+ s.add_development_dependency(%q<jeweler>, ["= 1.8.4"])
42
45
  else
43
- s.add_dependency(%q<jeweler>, [">= 0"])
44
- s.add_dependency(%q<rake>, [">= 0"])
46
+ s.add_dependency(%q<bundler>, [">= 0"])
45
47
  s.add_dependency(%q<flexmock>, ["~> 0.8"])
48
+ s.add_dependency(%q<rake>, [">= 0"])
49
+ s.add_dependency(%q<jeweler>, ["= 1.8.4"])
46
50
  end
47
51
  else
48
- s.add_dependency(%q<jeweler>, [">= 0"])
49
- s.add_dependency(%q<rake>, [">= 0"])
52
+ s.add_dependency(%q<bundler>, [">= 0"])
50
53
  s.add_dependency(%q<flexmock>, ["~> 0.8"])
54
+ s.add_dependency(%q<rake>, [">= 0"])
55
+ s.add_dependency(%q<jeweler>, ["= 1.8.4"])
51
56
  end
52
57
  end
53
58
 
@@ -4,6 +4,7 @@ require "flexmock"
4
4
  require "flexmock/test_unit"
5
5
  require "stringio"
6
6
 
7
+ # We are limited to flexmock 0.8 on Ruby 1.8
7
8
  # http://redmine.ruby-lang.org/issues/4882
8
9
  # https://github.com/jimweirich/flexmock/issues/4
9
10
  # https://github.com/julik/flexmock/commit/4acea00677e7b558bd564ec7c7630f0b27d368ca
@@ -87,15 +88,6 @@ class TestObuf < Test::Unit::TestCase
87
88
  assert_equal "E\r\nWow!", a[4]
88
89
  end
89
90
 
90
- def test_random_access
91
- a = Obuf.new
92
- letters = ("A".."Z").map{|e| "#{e}\r\nWow!"}.to_a
93
- letters.map(&a.method(:push))
94
-
95
- assert_equal "B\r\nWow!", a[1]
96
- assert_equal "E\r\nWow!", a[4]
97
- end
98
-
99
91
  def test_random_access_out_of_bounds
100
92
  a = Obuf.new
101
93
  letters = ("A".."Z").map{|e| "#{e}\r\nWow!"}.to_a
@@ -119,3 +111,74 @@ class TestObuf < Test::Unit::TestCase
119
111
  assert_equal 0, a.size
120
112
  end
121
113
  end
114
+
115
+ class TestLens < Test::Unit::TestCase
116
+ def test_lens_write
117
+ mock_io = flexmock()
118
+ lens = Obuf::Lens.new(mock_io)
119
+ flexmock(mock_io).should_receive(:write){|written|
120
+ assert_kind_of Fixnum, written
121
+ }
122
+ flexmock(mock_io).should_receive(:write).with("\t")
123
+ flexmock(mock_io).should_receive(:write){|byte_blob|
124
+ assert_kind_of String, byte_blob
125
+ }
126
+ flexmock(mock_io).should_receive(:write).with("\n")
127
+ lens << "Hi there!"
128
+ end
129
+
130
+ def test_lens_recover_object_after_write
131
+ mock_io = StringIO.new
132
+ lens = Obuf::Lens.new(mock_io)
133
+ lens << "Hi there!"
134
+
135
+ recovered = lens.recover_object
136
+ assert_nil recovered, "The IO is at the end now"
137
+
138
+ mock_io.rewind
139
+ recovered = lens.recover_object
140
+ assert_equal "Hi there!", recovered
141
+ end
142
+
143
+ def test_lens_each
144
+ mock_io = StringIO.new
145
+
146
+ writing_lens = Obuf::Lens.new(mock_io)
147
+ writing_lens << "Hi there!"
148
+ writing_lens << 98121
149
+ writing_lens << [123, :a]
150
+
151
+ mock_io.rewind
152
+
153
+ reading_lens = Obuf::Lens.new(mock_io)
154
+ items = []
155
+ reading_lens.each do | item |
156
+ items << item
157
+ end
158
+
159
+ assert_equal "Hi there!", items[0]
160
+ assert_equal 98121, items[1]
161
+ assert_equal [123, :a], items[2]
162
+ end
163
+
164
+ def test_lens_recover_at
165
+ mock_io = StringIO.new
166
+
167
+ writing_lens = Obuf::Lens.new(mock_io)
168
+ writing_lens << "Hi there!"
169
+ writing_lens << 98121
170
+ writing_lens << [123, :a]
171
+
172
+ reading_lens = Obuf::Lens.new(mock_io)
173
+ # Also try to recover object at index 3, which does not exist in the underlying buffer
174
+ objects_at_positions = (0..3).to_a.reverse.map do | i |
175
+ reading_lens.recover_at(i)
176
+ end
177
+
178
+ assert_nil objects_at_positions[0]
179
+ assert_equal [123, :a], objects_at_positions[1]
180
+ assert_equal 98121, objects_at_positions[2]
181
+ assert_equal "Hi there!", objects_at_positions[3]
182
+ end
183
+
184
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: obuf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,22 +9,43 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-27 00:00:00.000000000 Z
12
+ date: 2015-04-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: jeweler
16
- requirement: &4139570 !ruby/object:Gem::Requirement
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
17
25
  none: false
18
26
  requirements:
19
27
  - - ! '>='
20
28
  - !ruby/object:Gem::Version
21
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: flexmock
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.8'
22
38
  type: :development
23
39
  prerelease: false
24
- version_requirements: *4139570
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.8'
25
46
  - !ruby/object:Gem::Dependency
26
47
  name: rake
27
- requirement: &4139080 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
28
49
  none: false
29
50
  requirements:
30
51
  - - ! '>='
@@ -32,32 +53,44 @@ dependencies:
32
53
  version: '0'
33
54
  type: :development
34
55
  prerelease: false
35
- version_requirements: *4139080
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
36
62
  - !ruby/object:Gem::Dependency
37
- name: flexmock
38
- requirement: &4138610 !ruby/object:Gem::Requirement
63
+ name: jeweler
64
+ requirement: !ruby/object:Gem::Requirement
39
65
  none: false
40
66
  requirements:
41
- - - ~>
67
+ - - '='
42
68
  - !ruby/object:Gem::Version
43
- version: '0.8'
69
+ version: 1.8.4
44
70
  type: :development
45
71
  prerelease: false
46
- version_requirements: *4138610
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - '='
76
+ - !ruby/object:Gem::Version
77
+ version: 1.8.4
47
78
  description: Stores marshaled temporary objects on-disk in a simple Enumerable
48
79
  email: me@julik.nl
49
80
  executables: []
50
81
  extensions: []
51
82
  extra_rdoc_files:
52
- - README.rdoc
83
+ - README.md
53
84
  files:
54
85
  - .autotest
55
86
  - .travis.yml
56
87
  - Gemfile
57
88
  - History.txt
58
- - README.rdoc
89
+ - README.md
59
90
  - Rakefile
60
91
  - lib/obuf.rb
92
+ - lib/obuf/lens.rb
93
+ - lib/obuf/protected_lens.rb
61
94
  - obuf.gemspec
62
95
  - test/test_obuf.rb
63
96
  homepage: http://github.com/julik/obuf
@@ -73,6 +106,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
73
106
  - - ! '>='
74
107
  - !ruby/object:Gem::Version
75
108
  version: '0'
109
+ segments:
110
+ - 0
111
+ hash: 4056659205467737583
76
112
  required_rubygems_version: !ruby/object:Gem::Requirement
77
113
  none: false
78
114
  requirements:
@@ -81,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
117
  version: '0'
82
118
  requirements: []
83
119
  rubyforge_project:
84
- rubygems_version: 1.8.15
120
+ rubygems_version: 1.8.23.2
85
121
  signing_key:
86
122
  specification_version: 3
87
123
  summary: Ruby disk-backed object buffer