ractor-cache 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|