├── .github ├── dependabot.yml └── workflows │ ├── push_gem.yml │ └── test.yml ├── .gitignore ├── BSDL ├── COPYING ├── Gemfile ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib └── weakref.rb ├── test ├── lib │ └── helper.rb └── test_weakref.rb └── weakref.gemspec /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.github/workflows/push_gem.yml: -------------------------------------------------------------------------------- 1 | name: Publish gem to rubygems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | push: 13 | if: github.repository == 'ruby/weakref' 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: rubygems.org 18 | url: https://rubygems.org/gems/weakref 19 | 20 | permissions: 21 | contents: write 22 | id-token: write 23 | 24 | steps: 25 | - name: Harden Runner 26 | uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 27 | with: 28 | egress-policy: audit 29 | 30 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 31 | 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@0481980f17b760ef6bca5e8c55809102a0af1e5a # v1.263.0 34 | with: 35 | bundler-cache: true 36 | ruby-version: ruby 37 | 38 | - name: Publish to RubyGems 39 | uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 40 | 41 | - name: Create GitHub release 42 | run: | 43 | tag_name="$(git describe --tags --abbrev=0)" 44 | gh release create "${tag_name}" --verify-tag --generate-notes 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: ubuntu 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ruby-versions: 7 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 8 | with: 9 | engine: cruby 10 | min_version: 2.4 11 | 12 | test: 13 | needs: ruby-versions 14 | name: build (${{ matrix.ruby }} / ${{ matrix.os }}) 15 | strategy: 16 | matrix: 17 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 18 | os: [ ubuntu-latest, macos-latest ] 19 | exclude: 20 | - ruby: 2.4 21 | os: macos-latest 22 | - ruby: 2.5 23 | os: macos-latest 24 | runs-on: ${{ matrix.os }} 25 | steps: 26 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 27 | - name: Set up Ruby 28 | uses: ruby/setup-ruby@0481980f17b760ef6bca5e8c55809102a0af1e5a # v1.263.0 29 | with: 30 | ruby-version: ${{ matrix.ruby }} 31 | bundler-cache: true 32 | - name: Run test 33 | run: bundle exec rake test 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | Gemfile.lock 10 | -------------------------------------------------------------------------------- /BSDL: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a. place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b. use the modified software only within your corporation or 18 | organization. 19 | 20 | c. give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d. make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a. distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b. accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c. give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d. make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem "bundler" 6 | gem "rake" 7 | gem "test-unit" 8 | gem "test-unit-ruby-core" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Weakref 2 | 3 | Weak Reference class that allows a referenced object to be 4 | garbage-collected. 5 | 6 | A WeakRef may be used exactly like the object it references. 7 | 8 | ## Installation 9 | 10 | Add this line to your application's Gemfile: 11 | 12 | ```ruby 13 | gem 'weakref' 14 | ``` 15 | 16 | And then execute: 17 | 18 | $ bundle 19 | 20 | Or install it yourself as: 21 | 22 | $ gem install weakref 23 | 24 | ## Usage 25 | 26 | ```ruby 27 | foo = Object.new # create a new object instance 28 | p foo.to_s # original's class 29 | foo = WeakRef.new(foo) # reassign foo with WeakRef instance 30 | p foo.to_s # should be same class 31 | GC.start # start the garbage collector 32 | p foo.to_s # should raise exception (recycled) 33 | ``` 34 | 35 | ## Development 36 | 37 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 38 | 39 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 40 | 41 | ## Contributing 42 | 43 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/weakref. 44 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test/lib" 6 | t.ruby_opts << "-rhelper" 7 | t.test_files = FileList["test/**/test_*.rb"] 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "weakref" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /lib/weakref.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require "delegate" 3 | 4 | # Weak Reference class that allows a referenced object to be 5 | # garbage-collected. 6 | # 7 | # A WeakRef may be used exactly like the object it references. 8 | # 9 | # Usage: 10 | # 11 | # foo = Object.new # create a new object instance 12 | # p foo.to_s # original's class 13 | # foo = WeakRef.new(foo) # reassign foo with WeakRef instance 14 | # p foo.to_s # should be same class 15 | # GC.start # start the garbage collector 16 | # p foo.to_s # should raise exception (recycled) 17 | # 18 | 19 | class WeakRef < Delegator 20 | VERSION = "0.1.4" 21 | 22 | ## 23 | # RefError is raised when a referenced object has been recycled by the 24 | # garbage collector 25 | 26 | class RefError < StandardError 27 | end 28 | 29 | @@__map = ::ObjectSpace::WeakMap.new 30 | 31 | ## 32 | # Creates a weak reference to +orig+ 33 | 34 | def initialize(orig) 35 | case orig 36 | when true, false, nil 37 | @delegate_sd_obj = orig 38 | else 39 | @@__map[self] = orig 40 | end 41 | super 42 | end 43 | 44 | def __getobj__(&_block) # :nodoc: 45 | @@__map[self] or defined?(@delegate_sd_obj) ? @delegate_sd_obj : 46 | Kernel::raise(RefError, "Invalid Reference - probably recycled", Kernel::caller(2)) 47 | end 48 | 49 | def __setobj__(obj) # :nodoc: 50 | end 51 | 52 | ## 53 | # Returns true if the referenced object is still alive. 54 | 55 | def weakref_alive? 56 | @@__map.key?(self) or defined?(@delegate_sd_obj) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /test/lib/helper.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "core_assertions" 3 | 4 | Test::Unit::TestCase.include Test::Unit::CoreAssertions 5 | -------------------------------------------------------------------------------- /test/test_weakref.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'test/unit' 3 | require 'weakref' 4 | 5 | class TestWeakRef < Test::Unit::TestCase 6 | def make_weakref(level = 10) 7 | if level > 0 8 | make_weakref(level - 1) 9 | else 10 | WeakRef.new(Object.new) 11 | end 12 | end 13 | 14 | def test_ref 15 | obj = Object.new 16 | weak = WeakRef.new(obj) 17 | assert_equal(obj.to_s, weak.to_s) 18 | assert_predicate(weak, :weakref_alive?) 19 | end 20 | 21 | def test_recycled 22 | weaks = [] 23 | weak = nil 24 | 100.times do 25 | weaks << make_weakref 26 | ObjectSpace.garbage_collect 27 | ObjectSpace.garbage_collect 28 | break if weak = weaks.find {|w| !w.weakref_alive?} 29 | end 30 | assert_raise(WeakRef::RefError) {weak.to_s} 31 | assert_not_predicate(weak, :weakref_alive?) 32 | end 33 | 34 | def test_not_reference_different_object 35 | bug7304 = '[ruby-core:49044]' 36 | weakrefs = [] 37 | 3.times do 38 | obj = Object.new 39 | def obj.foo; end 40 | weakrefs << WeakRef.new(obj) 41 | ObjectSpace.garbage_collect 42 | end 43 | assert_nothing_raised(NoMethodError, bug7304) { 44 | weakrefs.each do |weak| 45 | begin 46 | weak.foo 47 | rescue WeakRef::RefError 48 | end 49 | end 50 | } 51 | end 52 | 53 | def test_weakref_finalize 54 | bug7304 = '[ruby-core:49044]' 55 | assert_normal_exit %q{ 56 | require 'weakref' 57 | obj = Object.new 58 | 3.times do 59 | WeakRef.new(obj) 60 | ObjectSpace.garbage_collect 61 | end 62 | }, bug7304 63 | end 64 | 65 | def test_repeated_object_memory_leak 66 | bug10537 = '[ruby-core:66428]' 67 | assert_no_memory_leak(%w(-rweakref), '', <<-'end;', bug10537, timeout: 60) 68 | a = Object.new 69 | 150_000.times { WeakRef.new(a) } 70 | end; 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /weakref.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | name = File.basename(__FILE__, ".gemspec") 4 | version = ["lib", Array.new(name.count("-")+1, ".").join("/")].find do |dir| 5 | break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| 6 | /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 7 | end rescue nil 8 | end 9 | 10 | Gem::Specification.new do |spec| 11 | spec.name = name 12 | spec.version = version 13 | spec.authors = ["Yukihiro Matsumoto"] 14 | spec.email = ["matz@ruby-lang.org"] 15 | 16 | spec.summary = %q{Allows a referenced object to be garbage-collected.} 17 | spec.description = %q{Allows a referenced object to be garbage-collected.} 18 | spec.homepage = "https://github.com/ruby/weakref" 19 | 20 | spec.metadata["homepage_uri"] = spec.homepage 21 | spec.metadata["source_code_uri"] = "https://github.com/ruby/weakref" 22 | spec.licenses = ["Ruby", "BSD-2-Clause"] 23 | 24 | # Specify which files should be added to the gem when it is released. 25 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 26 | spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do 27 | `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 28 | end 29 | spec.require_paths = ["lib"] 30 | 31 | spec.add_dependency "delegate" 32 | end 33 | --------------------------------------------------------------------------------