Skip to content
This repository has been archived by the owner on Mar 15, 2022. It is now read-only.

Commit

Permalink
Merge pull request #15 from ruby-concurrency/initial-updates
Browse files Browse the repository at this point in the history
Initial updates
  • Loading branch information
jdantonio committed Jan 26, 2015
2 parents e1462f4 + f22e34a commit 3766879
Show file tree
Hide file tree
Showing 19 changed files with 158 additions and 93 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ tmp
rdoc
*.rbc
coverage
.ruby-version
.ruby-version
.ruby-gemset
Gemfile.lock
30 changes: 26 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
language: ruby

rvm:
- 1.8.7
- 1.9.2
- rbx
- jruby
- 2.2.0
- 2.1.5
- 2.1.4
- 2.0.0
- 1.9.3
- ruby-head
- jruby-1.7.18
- jruby-head
- rbx-2

jdk:
- oraclejdk8

sudo: false

branches:
only:
- master

matrix:
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
- rvm: 1.9.3
20 changes: 20 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
source 'https://rubygems.org'

gemspec

group :development do
gem 'rake', '~> 10.3.2'
end

group :testing do
gem 'test-unit', '~> 3.0.9'
#gem 'rspec', '~> 3.0.0'
#gem 'simplecov', '~> 0.8.2', :require => false
gem 'coveralls', '~> 0.7.3', :require => false
end

group :documentation do
gem 'yard', '~> 0.8.7.4', :require => false
gem 'inch', '~> 0.4.6', :platforms => :mri, :require => false
gem 'redcarpet', '~> 3.1.2', platforms: :mri # understands github markdown
end
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Ref

[![Gem Version](https://badge.fury.io/rb/ref.svg)](http://badge.fury.io/rb/ref) [![Build Status](https://travis-ci.org/ruby-concurrency/ref.svg?branch=master)](https://travis-ci.org/ruby-concurrency/ref) [![Coverage Status](https://img.shields.io/coveralls/ruby-concurrency/ref/master.svg)](https://coveralls.io/r/ruby-concurrency/ref) [![Code Climate](https://codeclimate.com/github/ruby-concurrency/ref.svg)](https://codeclimate.com/github/ruby-concurrency/ref) [![Dependency Status](https://gemnasium.com/ruby-concurrency/ref.svg)](https://gemnasium.com/ruby-concurrency/ref) [![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT) [![Gitter chat](http://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/ruby-concurrency/concurrent-ruby)

This library provides object references for Ruby as well as some common utilities for working with references. Object references are used to point to other objects and come in three distinct flavors that interact differently with the garbage collector.

* `Ref::StrongReference` - This is a plain old pointer to another object.
* `Ref::WeakReference` - This is a pointer to another object, but it is not seen by the garbage collector and the memory used by the object can be reclaimed at any time.
* `Ref::SoftReference` - This is similar to a weak reference, but the garbage collector is not as eager to reclaim the referenced object.

All of these classes extend from a common `Ref::Reference` class and have a common interface.

Weak and soft references are useful when you have instantiated objects that you may want to use again but can recreate if necessary. Since the garbage collector determines when to reclaim the memory used by the objects, you don't need to worry about bloating the Ruby heap.

## Example Usage

```ruby
ref = Ref::WeakReference.new("hello")
ref.object # should be "hello"
ObjectSpace.garbage_collect
ref.object # should be nil (assuming the garbage collector reclaimed the reference)
```

## Goodies

This library also includes tools for some common uses of weak and soft references.

* `Ref::WeakKeyMap` - A map of keys to values where the keys are weak references
* `Ref::WeakValueMap` - A map of keys to values where the values are weak references
* `Ref::SoftKeyMap` - A map of keys to values where the keys are soft references
* `Ref::SoftValueMap` - A map of keys to values where the values are soft references
* `Ref::ReferenceQueue` - A thread safe implementation of a queue that will add references to itself as their objects are garbage collected.

## Problems with WeakRef

Ruby does come with the `WeakRef` class in the standard library. However, there are [issues with this class](https://bugs.ruby-lang.org/issues/4168) across several different Ruby runtimes. This gem provides a common interface to weak references that works across MRI, Ruby Enterprise Edition, YARV, Jruby, Rubinius, and IronRuby.

1. MRI and REE 1.8 - `WeakRef` extends from Delegator which is a very heavy weight class under Ruby 1.8. Creating a `WeakRef` object will allocate thousands of other objects and use up hundreds of kilobytes of memory. This makes `WeakRef` all but unusable even if you only need several hundred of them.
2. YARV 1.9 - `WeakRef` is unsafe to use because the garbage collector can run in a different system thread than a thread allocating memory. This exposes a bug where a `WeakRef` may end up pointing to a completely different object than it originally referenced.
3. Jruby and IronRuby - Jruby and IronRuby using the Ruby 1.8 libraries suffers from the same performance issue with the Delegator class. Furthermore, these VM's don't implement the method used to load an object from the heap using an object id and so cannot use a pure Ruby method to implement weak references.
4. Rubinius - Rubinius implements `WeakRef` with a lighter weight version of delegation and works very well.
5. MRI Ruby 2.0 has a good implementation of `WeakRef`.

## BasicObject

Note that weak references will not work with MRI 1.9 or earlier. References will be created, but the objects will never be stored so the reference object will always treat the object as if it is always garbage collected. BasicObject does not implement the necessary methods to maintain the reference.
40 changes: 0 additions & 40 deletions README.rdoc

This file was deleted.

1 change: 0 additions & 1 deletion VERSION

This file was deleted.

37 changes: 17 additions & 20 deletions lib/ref.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module Ref
require File.join(File.dirname(__FILE__), "ref", "abstract_reference_value_map.rb")
require File.join(File.dirname(__FILE__), "ref", "abstract_reference_key_map.rb")
require File.join(File.dirname(__FILE__), "ref", "reference.rb")
require File.join(File.dirname(__FILE__), "ref", "reference_queue.rb")
require File.join(File.dirname(__FILE__), "ref", "safe_monitor.rb")
require 'ref/abstract_reference_value_map'
require 'ref/abstract_reference_key_map'
require 'ref/reference'
require 'ref/reference_queue'
require 'ref/safe_monitor'

# Set the best implementation for weak references based on the runtime.
if defined?(RUBY_PLATFORM) && RUBY_PLATFORM == 'java'
Expand All @@ -15,31 +15,28 @@ module Ref
$LOAD_PATH.shift if $LOAD_PATH.first == File.dirname(__FILE__)
end
else
require File.join(File.dirname(__FILE__), "ref", "soft_reference.rb")
require 'ref/soft_reference'
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ironruby'
# IronRuby has it's own implementation of weak references.
require File.join(File.dirname(__FILE__), "ref", "weak_reference", "iron_ruby.rb")
require 'ref/weak_reference/iron_ruby'
elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
# If using Rubinius set the implementation to use WeakRef since it is very efficient and using finalizers is not.
require File.join(File.dirname(__FILE__), "ref", "weak_reference", "weak_ref.rb")
require 'ref/weak_reference/weak_ref'
elsif defined?(::ObjectSpace::WeakMap)
# Ruby 2.0 has a working implementation of weakref.rb backed by the new ObjectSpace::WeakMap
require File.join(File.dirname(__FILE__), "ref", "weak_reference", "weak_ref.rb")
require 'ref/weak_reference/weak_ref'
elsif defined?(::ObjectSpace._id2ref)
# If ObjectSpace can lookup objects from their object_id, then use the pure ruby implementation.
require File.join(File.dirname(__FILE__), "ref", "weak_reference", "pure_ruby.rb")
require 'ref/weak_reference/pure_ruby'
else
# Otherwise, wrap the standard library WeakRef class
require File.join(File.dirname(__FILE__), "ref", "weak_reference", "weak_ref.rb")
require 'ref/weak_reference/weak_ref'
end
end

require File.join(File.dirname(__FILE__), "ref", "soft_key_map.rb")
require File.join(File.dirname(__FILE__), "ref", "soft_value_map.rb")
require File.join(File.dirname(__FILE__), "ref", "strong_reference.rb")
require File.join(File.dirname(__FILE__), "ref", "weak_key_map.rb")
require File.join(File.dirname(__FILE__), "ref", "weak_value_map.rb")

# Used for testing
autoload :Mock, File.join(File.dirname(__FILE__), "ref", "mock.rb")

require 'ref/soft_key_map'
require 'ref/soft_value_map'
require 'ref/strong_reference'
require 'ref/weak_key_map'
require 'ref/weak_value_map'
end
3 changes: 3 additions & 0 deletions lib/ref/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Ref
VERSION = '1.0.5'
end
35 changes: 22 additions & 13 deletions ref.gemspec
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
$:.push File.join(File.dirname(__FILE__), 'lib')

require 'ref/version'

Gem::Specification.new do |s|
s.name = 'ref'
s.version = File.read(File.expand_path("../VERSION", __FILE__)).strip
s.summary = "Library that implements weak, soft, and strong references in Ruby."
s.name = 'ref'
s.version = Ref::VERSION
s.authors = ['Brian Durand']
s.email = ['bbdurand@gmail.com']
s.homepage = "http://github.com/ruby-concurrency/ref"
s.summary = "Library that implements weak, soft, and strong references in Ruby."
s.description = "Library that implements weak, soft, and strong references in Ruby that work across multiple runtimes (MRI, REE, YARV, Jruby, Rubinius, and IronRuby). Also includes implementation of maps/hashes that use references and a reference queue."
s.license = "MIT"
s.date = Time.now.strftime('%Y-%m-%d')

s.authors = ['Brian Durand']
s.email = ['bbdurand@gmail.com']
s.homepage = "http://github.com/bdurand/ref"
s.files = ['README.md', 'MIT_LICENSE']
s.files += Dir['lib/**/*.*']
s.files += Dir['ext/**/*.*']
s.files += Dir['test/**/*.*']

s.files = ['README.rdoc', 'VERSION', 'MIT_LICENSE'] + Dir.glob('lib/**/*'), Dir.glob('test/**/*'), Dir.glob('ext/**/*')
s.require_path = 'lib'
s.require_paths = ['lib']

s.has_rdoc = true
s.rdoc_options = ["--charset=UTF-8", "--main", "README.rdoc"]
s.extra_rdoc_files = ["README.rdoc"]
s.license = "MIT"
s.has_rdoc = true
s.rdoc_options = ["--charset=UTF-8", "--main", "README.md"]
s.extra_rdoc_files = ["README.md"]

s.required_ruby_version = '>= 1.9.3'
end
2 changes: 1 addition & 1 deletion test/mock_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require File.expand_path("../test_helper", __FILE__)
require_relative 'test_helper'

class TestMock < Test::Unit::TestCase
def test_gc_with_argument
Expand Down
2 changes: 1 addition & 1 deletion test/reference_queue_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require File.expand_path("../test_helper", __FILE__)
require_relative 'test_helper'

class TestReferenceQueue < Test::Unit::TestCase
def test_can_add_references
Expand Down
2 changes: 1 addition & 1 deletion test/soft_key_map_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require File.expand_path("../test_helper", __FILE__)
require_relative 'test_helper'

class TestSoftKeyMap < Test::Unit::TestCase
include ReferenceKeyMapBehavior
Expand Down
2 changes: 1 addition & 1 deletion test/soft_reference_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require File.expand_path("../test_helper", __FILE__)
require_relative 'test_helper'

class TestSoftReference < Test::Unit::TestCase
def test_can_get_non_garbage_collected_objects
Expand Down
2 changes: 1 addition & 1 deletion test/soft_value_map_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require File.expand_path("../test_helper", __FILE__)
require_relative 'test_helper'

class TestSoftValueMap < Test::Unit::TestCase
include ReferenceValueMapBehavior
Expand Down
2 changes: 1 addition & 1 deletion test/strong_reference_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require File.expand_path("../test_helper", __FILE__)
require_relative 'test_helper'

class TestStrongReference < Test::Unit::TestCase
def test_can_get_objects
Expand Down
12 changes: 9 additions & 3 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
require 'coveralls'
Coveralls.wear!

require 'test/unit'
require File.expand_path("../../lib/ref", __FILE__)
require File.expand_path("../reference_key_map_behavior", __FILE__)
require File.expand_path("../reference_value_map_behavior", __FILE__)

require 'ref'
require 'ref/mock'

require_relative './reference_key_map_behavior'
require_relative './reference_value_map_behavior'
2 changes: 1 addition & 1 deletion test/weak_key_map_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require File.expand_path("../test_helper", __FILE__)
require_relative 'test_helper'

class TestWeakKeyMap < Test::Unit::TestCase
include ReferenceKeyMapBehavior
Expand Down
7 changes: 4 additions & 3 deletions test/weak_reference_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require File.expand_path("../test_helper", __FILE__)
require_relative 'test_helper'

class TestWeakReference < Test::Unit::TestCase
def test_can_get_non_garbage_collected_objects
Expand Down Expand Up @@ -44,11 +44,12 @@ def test_inspect
assert ref.inspect
end

if defined?(BasicObject)
if defined?(BasicObject) && RUBY_VERSION != '1.9.3'
# Ref::WeakRef does not work on MRI 1.9.3 or earlier
def test_basic_object_does_not_throw_exception
obj = BasicObject.new
ref = Ref::WeakReference.new(obj)
assert_equal obj, ref.object
assert_equal obj.__id__, ref.object.__id__
end
end
end
2 changes: 1 addition & 1 deletion test/weak_value_map_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require File.expand_path("../test_helper", __FILE__)
require_relative 'test_helper'

class TestWeakValueMap < Test::Unit::TestCase
include ReferenceValueMapBehavior
Expand Down

0 comments on commit 3766879

Please sign in to comment.