ractor-cache 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ractor-cache.yml +8 -2
- data/.rubocop.yml +18 -0
- data/Gemfile +5 -5
- data/README.md +6 -9
- data/hacker_guide.md +27 -54
- data/lib/ractor/cache.rb +2 -2
- data/lib/ractor/cache/cached_method.rb +92 -0
- data/lib/ractor/cache/caching_layer.rb +49 -25
- data/lib/ractor/cache/store.rb +1 -29
- data/lib/ractor/cache/version.rb +1 -1
- metadata +3 -3
- data/lib/ractor/cache/strategy.rb +0 -121
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ed3b5b4ab4307230b9dab1a21f269ac9d3bae8803f11bb209798df4de02e031
|
4
|
+
data.tar.gz: ed75a15f8035a23955d1e25ce9c88e32a540881a2322ba0267c0c98302d50010
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63984280a9bc9d9530e86cc4d9c69987a4dbcc0128cf3525bf434006bbfc3b00515f5fb1e2aa65c85050cb37691dd666d7b39f1d9d05574078c29713501c8780
|
7
|
+
data.tar.gz: 4e9725ab3449785e3b87a594c13c4175a083cc5910bf72c5fece2bcdcd4da7ce3c4ac4aa222553c6efceb8525a9490173e0f32274fdee13e6c9dfaeb01a0f878
|
@@ -5,13 +5,16 @@ on: [pull_request]
|
|
5
5
|
jobs:
|
6
6
|
tests:
|
7
7
|
name: >-
|
8
|
-
Specs | ${{ matrix.ruby }}
|
8
|
+
Specs ${{ matrix.with_backports }} | ${{ matrix.ruby }}
|
9
9
|
runs-on: ${{ matrix.os }}-latest
|
10
10
|
strategy:
|
11
11
|
fail-fast: false
|
12
12
|
matrix:
|
13
13
|
os: [ ubuntu ]
|
14
|
-
ruby: [ 2.4, 2.5, 2.6, 2.7, head ]
|
14
|
+
ruby: [ 2.4, 2.5, 2.6, 2.7, 3.0, head ]
|
15
|
+
with_backports: [ null ]
|
16
|
+
include:
|
17
|
+
- { os: ubuntu, ruby: 2.7, with_backports: '(with Backports)' }
|
15
18
|
steps:
|
16
19
|
- name: checkout
|
17
20
|
uses: actions/checkout@v2
|
@@ -19,6 +22,9 @@ jobs:
|
|
19
22
|
uses: ruby/setup-ruby@v1
|
20
23
|
with:
|
21
24
|
ruby-version: ${{ matrix.ruby }}
|
25
|
+
- name: setup Env
|
26
|
+
if: matrix.with_backports
|
27
|
+
run: echo "B=true" >> $GITHUB_ENV
|
22
28
|
- name: install dependencies
|
23
29
|
run: bundle install --jobs 3 --retry 3
|
24
30
|
- name: spec
|
data/.rubocop.yml
CHANGED
@@ -3,6 +3,11 @@ AllCops:
|
|
3
3
|
TargetRubyVersion: 2.4
|
4
4
|
NewCops: enable
|
5
5
|
|
6
|
+
# Export
|
7
|
+
|
8
|
+
Lint/EmptyClass:
|
9
|
+
Enabled: false
|
10
|
+
|
6
11
|
# For sure
|
7
12
|
|
8
13
|
Layout/LineLength:
|
@@ -52,3 +57,16 @@ Style/AccessModifierDeclarations:
|
|
52
57
|
|
53
58
|
Style/DocumentDynamicEvalDefinition:
|
54
59
|
Enabled: false
|
60
|
+
|
61
|
+
Layout/EmptyLineAfterMagicComment:
|
62
|
+
Enabled: false # https://github.com/rubocop-hq/rubocop/issues/9327
|
63
|
+
|
64
|
+
Style/MutableConstant:
|
65
|
+
Enabled: false # https://github.com/rubocop-hq/rubocop/issues/9328
|
66
|
+
|
67
|
+
Lint/UselessAssignment:
|
68
|
+
Enabled: false # https://github.com/rubocop-hq/rubocop/issues/9330
|
69
|
+
|
70
|
+
Layout/HashAlignment:
|
71
|
+
Exclude:
|
72
|
+
- 'lib/ractor/cache/cached_method.rb'
|
data/Gemfile
CHANGED
@@ -5,8 +5,8 @@ source 'https://rubygems.org'
|
|
5
5
|
# Specify your gem's dependencies in ractor-cache.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
gem 'backports'
|
9
|
-
gem 'pry-byebug'
|
10
|
-
gem 'rake'
|
11
|
-
gem 'rspec'
|
12
|
-
gem 'rubocop'
|
8
|
+
gem 'backports'
|
9
|
+
gem 'pry-byebug'
|
10
|
+
gem 'rake'
|
11
|
+
gem 'rspec'
|
12
|
+
gem 'rubocop'
|
data/README.md
CHANGED
@@ -23,9 +23,11 @@ end
|
|
23
23
|
## Why?
|
24
24
|
|
25
25
|
0) It's pretty
|
26
|
-
1)
|
26
|
+
1) Handles `nil` / `false` results
|
27
|
+
2) Works even for frozen instances
|
28
|
+
3) Works even for deeply frozen instances (`Ractor`-shareable).
|
27
29
|
|
28
|
-
## `Ractor`-
|
30
|
+
## `Ractor`-shareable?
|
29
31
|
|
30
32
|
Ractor is new in Ruby 3.0 and is awesome.
|
31
33
|
|
@@ -69,14 +71,9 @@ foo.long_calc # => `FrozenError`, @cache is frozen
|
|
69
71
|
|
70
72
|
## How to resolve this
|
71
73
|
|
72
|
-
This gem will
|
73
|
-
1) Use a mutable data structure like above, which means no issue for shallow freezing an instance
|
74
|
-
2) If an instance is deeply frozen, the gem will either insure things will keep working by applying any of the following strategies:
|
75
|
-
- prebuild the cache,
|
76
|
-
- not write to the cache,
|
77
|
-
- (or maybe use a separate Ractor / `SharedHash`)
|
74
|
+
This gem will use associate a mutable data structure to the instance. Even if deeply-frozen it can still mutate the data structure. The data is Ractor-local, so it won't be shared and won't cause issues. Internally a `WeakMap` is used to make sure objects are still garbage collected as they should.
|
78
75
|
|
79
|
-
Implementation details
|
76
|
+
Implementation details [explained here](hacker_guide.md)
|
80
77
|
|
81
78
|
## Contributing
|
82
79
|
|
data/hacker_guide.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
### Structure
|
2
2
|
|
3
|
+
(Code simplified: handling of `nil` / `false` not shown for simplicity)
|
4
|
+
|
3
5
|
```ruby
|
4
6
|
using Ractor::Cache
|
5
7
|
|
@@ -17,7 +19,7 @@ class Mammal < Animal
|
|
17
19
|
end
|
18
20
|
|
19
21
|
class Ape < Mammal
|
20
|
-
cache def something_else
|
22
|
+
cache def something_else
|
21
23
|
return super unless a_predicate?
|
22
24
|
|
23
25
|
# specialized calculation
|
@@ -26,7 +28,7 @@ class Ape < Mammal
|
|
26
28
|
def complex(arg)
|
27
29
|
# ... calculation
|
28
30
|
def
|
29
|
-
cache :complex
|
31
|
+
cache :complex
|
30
32
|
end
|
31
33
|
```
|
32
34
|
|
@@ -34,42 +36,25 @@ Equivalent code:
|
|
34
36
|
```
|
35
37
|
class Animal
|
36
38
|
module RactorCacheLayer
|
37
|
-
CACHED = ... # private information of how things are cached
|
38
|
-
|
39
39
|
# Where caching info is stored for `Animal`
|
40
40
|
class Store
|
41
|
-
def
|
42
|
-
@
|
41
|
+
def cached_something_or_init
|
42
|
+
@something ||= yield
|
43
43
|
end
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
def freeze # only called in case of deep-freezing
|
48
|
-
@owner.class::RactorCacheLayer.deep_freeze_callback(@owner)
|
49
|
-
super
|
50
|
-
end
|
45
|
+
# same for something_else
|
51
46
|
end
|
52
47
|
|
53
48
|
def something
|
54
|
-
ractor_cache.
|
55
|
-
end
|
56
|
-
|
57
|
-
def freeze
|
58
|
-
ractor_cache # make sure cache store is built
|
59
|
-
super
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.deep_freeze_callback(instance)
|
63
|
-
# strategy for `something` is prebuild:
|
64
|
-
instance.something
|
65
|
-
# same for `something_else`:
|
66
|
-
instance.something_else
|
49
|
+
ractor_cache.cached_something_or_init { super }
|
67
50
|
end
|
68
51
|
|
69
52
|
private
|
70
53
|
|
71
54
|
def ractor_cache
|
55
|
+
# similar to:
|
72
56
|
@ractor_cache ||= self.class::Store.new
|
57
|
+
# but actually using a ractor-local WeakMap instead of an instance variable
|
73
58
|
end
|
74
59
|
end
|
75
60
|
prepend CacheLayer
|
@@ -85,38 +70,23 @@ end
|
|
85
70
|
|
86
71
|
class Ape < Mammal
|
87
72
|
module RactorCacheLayer
|
88
|
-
CACHED = ... # private array of Strategy
|
89
|
-
|
90
73
|
# Where caching info is stored for `Ape`
|
91
74
|
class Store < Animal::CacheLayer::Store
|
92
|
-
|
93
|
-
|
94
|
-
def initialize(owner)
|
95
|
-
@complex = {}
|
96
|
-
super
|
75
|
+
def initilialize
|
76
|
+
@complex_fetch = {}
|
97
77
|
end
|
98
|
-
end
|
99
78
|
|
100
|
-
|
101
|
-
|
79
|
+
def cached_complex_or_init(owner)
|
80
|
+
@complex_fetch[owner] ||= yield
|
81
|
+
end
|
102
82
|
end
|
103
83
|
|
104
84
|
def complex(arg)
|
105
|
-
|
106
|
-
hash.fetch(arg) do
|
107
|
-
result = super
|
108
|
-
# strategy is disable:
|
109
|
-
hash[arg] = result unless frozen?
|
110
|
-
result
|
111
|
-
end
|
85
|
+
ractor_cache.cached_complex_or_init(arg) { super }
|
112
86
|
end
|
113
87
|
|
114
|
-
def
|
115
|
-
|
116
|
-
# nothing to do
|
117
|
-
# process any other cached data
|
118
|
-
# and then
|
119
|
-
super
|
88
|
+
def something_else # refine again
|
89
|
+
ractor_cache.cached_something_else_or_init { super }
|
120
90
|
end
|
121
91
|
end
|
122
92
|
prepend CacheLayer
|
@@ -134,10 +104,13 @@ Ape.ancestors # =>
|
|
134
104
|
Animal,
|
135
105
|
Object, #...
|
136
106
|
]
|
137
|
-
```
|
138
|
-
|
139
|
-
### Class ancestors
|
140
107
|
|
141
|
-
|
142
|
-
|
143
|
-
|
108
|
+
m = Ape.instance_method(:something_else)
|
109
|
+
# => #<UnboundMethod: Ape::CacheLayer#something_else()>
|
110
|
+
m = m.super_method
|
111
|
+
# => #<UnboundMethod: Ape#something_else()>
|
112
|
+
m = m.super_method
|
113
|
+
# => #<UnboundMethod: Animal::CacheLayer#something_else()>
|
114
|
+
m = m.super_method
|
115
|
+
# => #<UnboundMethod: Animal#something_else()>
|
116
|
+
```
|
data/lib/ractor/cache.rb
CHANGED
@@ -7,8 +7,8 @@ class Ractor
|
|
7
7
|
module Cache
|
8
8
|
require_relative_dir
|
9
9
|
|
10
|
-
private def cache(method_name
|
11
|
-
CachingLayer[self].cache(method_name
|
10
|
+
private def cache(method_name)
|
11
|
+
CachingLayer[self].cache(method_name)
|
12
12
|
end
|
13
13
|
|
14
14
|
refine Module do
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# shareable_constant_values: literal
|
3
|
+
|
4
|
+
class Ractor
|
5
|
+
module Cache
|
6
|
+
module CachedMethod
|
7
|
+
Base = Struct.new(:method_name, :argument_kind)
|
8
|
+
class Base
|
9
|
+
def compile_method
|
10
|
+
<<~RUBY
|
11
|
+
def #{method_name}#{args}
|
12
|
+
ractor_cache.cached_#{method_name}_or_init#{args} { super }
|
13
|
+
end
|
14
|
+
RUBY
|
15
|
+
end
|
16
|
+
|
17
|
+
SIGNATURES = {
|
18
|
+
none: [''],
|
19
|
+
positional: ['(*args)', 'args'],
|
20
|
+
keyword: ['(**opt)', 'opt'],
|
21
|
+
both: ['(*args, **opt)', '[args, opt]'],
|
22
|
+
}
|
23
|
+
private_constant :SIGNATURES
|
24
|
+
|
25
|
+
private def args
|
26
|
+
SIGNATURES.fetch(argument_kind).first
|
27
|
+
end
|
28
|
+
|
29
|
+
private def key
|
30
|
+
SIGNATURES.fetch(argument_kind).fetch(1)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class WithoutArgument < Base
|
35
|
+
def compile_initializer
|
36
|
+
''
|
37
|
+
end
|
38
|
+
|
39
|
+
def compile_accessor
|
40
|
+
<<~RUBY
|
41
|
+
def cached_#{method_name}_or_init
|
42
|
+
return @#{method_name} if defined?(@#{method_name})
|
43
|
+
|
44
|
+
@#{method_name} = yield
|
45
|
+
end
|
46
|
+
RUBY
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class WithArguments < Base
|
51
|
+
def compile_initializer
|
52
|
+
"@#{method_name} = {}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def compile_accessor
|
56
|
+
<<~RUBY
|
57
|
+
def cached_#{method_name}_or_init#{args}
|
58
|
+
@#{method_name}.fetch(#{key}) do
|
59
|
+
@#{method_name}[#{key}] = yield
|
60
|
+
end
|
61
|
+
end
|
62
|
+
RUBY
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class << self
|
67
|
+
def new(instance_method)
|
68
|
+
kind = argument_kind(instance_method.parameters)
|
69
|
+
klass = kind == :none ? WithoutArgument : WithArguments
|
70
|
+
klass.new(instance_method.name, kind)
|
71
|
+
end
|
72
|
+
|
73
|
+
private def argument_kind(parameters) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
74
|
+
kind = :none
|
75
|
+
parameters.each do |type, name|
|
76
|
+
case type
|
77
|
+
when :req, :opt, :rest
|
78
|
+
return :both if type == :rest && name == :*
|
79
|
+
|
80
|
+
kind = :positional
|
81
|
+
when :key, :keyrest
|
82
|
+
return kind == :positional ? :both : :keyword
|
83
|
+
when :nokey, :block
|
84
|
+
# ignore
|
85
|
+
end
|
86
|
+
end
|
87
|
+
kind
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -3,18 +3,57 @@
|
|
3
3
|
class Ractor
|
4
4
|
module Cache
|
5
5
|
module CachingLayer
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
use_ractor_storage = defined?(Ractor.current) && # check we are not running Backports on a Ruby that is compatible:
|
7
|
+
begin
|
8
|
+
(::ObjectSpace::WeakMap.new[Object.new.freeze] = []) # Ruby 2.6- didn't work with frozen keys
|
9
|
+
rescue FrozenError
|
10
|
+
false
|
11
|
+
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
+
if use_ractor_storage
|
14
|
+
private def ractor_cache
|
15
|
+
CachingLayer.ractor_storage[self] ||= self.class::Store.new
|
16
|
+
end
|
17
|
+
else
|
18
|
+
def freeze
|
19
|
+
ractor_cache
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
private def ractor_cache
|
24
|
+
@ractor_cache ||= self.class::Store.new
|
25
|
+
end
|
13
26
|
end
|
14
27
|
|
15
28
|
class << self
|
29
|
+
def ractor_storage
|
30
|
+
Ractor.current[:RactorCacheLocalStore] ||= ::ObjectSpace::WeakMap.new
|
31
|
+
end
|
32
|
+
|
16
33
|
attr_reader :sublayer, :cached, :parent
|
17
34
|
|
35
|
+
def cache(method_name)
|
36
|
+
cm = cached_method(method_name)
|
37
|
+
|
38
|
+
file, line = cm.method(:compile_method).source_location
|
39
|
+
module_eval(cm.compile_method, file, line + 2)
|
40
|
+
@cached << cm
|
41
|
+
update_store_methods(self::Store)
|
42
|
+
end
|
43
|
+
|
44
|
+
private def update_store_methods(store)
|
45
|
+
init = @cached.map(&:compile_initializer).join("\n")
|
46
|
+
|
47
|
+
store.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
48
|
+
def initialize
|
49
|
+
#{init}
|
50
|
+
end
|
51
|
+
|
52
|
+
#{@cached.last.compile_accessor}
|
53
|
+
RUBY
|
54
|
+
end
|
55
|
+
|
56
|
+
# @api private
|
18
57
|
def attach(mod, sublayer)
|
19
58
|
@sublayer = sublayer
|
20
59
|
@cached = []
|
@@ -26,29 +65,14 @@ class Ractor
|
|
26
65
|
self
|
27
66
|
end
|
28
67
|
|
29
|
-
|
30
|
-
|
31
|
-
file, line = strat.method(:compile_accessor).source_location
|
32
|
-
module_eval(strat.compile_accessor, file, line + 2)
|
33
|
-
@cached << strat
|
34
|
-
self::Store.update(@cached)
|
35
|
-
end
|
36
|
-
|
37
|
-
def deep_freeze_callback(instance)
|
38
|
-
@cached.each do |strategy|
|
39
|
-
strategy.deep_freeze_callback(instance)
|
40
|
-
end
|
41
|
-
sublayer&.deep_freeze_callback(instance)
|
42
|
-
end
|
43
|
-
|
44
|
-
# @returns [Strategy]
|
45
|
-
private def build_stragegy(method, strategy)
|
68
|
+
# @returns [CachedMethod]
|
69
|
+
private def cached_method(method_name)
|
46
70
|
im = begin
|
47
|
-
@parent.instance_method(
|
71
|
+
@parent.instance_method(method_name)
|
48
72
|
rescue ::NameError => e
|
49
73
|
raise e, "#{e.message}. Method must be defined before calling `cache`", e.backtrace
|
50
74
|
end
|
51
|
-
|
75
|
+
CachedMethod.new(im)
|
52
76
|
end
|
53
77
|
|
54
78
|
# Return `CachingLayer` in `mod`, creating it if need be.
|
data/lib/ractor/cache/store.rb
CHANGED
@@ -3,35 +3,7 @@
|
|
3
3
|
class Ractor
|
4
4
|
module Cache
|
5
5
|
class Store
|
6
|
-
|
7
|
-
@owner = owner
|
8
|
-
end
|
9
|
-
|
10
|
-
def freeze
|
11
|
-
@owner.class::RactorCacheLayer.deep_freeze_callback(@owner)
|
12
|
-
super
|
13
|
-
end
|
14
|
-
|
15
|
-
class << self
|
16
|
-
def update(cached)
|
17
|
-
update_accessors(cached)
|
18
|
-
update_init(cached)
|
19
|
-
end
|
20
|
-
|
21
|
-
private def update_accessors(cached)
|
22
|
-
attr_accessor(*cached.map(&:method_name))
|
23
|
-
end
|
24
|
-
|
25
|
-
private def update_init(cached)
|
26
|
-
body = cached.map(&:compile_store_init).join("\n")
|
27
|
-
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
28
|
-
def initialize(owner)
|
29
|
-
#{body}
|
30
|
-
super
|
31
|
-
end
|
32
|
-
RUBY
|
33
|
-
end
|
34
|
-
end
|
6
|
+
# No base methods. All actual methods defined by `CachingLayer`
|
35
7
|
end
|
36
8
|
end
|
37
9
|
end
|
data/lib/ractor/cache/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ractor-cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marc-Andre Lafortune
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: require_relative_dir
|
@@ -44,9 +44,9 @@ files:
|
|
44
44
|
- bin/setup
|
45
45
|
- hacker_guide.md
|
46
46
|
- lib/ractor/cache.rb
|
47
|
+
- lib/ractor/cache/cached_method.rb
|
47
48
|
- lib/ractor/cache/caching_layer.rb
|
48
49
|
- lib/ractor/cache/store.rb
|
49
|
-
- lib/ractor/cache/strategy.rb
|
50
50
|
- lib/ractor/cache/version.rb
|
51
51
|
- ractor-cache.gemspec
|
52
52
|
homepage: https://github.com/marcandre/ractor-cache
|
@@ -1,121 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Ractor
|
4
|
-
module Cache
|
5
|
-
EMPTY_CACHE = Class.new.freeze # lazy way to get a name
|
6
|
-
|
7
|
-
module Strategy
|
8
|
-
class Base
|
9
|
-
attr_reader :parameters, :method_name
|
10
|
-
|
11
|
-
def initialize(instance_method)
|
12
|
-
@method_name = instance_method.name
|
13
|
-
analyse_parameters(instance_method.parameters)
|
14
|
-
end
|
15
|
-
|
16
|
-
def compile_store_init
|
17
|
-
init = @has_arguments ? "Hash.new { #{EMPTY_CACHE} }" : EMPTY_CACHE
|
18
|
-
"@#{method_name} = #{init}"
|
19
|
-
end
|
20
|
-
|
21
|
-
private def analyse_parameters(parameters) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
22
|
-
@has_positional_arguments = @has_keywords_arguments = false
|
23
|
-
parameters.each do |type, name|
|
24
|
-
case type
|
25
|
-
when :req, :opt, :rest
|
26
|
-
@has_positional_arguments = true
|
27
|
-
@has_keywords_arguments = true if type == :rest && name == :*
|
28
|
-
when :key, :keyrest
|
29
|
-
@has_keywords_arguments = true
|
30
|
-
when :nokey, :block
|
31
|
-
# ignore
|
32
|
-
end
|
33
|
-
@has_arguments = @has_positional_arguments || @has_keywords_arguments
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
private def compile_lookup
|
38
|
-
args = [
|
39
|
-
*('args' if @has_positional_arguments),
|
40
|
-
*('opts' if @has_keywords_arguments),
|
41
|
-
]
|
42
|
-
|
43
|
-
return if args.empty?
|
44
|
-
|
45
|
-
"[#{args.join(', ')}]"
|
46
|
-
end
|
47
|
-
|
48
|
-
private def signature
|
49
|
-
[
|
50
|
-
*('*args' if @has_positional_arguments),
|
51
|
-
*('**opts' if @has_keywords_arguments),
|
52
|
-
].join(', ')
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
class Prebuild < Base
|
57
|
-
def initialize(*)
|
58
|
-
super
|
59
|
-
raise ArgumentError, "Can not cache method #{method_name} by prebuilding because it accepts arguments" if @has_arguments
|
60
|
-
end
|
61
|
-
|
62
|
-
def deep_freeze_callback(instance)
|
63
|
-
instance.__send__ method_name
|
64
|
-
end
|
65
|
-
|
66
|
-
def compile_accessor
|
67
|
-
<<~RUBY
|
68
|
-
def #{method_name}(#{signature})
|
69
|
-
r = ractor_cache.#{method_name}
|
70
|
-
return r unless r == EMPTY_CACHE
|
71
|
-
|
72
|
-
ractor_cache.#{method_name} = super
|
73
|
-
end
|
74
|
-
RUBY
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
class Disable < Base
|
79
|
-
def compile_accessor
|
80
|
-
<<~RUBY
|
81
|
-
def #{method_name}(#{signature})
|
82
|
-
r = ractor_cache.#{method_name}#{compile_lookup}
|
83
|
-
return r unless r == EMPTY_CACHE
|
84
|
-
|
85
|
-
r = super
|
86
|
-
ractor_cache.#{method_name}#{compile_lookup} = r unless ractor_cache.frozen?
|
87
|
-
r
|
88
|
-
end
|
89
|
-
RUBY
|
90
|
-
end
|
91
|
-
|
92
|
-
def deep_freeze_callback(instance)
|
93
|
-
# nothing to do
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
MAP = {
|
98
|
-
prebuild: Prebuild,
|
99
|
-
disable: Disable,
|
100
|
-
}.freeze
|
101
|
-
private_constant :MAP
|
102
|
-
|
103
|
-
class << self
|
104
|
-
def [](kind)
|
105
|
-
MAP.fetch(kind)
|
106
|
-
end
|
107
|
-
|
108
|
-
def new(
|
109
|
-
strategy = nil, # => (:prebuild | :disable)?
|
110
|
-
to_cache: # => UnboundMethod
|
111
|
-
) # => Strategy
|
112
|
-
self[strategy || :prebuild].new(to_cache)
|
113
|
-
rescue ArgumentError
|
114
|
-
return new(:disable, to_cache: to_cache) if strategy == nil
|
115
|
-
|
116
|
-
raise
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|