-
Notifications
You must be signed in to change notification settings - Fork 98
/
cache.rb
160 lines (147 loc) · 4.35 KB
/
cache.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
module RDF; module Util
##
# A `Hash`-like cache that holds only weak references to the values it
# caches, meaning that values contained in the cache can be garbage
# collected. This allows the cache to dynamically adjust to changing
# memory conditions, caching more objects when memory is plentiful, but
# evicting most objects if memory pressure increases to the point of
# scarcity.
#
# While this cache is something of an internal implementation detail of
# RDF.rb, some external libraries do currently make use of it as well,
# including [SPARQL](https://github.com/ruby-rdf/sparql/) and
# [Spira](https://github.com/ruby-rdf/spira). Do be sure to include any changes
# here in the RDF.rb changelog.
#
# @see RDF::URI.intern
# @see http://en.wikipedia.org/wiki/Weak_reference
# @since 0.2.0
class Cache
# The configured cache capacity.
attr_reader :capacity
##
# @private
def self.new(*args)
# JRuby doesn't support `ObjectSpace#_id2ref` unless the `-X+O`
# startup option is given. In addition, ObjectSpaceCache is very slow
# on Rubinius. On those platforms we'll default to using
# the WeakRef-based cache:
if RUBY_PLATFORM == 'java' || (defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx')
klass = WeakRefCache
else
klass = ObjectSpaceCache
end
cache = klass.allocate
cache.send(:initialize, *args)
cache
end
##
# @param [Integer] capacity
def initialize(capacity = nil)
@capacity = capacity || RDF.config.cache_size
@cache ||= {}
@index ||= {}
end
##
# @return [Integer]
def size
@cache.size
end
##
# @return [Boolean]
def capacity?
@capacity.equal?(-1) || @capacity > @cache.size
end
alias_method :has_capacity?, :capacity?
##
# This implementation relies on `ObjectSpace#_id2ref` and performs
# optimally on Ruby >= 2.x; however, it does not work on JRuby
# by default since much `ObjectSpace` functionality on that platform is
# disabled unless the `-X+O` startup option is given.
#
# @see http://ruby-doc.org/core-2.2.2/ObjectSpace.html
# @see http://ruby-doc.org/stdlib-2.2.0/libdoc/weakref/rdoc/WeakRef.html
class ObjectSpaceCache < Cache
##
# @param [Object] key
# @return [Object]
def [](key)
if value_id = @cache[key]
ObjectSpace._id2ref(value_id) rescue nil
end
end
##
# @param [Object] key
# @param [Object] value
# @return [Object]
def []=(key, value)
if capacity?
id = value.__id__
@cache[key] = id
@index[id] = key
ObjectSpace.define_finalizer(value, finalizer_proc)
end
value
end
##
# Remove cache entry for key
#
# @param [Object] key
# @return [Object] the previously referenced object
def delete(key)
id = @cache[key]
@cache.delete(key)
@index.delete(id) if id
end
private
def finalizer_proc
proc { |id| @cache.delete(@index.delete(id)) }
end
end # ObjectSpaceCache
##
# This implementation uses the `WeakRef` class from Ruby's standard
# library, and provides adequate performance on JRuby and on Ruby 2.x.
#
# @see http://ruby-doc.org/stdlib-2.2.0/libdoc/weakref/rdoc/WeakRef.html
class WeakRefCache < Cache
##
# @param [Integer] capacity
def initialize(capacity = nil)
require 'weakref' unless defined?(::WeakRef)
super
end
##
# @param [Object] key
# @return [Object]
def [](key)
if (ref = @cache[key])
if ref.weakref_alive?
value = ref.__getobj__ rescue nil
else
@cache.delete(key)
nil
end
end
end
##
# @param [Object] key
# @param [Object] value
# @return [Object]
def []=(key, value)
if capacity?
@cache[key] = WeakRef.new(value)
end
value
end
##
# Remove cache entry for key
#
# @param [Object] key
# @return [Object] the previously referenced object
def delete(key)
ref = @cache.delete(key)
ref.__getobj__ rescue nil
end
end # WeakRefCache
end # Cache
end; end # RDF::Util