makara 0.3.9 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +5 -5
  2. data/.github/dependabot.yml +11 -0
  3. data/.github/workflows/CI.yml +88 -0
  4. data/.github/workflows/gem-publish-public.yml +36 -0
  5. data/.rspec +1 -1
  6. data/.rubocop.yml +15 -0
  7. data/.rubocop_todo.yml +670 -0
  8. data/CHANGELOG.md +88 -32
  9. data/Gemfile +1 -16
  10. data/README.md +39 -35
  11. data/Rakefile +1 -1
  12. data/gemfiles/activerecord_5.2.gemfile +8 -0
  13. data/gemfiles/activerecord_6.0.gemfile +8 -0
  14. data/gemfiles/activerecord_6.1.gemfile +8 -0
  15. data/gemfiles/activerecord_head.gemfile +6 -0
  16. data/lib/active_record/connection_adapters/jdbcmysql_makara_adapter.rb +4 -18
  17. data/lib/active_record/connection_adapters/jdbcpostgresql_makara_adapter.rb +4 -18
  18. data/lib/active_record/connection_adapters/makara_abstract_adapter.rb +111 -33
  19. data/lib/active_record/connection_adapters/makara_jdbcmysql_adapter.rb +4 -18
  20. data/lib/active_record/connection_adapters/makara_jdbcpostgresql_adapter.rb +4 -18
  21. data/lib/active_record/connection_adapters/makara_mysql2_adapter.rb +4 -20
  22. data/lib/active_record/connection_adapters/makara_postgis_adapter.rb +4 -19
  23. data/lib/active_record/connection_adapters/makara_postgresql_adapter.rb +4 -20
  24. data/lib/active_record/connection_adapters/mysql2_makara_adapter.rb +4 -20
  25. data/lib/active_record/connection_adapters/postgresql_makara_adapter.rb +4 -20
  26. data/lib/makara.rb +14 -5
  27. data/lib/makara/cache.rb +4 -42
  28. data/lib/makara/config_parser.rb +18 -16
  29. data/lib/makara/connection_wrapper.rb +43 -22
  30. data/lib/makara/context.rb +108 -37
  31. data/lib/makara/cookie.rb +53 -0
  32. data/lib/makara/error_handler.rb +2 -11
  33. data/lib/makara/errors/all_connections_blacklisted.rb +0 -2
  34. data/lib/makara/errors/blacklist_connection.rb +0 -2
  35. data/lib/makara/errors/blacklisted_while_in_transaction.rb +12 -0
  36. data/lib/makara/errors/invalid_shard.rb +14 -0
  37. data/lib/makara/errors/makara_error.rb +0 -1
  38. data/lib/makara/errors/no_connections_available.rb +0 -2
  39. data/lib/makara/logging/logger.rb +1 -5
  40. data/lib/makara/logging/subscriber.rb +0 -2
  41. data/lib/makara/middleware.rb +12 -76
  42. data/lib/makara/pool.rb +55 -47
  43. data/lib/makara/proxy.rb +76 -56
  44. data/lib/makara/railtie.rb +0 -8
  45. data/lib/makara/strategies/abstract.rb +1 -0
  46. data/lib/makara/strategies/priority_failover.rb +2 -0
  47. data/lib/makara/strategies/round_robin.rb +7 -3
  48. data/lib/makara/strategies/shard_aware.rb +45 -0
  49. data/lib/makara/version.rb +2 -4
  50. data/makara.gemspec +26 -3
  51. data/spec/active_record/connection_adapters/makara_abstract_adapter_error_handling_spec.rb +1 -6
  52. data/spec/active_record/connection_adapters/makara_abstract_adapter_spec.rb +10 -14
  53. data/spec/active_record/connection_adapters/makara_mysql2_adapter_spec.rb +18 -16
  54. data/spec/active_record/connection_adapters/makara_postgis_adapter_spec.rb +6 -9
  55. data/spec/active_record/connection_adapters/makara_postgresql_adapter_spec.rb +70 -10
  56. data/spec/cache_spec.rb +2 -53
  57. data/spec/config_parser_spec.rb +75 -57
  58. data/spec/connection_wrapper_spec.rb +6 -4
  59. data/spec/context_spec.rb +163 -100
  60. data/spec/cookie_spec.rb +72 -0
  61. data/spec/middleware_spec.rb +27 -56
  62. data/spec/pool_spec.rb +25 -14
  63. data/spec/proxy_spec.rb +50 -39
  64. data/spec/spec_helper.rb +10 -10
  65. data/spec/strategies/priority_failover_spec.rb +3 -4
  66. data/spec/strategies/round_robin_spec.rb +4 -8
  67. data/spec/strategies/shard_aware_spec.rb +218 -0
  68. data/spec/support/deep_dup.rb +1 -1
  69. data/spec/support/helpers.rb +10 -6
  70. data/spec/support/mock_objects.rb +6 -5
  71. data/spec/support/mysql2_database.yml +3 -2
  72. data/spec/support/mysql2_database_with_custom_errors.yml +6 -1
  73. data/spec/support/pool_extensions.rb +0 -3
  74. data/spec/support/postgis_database.yml +2 -0
  75. data/spec/support/postgis_schema.rb +6 -3
  76. data/spec/support/postgresql_database.yml +2 -2
  77. data/spec/support/proxy_extensions.rb +1 -3
  78. data/spec/support/schema.rb +6 -6
  79. data/spec/support/user.rb +4 -0
  80. metadata +170 -22
  81. data/.travis.yml +0 -70
  82. data/gemfiles/ar-head.gemfile +0 -15
  83. data/gemfiles/ar30.gemfile +0 -32
  84. data/gemfiles/ar31.gemfile +0 -31
  85. data/gemfiles/ar32.gemfile +0 -31
  86. data/gemfiles/ar40.gemfile +0 -17
  87. data/gemfiles/ar41.gemfile +0 -17
  88. data/gemfiles/ar42.gemfile +0 -17
  89. data/gemfiles/ar50.gemfile +0 -15
  90. data/gemfiles/ar51.gemfile +0 -15
  91. data/lib/makara/cache/memory_store.rb +0 -28
  92. 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
- if ActiveRecord::VERSION::MAJOR >= 4
5
-
6
- module ActiveRecord
7
- module ConnectionHandling
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
- if ActiveRecord::VERSION::MAJOR >= 4
5
-
6
- module ActiveRecord
7
- module ConnectionHandling
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, 'makara/errors/makara_error'
16
- autoload :AllConnectionsBlacklisted, 'makara/errors/all_connections_blacklisted'
17
- autoload :BlacklistConnection, 'makara/errors/blacklist_connection'
18
- autoload :NoConnectionsAvailable, 'makara/errors/no_connections_available'
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
- @store = store
5
+ Makara::Logging::Logger.log deprecation_warning, :warn
16
6
  end
17
7
 
18
- def read(key)
19
- store.try(:read, key)
20
- end
8
+ private
21
9
 
22
- def write(key, value, ttl)
23
- store.try(:write, key, value, :expires_in => ttl.to_i)
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
@@ -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
- :master_ttl => 5,
25
- :blacklist_duration => 30,
26
- :sticky => true
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] = URI.unescape(value) if value.is_a? String }
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 = 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
- if _makara_connection.respond_to?(m)
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
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
107
- def respond_to#{RUBY_VERSION.to_s =~ /^1.8/ ? nil : '_missing'}?(m, include_private = false)
108
- _makara_connection.respond_to?(m, true)
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
- extension << %Q{
149
- def #{meth}(*args)
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.#{meth}(*args)
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
@@ -1,63 +1,135 @@
1
1
  require 'digest/md5'
2
2
 
3
- # Keeps track of the current and previous context (hexdigests)
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
- class << self
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
- def generate(seed = nil)
11
- seed ||= "#{Time.now.to_i}#{Thread.current.object_id}#{rand(99999)}"
12
- Digest::MD5.hexdigest(seed)
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
- def get_previous
16
- fetch(:makara_context_previous) { generate }
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
- def set_previous(context)
20
- previously_sticky.clear
21
- set(:makara_context_previous,context)
97
+ class << self
98
+ def set_current(context_data)
99
+ set(:makara_current_context, new(context_data))
22
100
  end
23
101
 
24
- def get_current
25
- fetch(:makara_context_current) { generate }
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 set_current(context)
29
- set(:makara_context_current,context)
107
+ def stuck?(proxy_id)
108
+ current.staged?(proxy_id) || current.stuck?(proxy_id)
30
109
  end
31
110
 
32
- def previously_stuck?(config_id)
33
- previously_sticky.fetch(config_id) do
34
- stuck?(Makara::Context.get_previous, config_id)
111
+ def next
112
+ if current.commit
113
+ current.stored_data
35
114
  end
36
115
  end
37
116
 
38
- # Called by `Proxy#stick_to_master!` to stick subsequent requests to
39
- # master. They'll see the current context as their previous context
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 stuck?(context, config_id)
46
- !!Makara::Cache.read(cache_key_for(context, config_id))
121
+ def release_all
122
+ current.release_all
47
123
  end
48
124
 
49
125
  protected
50
126
 
51
- def previously_sticky
52
- fetch(:makara_previously_sticky) { Hash.new }
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