Skip to content

Commit

Permalink
Merge pull request #485 from chopraanmol1/add_finalizer_to_weak_insta…
Browse files Browse the repository at this point in the history
…nce_cache

Add finalizer to weak instance cache
  • Loading branch information
chopraanmol1 authored Jan 11, 2019
2 parents aee36f2 + ddcf495 commit cdfa468
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 0 deletions.
9 changes: 9 additions & 0 deletions lib/roo/helpers/weak_instance_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,20 @@ def instance_cache(key)

unless object
object = yield
ObjectSpace.define_finalizer(object, instance_cache_finalizer(key))
instance_variable_set(key, WeakRef.new(object))
end

object
end

def instance_cache_finalizer(key)
proc do |object_id|
if instance_variable_defined?(key) && (ref = instance_variable_get(key)) && (!ref.weakref_alive? || ref.__getobj__.object_id == object_id)
remove_instance_variable(key)
end
end
end
end
end
end
90 changes: 90 additions & 0 deletions spec/lib/roo/weak_instance_cache_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
require 'spec_helper'

if RUBY_PLATFORM == "java"
require 'java'
java_import 'java.lang.System'
end

describe Roo::Helpers::WeakInstanceCache do
let(:klass) do
Class.new do
include Roo::Helpers::WeakInstanceCache

def memoized_data
instance_cache(:@memoized_data) do
"Some Costly Operation #{rand(1000)}" * 1_000
end
end
end
end

subject do
klass.new
end

it 'should be lazy' do
expect(subject.instance_variables).to_not include(:@memoized_data)
data = subject.memoized_data
expect(subject.instance_variables).to include(:@memoized_data)
end


it 'should be memoized' do
data = subject.memoized_data
expect(subject.memoized_data).to equal(data)
end

it 'should recalculate after GC' do
expect(subject.instance_variables).to_not include(:@memoized_data)
GC.disable
subject.memoized_data && nil
expect(subject.instance_variables).to include(:@memoized_data)

force_gc
expect(subject.instance_variables).to_not include(:@memoized_data)
GC.disable
subject.memoized_data && nil
expect(subject.instance_variables).to include(:@memoized_data)
end

it 'must remove instance variable' do
expect(subject.instance_variables).to_not include(:@memoized_data)
GC.disable
subject.memoized_data && nil
expect(subject.instance_variables).to include(:@memoized_data)

force_gc
expect(subject.instance_variables).to_not include(:@memoized_data)
end

context '#inspect must not raise' do
it 'before calculation' do
expect{subject.inspect}.to_not raise_error
end
it 'after calculation' do
GC.disable
subject.memoized_data && nil
expect{subject.inspect}.to_not raise_error
expect(subject.inspect).to include("Some Costly Operation")
force_gc
end
it 'after GC' do
subject.memoized_data && nil
force_gc
expect(subject.instance_variables).to_not include(:@memoized_data)
expect{subject.inspect}.to_not raise_error
expect(subject.inspect).to_not include("Some Costly Operation")
end
end

if RUBY_PLATFORM == "java"
def force_gc
System.gc
sleep(0.1)
end
else
def force_gc
GC.start
end
end
end

0 comments on commit cdfa468

Please sign in to comment.