makara 0.3.9 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +11 -0
- data/.github/workflows/CI.yml +88 -0
- data/.github/workflows/gem-publish-public.yml +36 -0
- data/.rspec +1 -1
- data/.rubocop.yml +15 -0
- data/.rubocop_todo.yml +670 -0
- data/CHANGELOG.md +88 -32
- data/Gemfile +1 -16
- data/README.md +39 -35
- data/Rakefile +1 -1
- data/gemfiles/activerecord_5.2.gemfile +8 -0
- data/gemfiles/activerecord_6.0.gemfile +8 -0
- data/gemfiles/activerecord_6.1.gemfile +8 -0
- data/gemfiles/activerecord_head.gemfile +6 -0
- data/lib/active_record/connection_adapters/jdbcmysql_makara_adapter.rb +4 -18
- data/lib/active_record/connection_adapters/jdbcpostgresql_makara_adapter.rb +4 -18
- data/lib/active_record/connection_adapters/makara_abstract_adapter.rb +111 -33
- data/lib/active_record/connection_adapters/makara_jdbcmysql_adapter.rb +4 -18
- data/lib/active_record/connection_adapters/makara_jdbcpostgresql_adapter.rb +4 -18
- data/lib/active_record/connection_adapters/makara_mysql2_adapter.rb +4 -20
- data/lib/active_record/connection_adapters/makara_postgis_adapter.rb +4 -19
- data/lib/active_record/connection_adapters/makara_postgresql_adapter.rb +4 -20
- data/lib/active_record/connection_adapters/mysql2_makara_adapter.rb +4 -20
- data/lib/active_record/connection_adapters/postgresql_makara_adapter.rb +4 -20
- data/lib/makara.rb +14 -5
- data/lib/makara/cache.rb +4 -42
- data/lib/makara/config_parser.rb +18 -16
- data/lib/makara/connection_wrapper.rb +43 -22
- data/lib/makara/context.rb +108 -37
- data/lib/makara/cookie.rb +53 -0
- data/lib/makara/error_handler.rb +2 -11
- data/lib/makara/errors/all_connections_blacklisted.rb +0 -2
- data/lib/makara/errors/blacklist_connection.rb +0 -2
- data/lib/makara/errors/blacklisted_while_in_transaction.rb +12 -0
- data/lib/makara/errors/invalid_shard.rb +14 -0
- data/lib/makara/errors/makara_error.rb +0 -1
- data/lib/makara/errors/no_connections_available.rb +0 -2
- data/lib/makara/logging/logger.rb +1 -5
- data/lib/makara/logging/subscriber.rb +0 -2
- data/lib/makara/middleware.rb +12 -76
- data/lib/makara/pool.rb +55 -47
- data/lib/makara/proxy.rb +76 -56
- data/lib/makara/railtie.rb +0 -8
- data/lib/makara/strategies/abstract.rb +1 -0
- data/lib/makara/strategies/priority_failover.rb +2 -0
- data/lib/makara/strategies/round_robin.rb +7 -3
- data/lib/makara/strategies/shard_aware.rb +45 -0
- data/lib/makara/version.rb +2 -4
- data/makara.gemspec +26 -3
- data/spec/active_record/connection_adapters/makara_abstract_adapter_error_handling_spec.rb +1 -6
- data/spec/active_record/connection_adapters/makara_abstract_adapter_spec.rb +10 -14
- data/spec/active_record/connection_adapters/makara_mysql2_adapter_spec.rb +18 -16
- data/spec/active_record/connection_adapters/makara_postgis_adapter_spec.rb +6 -9
- data/spec/active_record/connection_adapters/makara_postgresql_adapter_spec.rb +70 -10
- data/spec/cache_spec.rb +2 -53
- data/spec/config_parser_spec.rb +75 -57
- data/spec/connection_wrapper_spec.rb +6 -4
- data/spec/context_spec.rb +163 -100
- data/spec/cookie_spec.rb +72 -0
- data/spec/middleware_spec.rb +27 -56
- data/spec/pool_spec.rb +25 -14
- data/spec/proxy_spec.rb +50 -39
- data/spec/spec_helper.rb +10 -10
- data/spec/strategies/priority_failover_spec.rb +3 -4
- data/spec/strategies/round_robin_spec.rb +4 -8
- data/spec/strategies/shard_aware_spec.rb +218 -0
- data/spec/support/deep_dup.rb +1 -1
- data/spec/support/helpers.rb +10 -6
- data/spec/support/mock_objects.rb +6 -5
- data/spec/support/mysql2_database.yml +3 -2
- data/spec/support/mysql2_database_with_custom_errors.yml +6 -1
- data/spec/support/pool_extensions.rb +0 -3
- data/spec/support/postgis_database.yml +2 -0
- data/spec/support/postgis_schema.rb +6 -3
- data/spec/support/postgresql_database.yml +2 -2
- data/spec/support/proxy_extensions.rb +1 -3
- data/spec/support/schema.rb +6 -6
- data/spec/support/user.rb +4 -0
- metadata +170 -22
- data/.travis.yml +0 -70
- data/gemfiles/ar-head.gemfile +0 -15
- data/gemfiles/ar30.gemfile +0 -32
- data/gemfiles/ar31.gemfile +0 -31
- data/gemfiles/ar32.gemfile +0 -31
- data/gemfiles/ar40.gemfile +0 -17
- data/gemfiles/ar41.gemfile +0 -17
- data/gemfiles/ar42.gemfile +0 -17
- data/gemfiles/ar50.gemfile +0 -15
- data/gemfiles/ar51.gemfile +0 -15
- data/lib/makara/cache/memory_store.rb +0 -28
- data/lib/makara/cache/noop_store.rb +0 -15
@@ -1,32 +1,17 @@
|
|
1
1
|
require 'active_record/connection_adapters/makara_abstract_adapter'
|
2
2
|
require 'active_record/connection_adapters/mysql2_adapter'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def mysql2_makara_connection(config)
|
9
|
-
ActiveRecord::ConnectionAdapters::MakaraMysql2Adapter.new(config)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
else
|
15
|
-
|
16
|
-
module ActiveRecord
|
17
|
-
class Base
|
18
|
-
def self.mysql2_makara_connection(config)
|
19
|
-
ActiveRecord::ConnectionAdapters::MakaraMysql2Adapter.new(config)
|
20
|
-
end
|
4
|
+
module ActiveRecord
|
5
|
+
module ConnectionHandling
|
6
|
+
def mysql2_makara_connection(config)
|
7
|
+
ActiveRecord::ConnectionAdapters::MakaraMysql2Adapter.new(config)
|
21
8
|
end
|
22
9
|
end
|
23
|
-
|
24
10
|
end
|
25
11
|
|
26
12
|
module ActiveRecord
|
27
13
|
module ConnectionAdapters
|
28
14
|
class MakaraMysql2Adapter < ActiveRecord::ConnectionAdapters::MakaraAbstractAdapter
|
29
|
-
|
30
15
|
class << self
|
31
16
|
def visitor_for(*args)
|
32
17
|
ActiveRecord::ConnectionAdapters::Mysql2Adapter.visitor_for(*args)
|
@@ -38,7 +23,6 @@ module ActiveRecord
|
|
38
23
|
def active_record_connection_for(config)
|
39
24
|
::ActiveRecord::Base.mysql2_connection(config)
|
40
25
|
end
|
41
|
-
|
42
26
|
end
|
43
27
|
end
|
44
28
|
end
|
@@ -1,32 +1,17 @@
|
|
1
1
|
require 'active_record/connection_adapters/makara_abstract_adapter'
|
2
2
|
require 'active_record/connection_adapters/postgresql_adapter'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def postgresql_makara_connection(config)
|
9
|
-
ActiveRecord::ConnectionAdapters::MakaraPostgreSQLAdapter.new(config)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
else
|
15
|
-
|
16
|
-
module ActiveRecord
|
17
|
-
class Base
|
18
|
-
def self.postgresql_makara_connection(config)
|
19
|
-
ActiveRecord::ConnectionAdapters::MakaraPostgreSQLAdapter.new(config)
|
20
|
-
end
|
4
|
+
module ActiveRecord
|
5
|
+
module ConnectionHandling
|
6
|
+
def postgresql_makara_connection(config)
|
7
|
+
ActiveRecord::ConnectionAdapters::MakaraPostgreSQLAdapter.new(config)
|
21
8
|
end
|
22
9
|
end
|
23
|
-
|
24
10
|
end
|
25
11
|
|
26
12
|
module ActiveRecord
|
27
13
|
module ConnectionAdapters
|
28
14
|
class MakaraPostgreSQLAdapter < ActiveRecord::ConnectionAdapters::MakaraAbstractAdapter
|
29
|
-
|
30
15
|
class << self
|
31
16
|
def visitor_for(*args)
|
32
17
|
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.visitor_for(*args)
|
@@ -38,7 +23,6 @@ module ActiveRecord
|
|
38
23
|
def active_record_connection_for(config)
|
39
24
|
::ActiveRecord::Base.postgresql_connection(config)
|
40
25
|
end
|
41
|
-
|
42
26
|
end
|
43
27
|
end
|
44
28
|
end
|
data/lib/makara.rb
CHANGED
@@ -1,21 +1,24 @@
|
|
1
|
+
require 'active_support'
|
1
2
|
require 'makara/version'
|
2
3
|
require 'makara/railtie' if defined?(Rails)
|
3
4
|
module Makara
|
4
|
-
|
5
5
|
autoload :Cache, 'makara/cache'
|
6
6
|
autoload :ConfigParser, 'makara/config_parser'
|
7
7
|
autoload :ConnectionWrapper, 'makara/connection_wrapper'
|
8
8
|
autoload :Context, 'makara/context'
|
9
|
+
autoload :Cookie, 'makara/cookie'
|
9
10
|
autoload :ErrorHandler, 'makara/error_handler'
|
10
11
|
autoload :Middleware, 'makara/middleware'
|
11
12
|
autoload :Pool, 'makara/pool'
|
12
13
|
autoload :Proxy, 'makara/proxy'
|
13
14
|
|
14
15
|
module Errors
|
15
|
-
autoload :MakaraError,
|
16
|
-
autoload :AllConnectionsBlacklisted,
|
17
|
-
autoload :BlacklistConnection,
|
18
|
-
autoload :NoConnectionsAvailable,
|
16
|
+
autoload :MakaraError, 'makara/errors/makara_error'
|
17
|
+
autoload :AllConnectionsBlacklisted, 'makara/errors/all_connections_blacklisted'
|
18
|
+
autoload :BlacklistConnection, 'makara/errors/blacklist_connection'
|
19
|
+
autoload :NoConnectionsAvailable, 'makara/errors/no_connections_available'
|
20
|
+
autoload :BlacklistedWhileInTransaction, 'makara/errors/blacklisted_while_in_transaction'
|
21
|
+
autoload :InvalidShard, 'makara/errors/invalid_shard'
|
19
22
|
end
|
20
23
|
|
21
24
|
module Logging
|
@@ -27,6 +30,12 @@ module Makara
|
|
27
30
|
autoload :Abstract, 'makara/strategies/abstract'
|
28
31
|
autoload :RoundRobin, 'makara/strategies/round_robin'
|
29
32
|
autoload :PriorityFailover, 'makara/strategies/priority_failover'
|
33
|
+
autoload :ShardAware, 'makara/strategies/shard_aware'
|
30
34
|
end
|
35
|
+
end
|
31
36
|
|
37
|
+
ActiveSupport.on_load(:active_record) do
|
38
|
+
ActiveRecord::LogSubscriber.log_subscribers.each do |subscriber|
|
39
|
+
subscriber.extend ::Makara::Logging::Subscriber
|
40
|
+
end
|
32
41
|
end
|
data/lib/makara/cache.rb
CHANGED
@@ -1,53 +1,15 @@
|
|
1
|
-
require 'active_support/core_ext/object/try'
|
2
|
-
|
3
|
-
# The Makara Cache should have access to your centralized cache store.
|
4
|
-
# It serves the purpose of storing the Makara::Context across requests, servers, etc.
|
5
|
-
|
6
1
|
module Makara
|
7
2
|
module Cache
|
8
|
-
|
9
|
-
autoload :MemoryStore, 'makara/cache/memory_store'
|
10
|
-
autoload :NoopStore, 'makara/cache/noop_store'
|
11
|
-
|
12
3
|
class << self
|
13
|
-
|
14
4
|
def store=(store)
|
15
|
-
|
5
|
+
Makara::Logging::Logger.log deprecation_warning, :warn
|
16
6
|
end
|
17
7
|
|
18
|
-
|
19
|
-
store.try(:read, key)
|
20
|
-
end
|
8
|
+
private
|
21
9
|
|
22
|
-
def
|
23
|
-
|
10
|
+
def deprecation_warning
|
11
|
+
"Makara's context is no longer persisted in a backend cache, a cookie store is used by default.\nSetting the Makara::Cache.store won't have any effects."
|
24
12
|
end
|
25
|
-
|
26
|
-
protected
|
27
|
-
|
28
|
-
def store
|
29
|
-
case @store
|
30
|
-
when :noop, :null
|
31
|
-
@store = Makara::Cache::NoopStore.new
|
32
|
-
when :memory
|
33
|
-
@store = Makara::Cache::MemoryStore.new
|
34
|
-
else
|
35
|
-
if defined?(Rails)
|
36
|
-
|
37
|
-
# in AR3 RAILS_CACHE may not be loaded if the full env is not present
|
38
|
-
# Rails.cache will throw an error because of it.
|
39
|
-
if ActiveRecord::VERSION::MAJOR < 4
|
40
|
-
@store ||= Rails.cache if defined?(RAILS_CACHE)
|
41
|
-
else
|
42
|
-
@store ||= Rails.cache if defined?(Rails)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
@store
|
48
|
-
end
|
49
|
-
|
50
13
|
end
|
51
|
-
|
52
14
|
end
|
53
15
|
end
|
data/lib/makara/config_parser.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'digest/md5'
|
2
2
|
require 'active_support/core_ext/hash/keys'
|
3
3
|
require 'active_support/core_ext/hash/except'
|
4
|
+
require 'cgi'
|
4
5
|
|
5
6
|
# Convenience methods to grab subconfigs out of the primary configuration.
|
6
7
|
# Provides a way to generate a consistent ID based on a unique config.
|
@@ -19,11 +20,10 @@ require 'active_support/core_ext/hash/except'
|
|
19
20
|
|
20
21
|
module Makara
|
21
22
|
class ConfigParser
|
22
|
-
|
23
23
|
DEFAULTS = {
|
24
|
-
:
|
25
|
-
:
|
26
|
-
:
|
24
|
+
master_ttl: 5,
|
25
|
+
blacklist_duration: 30,
|
26
|
+
sticky: true
|
27
27
|
}
|
28
28
|
|
29
29
|
# ConnectionUrlResolver is borrowed from Rails 4-2 since its location and implementation
|
@@ -32,7 +32,6 @@ module Makara
|
|
32
32
|
#
|
33
33
|
# Expands a connection string into a hash.
|
34
34
|
class ConnectionUrlResolver # :nodoc:
|
35
|
-
|
36
35
|
# == Example
|
37
36
|
#
|
38
37
|
# url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
|
@@ -49,6 +48,7 @@ module Makara
|
|
49
48
|
# }
|
50
49
|
def initialize(url)
|
51
50
|
raise "Database URL cannot be empty" if url.blank?
|
51
|
+
|
52
52
|
@uri = URI.parse(url)
|
53
53
|
@adapter = @uri.scheme.tr('-', '_')
|
54
54
|
@adapter = "postgresql" if @adapter == "postgres"
|
@@ -63,7 +63,7 @@ module Makara
|
|
63
63
|
# Converts the given URL to a full connection hash.
|
64
64
|
def to_hash
|
65
65
|
config = raw_config.reject { |_,value| value.blank? }
|
66
|
-
config.map { |key,value| config[key] =
|
66
|
+
config.map { |key,value| config[key] = CGI.unescape(value) if value.is_a? String }
|
67
67
|
config
|
68
68
|
end
|
69
69
|
|
@@ -127,9 +127,10 @@ module Makara
|
|
127
127
|
# NOTE: Does not use ENV['DATABASE_URL']
|
128
128
|
def self.merge_and_resolve_default_url_config(config)
|
129
129
|
if ENV['DATABASE_URL']
|
130
|
-
Logging::Logger.log "Please rename DATABASE_URL to use in the database.yml", :warn
|
130
|
+
Makara::Logging::Logger.log "Please rename DATABASE_URL to use in the database.yml", :warn
|
131
131
|
end
|
132
132
|
return config unless config.key?(:url)
|
133
|
+
|
133
134
|
url = config[:url]
|
134
135
|
url_config = ConnectionUrlResolver.new(url).to_hash
|
135
136
|
url_config = url_config.symbolize_keys
|
@@ -144,10 +145,9 @@ module Makara
|
|
144
145
|
@config = config.symbolize_keys
|
145
146
|
@makara_config = DEFAULTS.merge(@config[:makara] || {})
|
146
147
|
@makara_config = @makara_config.symbolize_keys
|
147
|
-
@id = @makara_config[:id]
|
148
|
+
@id = sanitize_id(@makara_config[:id])
|
148
149
|
end
|
149
150
|
|
150
|
-
|
151
151
|
def id
|
152
152
|
@id ||= begin
|
153
153
|
sorted = recursive_sort(@config)
|
@@ -155,24 +155,20 @@ module Makara
|
|
155
155
|
end
|
156
156
|
end
|
157
157
|
|
158
|
-
|
159
158
|
def master_configs
|
160
159
|
all_configs
|
161
160
|
.select { |config| config[:role] == 'master' }
|
162
161
|
.map { |config| config.except(:role) }
|
163
162
|
end
|
164
163
|
|
165
|
-
|
166
164
|
def slave_configs
|
167
165
|
all_configs
|
168
166
|
.reject { |config| config[:role] == 'master' }
|
169
167
|
.map { |config| config.except(:role) }
|
170
168
|
end
|
171
169
|
|
172
|
-
|
173
170
|
protected
|
174
171
|
|
175
|
-
|
176
172
|
def all_configs
|
177
173
|
@makara_config[:connections].map do |connection|
|
178
174
|
base_config.merge(makara_config.except(:connections))
|
@@ -180,12 +176,10 @@ module Makara
|
|
180
176
|
end
|
181
177
|
end
|
182
178
|
|
183
|
-
|
184
179
|
def base_config
|
185
180
|
@base_config ||= DEFAULTS.merge(@config).except(:makara)
|
186
181
|
end
|
187
182
|
|
188
|
-
|
189
183
|
def recursive_sort(thing)
|
190
184
|
return thing.to_s unless thing.include?(Enumerable)
|
191
185
|
|
@@ -194,8 +188,16 @@ module Makara
|
|
194
188
|
end
|
195
189
|
|
196
190
|
thing.sort_by(&:to_s)
|
197
|
-
|
198
191
|
end
|
199
192
|
|
193
|
+
def sanitize_id(id)
|
194
|
+
return if id.nil? || id.empty?
|
195
|
+
|
196
|
+
id.gsub(/[\|:]/, '').tap do |sanitized_id|
|
197
|
+
if sanitized_id.size != id.size
|
198
|
+
Makara::Logging::Logger.log "Proxy id '#{id}' changed to '#{sanitized_id}'", :warn
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
200
202
|
end
|
201
203
|
end
|
@@ -7,7 +7,6 @@ require 'active_support/core_ext/hash/keys'
|
|
7
7
|
|
8
8
|
module Makara
|
9
9
|
class ConnectionWrapper
|
10
|
-
|
11
10
|
attr_accessor :initial_error, :config
|
12
11
|
|
13
12
|
# invalid queries caused by connections switching that needs to be replaced
|
@@ -16,7 +15,7 @@ module Makara
|
|
16
15
|
def initialize(proxy, connection, config)
|
17
16
|
@config = config.symbolize_keys
|
18
17
|
@connection = connection
|
19
|
-
@proxy
|
18
|
+
@proxy = proxy
|
20
19
|
|
21
20
|
if connection.nil?
|
22
21
|
_makara_blacklist!
|
@@ -35,11 +34,19 @@ module Makara
|
|
35
34
|
@config[:name]
|
36
35
|
end
|
37
36
|
|
37
|
+
def _makara_shard_id
|
38
|
+
@config[:shard_id]
|
39
|
+
end
|
40
|
+
|
38
41
|
# has this node been blacklisted?
|
39
42
|
def _makara_blacklisted?
|
40
43
|
@blacklisted_until.present? && @blacklisted_until.to_i > Time.now.to_i
|
41
44
|
end
|
42
45
|
|
46
|
+
def _makara_in_transaction?
|
47
|
+
@connection && @connection.open_transactions > 0
|
48
|
+
end
|
49
|
+
|
43
50
|
# blacklist this node for @config[:blacklist_duration] seconds
|
44
51
|
def _makara_blacklist!
|
45
52
|
@connection.disconnect! if @connection
|
@@ -95,28 +102,21 @@ module Makara
|
|
95
102
|
|
96
103
|
# we want to forward all private methods, since we could have kicked out from a private scenario
|
97
104
|
def method_missing(m, *args, &block)
|
98
|
-
|
99
|
-
_makara_connection.public_send(m, *args, &block)
|
100
|
-
else # probably private method
|
101
|
-
_makara_connection.__send__(m, *args, &block)
|
102
|
-
end
|
105
|
+
_makara_connection.send(m, *args, &block)
|
103
106
|
end
|
104
107
|
|
108
|
+
ruby2_keywords :method_missing if Module.private_method_defined?(:ruby2_keywords)
|
105
109
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
110
|
-
RUBY_EVAL
|
111
|
-
|
110
|
+
def respond_to_missing?(m, include_private = false)
|
111
|
+
_makara_connection.respond_to?(m, true)
|
112
|
+
end
|
112
113
|
|
113
114
|
protected
|
114
115
|
|
115
116
|
# once the underlying connection is present we must evaluate extra functionality into it.
|
116
117
|
# all extra functionality is in the format of _makara*
|
117
118
|
def _makara_decorate_connection(con)
|
118
|
-
|
119
|
-
extension = %Q{
|
119
|
+
extension = <<~RUBY
|
120
120
|
# the proxy object controlling this connection
|
121
121
|
def _makara
|
122
122
|
@_makara
|
@@ -140,31 +140,52 @@ module Makara
|
|
140
140
|
def _makara_name
|
141
141
|
#{@config[:name].inspect}
|
142
142
|
end
|
143
|
-
|
143
|
+
RUBY
|
144
|
+
|
145
|
+
args = RUBY_VERSION >= "3.0.0" ? "..." : "*args, &block"
|
144
146
|
|
145
147
|
# Each method the Makara::Proxy needs to hijack should be redefined in the underlying connection.
|
146
148
|
# The new definition should allow for the proxy to intercept the invocation if required.
|
147
149
|
@proxy.class.hijack_methods.each do |meth|
|
148
|
-
|
149
|
-
|
150
|
+
method_call = RUBY_VERSION >= "3.0.0" ? "public_send(#{meth.inspect}, ...)" : "#{meth}(*args, &block)"
|
151
|
+
|
152
|
+
extension << <<~RUBY
|
153
|
+
def #{meth}(#{args})
|
150
154
|
_makara_hijack do |proxy|
|
151
155
|
if proxy
|
152
|
-
proxy.#{
|
156
|
+
proxy.#{method_call}
|
153
157
|
else
|
154
158
|
super
|
155
159
|
end
|
156
160
|
end
|
157
161
|
end
|
158
|
-
|
162
|
+
RUBY
|
163
|
+
end
|
164
|
+
|
165
|
+
# Control methods must always be passed to the
|
166
|
+
# Makara::Proxy control object for handling (typically
|
167
|
+
# related to ActiveRecord connection pool management)
|
168
|
+
@proxy.class.control_methods.each do |meth|
|
169
|
+
method_call = RUBY_VERSION >= "3.0.0" ? "public_send(#{meth.inspect}, ...)" : "#{meth}(*args=args, block)"
|
170
|
+
|
171
|
+
extension << <<~RUBY
|
172
|
+
def #{meth}(#{args})
|
173
|
+
proxy = _makara
|
174
|
+
if proxy
|
175
|
+
proxy.control.#{method_call}
|
176
|
+
else
|
177
|
+
super # Only if we are not wrapped any longer
|
178
|
+
end
|
179
|
+
end
|
180
|
+
RUBY
|
159
181
|
end
|
160
182
|
|
161
183
|
# extend the instance
|
162
|
-
con.instance_eval(extension)
|
184
|
+
con.instance_eval(extension, __FILE__, __LINE__ + 1)
|
163
185
|
# set the makara context
|
164
186
|
con._makara = @proxy
|
165
187
|
|
166
188
|
con._makara
|
167
189
|
end
|
168
|
-
|
169
190
|
end
|
170
191
|
end
|
data/lib/makara/context.rb
CHANGED
@@ -1,63 +1,135 @@
|
|
1
1
|
require 'digest/md5'
|
2
2
|
|
3
|
-
# Keeps track of the current
|
4
|
-
# If a new context is needed it can be generated via Makara::Context.generate
|
5
|
-
|
3
|
+
# Keeps track of the current stickiness state for different Makara proxies
|
6
4
|
module Makara
|
7
5
|
class Context
|
8
|
-
|
6
|
+
attr_accessor :stored_data, :staged_data
|
7
|
+
attr_reader :current_timestamp
|
8
|
+
|
9
|
+
def initialize(context_data)
|
10
|
+
@stored_data = context_data
|
11
|
+
@staged_data = {}
|
12
|
+
@dirty = @was_dirty = false
|
13
|
+
|
14
|
+
freeze_time
|
15
|
+
end
|
16
|
+
|
17
|
+
def stage(proxy_id, ttl)
|
18
|
+
staged_data[proxy_id] = [staged_data[proxy_id].to_f, ttl.to_f].max
|
19
|
+
end
|
20
|
+
|
21
|
+
def stuck?(proxy_id)
|
22
|
+
stored_data[proxy_id] && !expired?(stored_data[proxy_id])
|
23
|
+
end
|
9
24
|
|
10
|
-
|
11
|
-
|
12
|
-
|
25
|
+
def staged?(proxy_id)
|
26
|
+
staged_data.key?(proxy_id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def release(proxy_id)
|
30
|
+
@dirty ||= !!stored_data.delete(proxy_id)
|
31
|
+
staged_data.delete(proxy_id)
|
32
|
+
end
|
33
|
+
|
34
|
+
def release_all
|
35
|
+
if self.stored_data.any?
|
36
|
+
self.stored_data = {}
|
37
|
+
# We need to track a change made to the current stored data
|
38
|
+
# so we can commit it later
|
39
|
+
@dirty = true
|
13
40
|
end
|
41
|
+
self.staged_data = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Stores the staged data with an expiration time based on the current time,
|
45
|
+
# and clears any expired entries. Returns true if any changes were made to
|
46
|
+
# the current store
|
47
|
+
def commit
|
48
|
+
freeze_time
|
49
|
+
release_expired
|
50
|
+
store_staged_data
|
51
|
+
clean
|
52
|
+
|
53
|
+
was_dirty?
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
14
57
|
|
15
|
-
|
16
|
-
|
58
|
+
def freeze_time
|
59
|
+
@current_timestamp = Time.now.to_f
|
60
|
+
end
|
61
|
+
|
62
|
+
# Indicates whether there have been changes to the context that need
|
63
|
+
# to be persisted when the request finishes
|
64
|
+
def dirty?
|
65
|
+
@dirty
|
66
|
+
end
|
67
|
+
|
68
|
+
def was_dirty?
|
69
|
+
@was_dirty
|
70
|
+
end
|
71
|
+
|
72
|
+
def expired?(timestamp)
|
73
|
+
timestamp <= current_timestamp
|
74
|
+
end
|
75
|
+
|
76
|
+
def release_expired
|
77
|
+
previous_size = stored_data.size
|
78
|
+
stored_data.delete_if { |_, timestamp| expired?(timestamp) }
|
79
|
+
@dirty ||= previous_size != stored_data.size
|
80
|
+
end
|
81
|
+
|
82
|
+
def store_staged_data
|
83
|
+
staged_data.each do |proxy_id, ttl|
|
84
|
+
if ttl > 0 && self.stored_data[proxy_id].to_f < current_timestamp + ttl
|
85
|
+
self.stored_data[proxy_id] = current_timestamp + ttl
|
86
|
+
@dirty = true
|
87
|
+
end
|
17
88
|
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def clean
|
92
|
+
@was_dirty = dirty?
|
93
|
+
@dirty = false
|
94
|
+
@staged_data = {}
|
95
|
+
end
|
18
96
|
|
19
|
-
|
20
|
-
|
21
|
-
set(:
|
97
|
+
class << self
|
98
|
+
def set_current(context_data)
|
99
|
+
set(:makara_current_context, new(context_data))
|
22
100
|
end
|
23
101
|
|
24
|
-
|
25
|
-
|
102
|
+
# Called by `Proxy#stick_to_master!` to use master in subsequent requests
|
103
|
+
def stick(proxy_id, ttl)
|
104
|
+
current.stage(proxy_id, ttl)
|
26
105
|
end
|
27
106
|
|
28
|
-
def
|
29
|
-
|
107
|
+
def stuck?(proxy_id)
|
108
|
+
current.staged?(proxy_id) || current.stuck?(proxy_id)
|
30
109
|
end
|
31
110
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
111
|
+
def next
|
112
|
+
if current.commit
|
113
|
+
current.stored_data
|
35
114
|
end
|
36
115
|
end
|
37
116
|
|
38
|
-
|
39
|
-
|
40
|
-
# when they're asking whether they should be stuck to master.
|
41
|
-
def stick(context, config_id, ttl)
|
42
|
-
Makara::Cache.write(cache_key_for(context, config_id), '1', ttl)
|
117
|
+
def release(proxy_id)
|
118
|
+
current.release(proxy_id)
|
43
119
|
end
|
44
120
|
|
45
|
-
def
|
46
|
-
|
121
|
+
def release_all
|
122
|
+
current.release_all
|
47
123
|
end
|
48
124
|
|
49
125
|
protected
|
50
126
|
|
51
|
-
def
|
52
|
-
fetch(:
|
53
|
-
end
|
54
|
-
|
55
|
-
def cache_key_for(context, config_id)
|
56
|
-
"makara::#{context}-#{config_id}"
|
127
|
+
def current
|
128
|
+
fetch(:makara_current_context) { new({}) }
|
57
129
|
end
|
58
130
|
|
59
131
|
def fetch(key)
|
60
|
-
get(key) || set(key,yield)
|
132
|
+
get(key) || set(key, yield)
|
61
133
|
end
|
62
134
|
|
63
135
|
if Thread.current.respond_to?(:thread_variable_get)
|
@@ -65,19 +137,18 @@ module Makara
|
|
65
137
|
Thread.current.thread_variable_get(key)
|
66
138
|
end
|
67
139
|
|
68
|
-
def set(key,value)
|
69
|
-
Thread.current.thread_variable_set(key,value)
|
140
|
+
def set(key, value)
|
141
|
+
Thread.current.thread_variable_set(key, value)
|
70
142
|
end
|
71
143
|
else
|
72
144
|
def get(key)
|
73
145
|
Thread.current[key]
|
74
146
|
end
|
75
147
|
|
76
|
-
def set(key,value)
|
148
|
+
def set(key, value)
|
77
149
|
Thread.current[key]=value
|
78
150
|
end
|
79
151
|
end
|
80
|
-
|
81
152
|
end
|
82
153
|
end
|
83
154
|
end
|