Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .app-image-tag
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.10.1.13
v0.10.1.15
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ gem "pry", "~> 0.16.0"

# for documentation server
gem "puma"
gem "rack", "~> 2.2.21"
gem "rack", "~> 2.2.22"
gem "yard"

group :test do
Expand Down
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ GEM
puma (7.2.0)
nio4r (~> 2.0)
racc (1.8.1)
rack (2.2.21)
rack (2.2.22)
rainbow (3.1.1)
rake (13.3.1)
readline (0.0.4)
Expand All @@ -98,7 +98,7 @@ GEM
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.6)
rubocop (1.84.1)
rubocop (1.84.2)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
Expand Down Expand Up @@ -150,7 +150,7 @@ DEPENDENCIES
pg (~> 1.6)
pry (~> 0.16.0)
puma
rack (~> 2.2.21)
rack (~> 2.2.22)
rake (~> 13.3)
readline
redis (~> 5.4.1)
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ ActiveRecordProxyAdapters.configure do |config|

# How long proxy should wait for replica to connect.
config.checkout_timeout = 5.seconds # defaults to 2.seconds

# Whether to persist write timestamps in a cookie for cross-request stickiness.
# When false, the middleware still creates a fresh context per request (preventing
# thread-local leaks in multi-threaded servers) but skips reading/writing the cookie.
config.stickiness_cookie_enabled = false # defaults to true
end
```

Expand Down
2 changes: 1 addition & 1 deletion gemfiles/ar_7.2.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ gem "logger"
gem "readline"
gem "pry", "~> 0.16.0"
gem "puma"
gem "rack", "~> 2.2.21"
gem "rack", "~> 2.2.22"
gem "yard"
gem "activerecord", "~> 7.2.0"
gem "activesupport", "~> 7.2.0"
Expand Down
2 changes: 1 addition & 1 deletion gemfiles/ar_8.0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ gem "logger"
gem "readline"
gem "pry", "~> 0.16.0"
gem "puma"
gem "rack", "~> 2.2.21"
gem "rack", "~> 2.2.22"
gem "yard"
gem "activerecord", "~> 8.0.0"
gem "activesupport", "~> 8.0.0"
Expand Down
2 changes: 1 addition & 1 deletion gemfiles/ar_8.1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ gem "logger"
gem "readline"
gem "pry", "~> 0.16.0"
gem "puma"
gem "rack", "~> 2.2.21"
gem "rack", "~> 2.2.22"
gem "yard"
gem "activerecord", "~> 8.1.0"
gem "activesupport", "~> 8.1.0"
Expand Down
25 changes: 14 additions & 11 deletions lib/active_record_proxy_adapters/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,19 @@ class Configuration
}.freeze

# @return [Class] The context that is used to store the current request's state.
attr_reader :context_store

# @return [Proc] The timeout strategy to use for regex matching.
attr_reader :regexp_timeout_strategy

# @return [Logger] The logger to use for logging messages.
attr_reader :logger
# @return [Boolean] Whether to use cookies for cross-request stickiness.
attr_reader :context_store, :regexp_timeout_strategy, :logger, :stickiness_cookie_enabled

def initialize
@lock = Monitor.new

self.cache_configuration = CacheConfiguration.new(@lock)
self.context_store = ActiveRecordProxyAdapters::Context
self.regexp_timeout_strategy = :log
self.logger = ActiveRecord::Base.logger || Logger.new($stdout)
@database_configurations = {}
self.cache_configuration = CacheConfiguration.new(@lock)
self.context_store = ActiveRecordProxyAdapters::Context
self.regexp_timeout_strategy = :log
self.logger = ActiveRecord::Base.logger || Logger.new($stdout)
self.stickiness_cookie_enabled = true
@database_configurations = {}
end

def log_subscriber_primary_prefix=(prefix)
Expand Down Expand Up @@ -95,6 +92,12 @@ def checkout_timeout=(checkout_timeout)
default_database_config.checkout_timeout = checkout_timeout
end

def stickiness_cookie_enabled=(enabled)
synchronize_update(:stickiness_cookie_enabled, from: @stickiness_cookie_enabled, to: enabled) do
@stickiness_cookie_enabled = enabled
end
end

def logger=(logger)
synchronize_update(:logger, from: @logger, to: logger) do
@logger = logger
Expand Down
8 changes: 6 additions & 2 deletions lib/active_record_proxy_adapters/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@ def initialize(app, cookie_options = {})
def call(env)
return @app.call(env) if ignore_request?(env)

self.current_context = context_store.new(COOKIE_READER.call(env))
self.current_context = if stickiness_cookie_enabled
context_store.new(COOKIE_READER.call(env))
else
context_store.new({})
end

status, headers, body = @app.call(env)

update_cookie_from_context(headers)
update_cookie_from_context(headers) if stickiness_cookie_enabled

[status, headers, body]
end
Expand Down
7 changes: 7 additions & 0 deletions lib/active_record_proxy_adapters/mixin/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ def logger
proxy_config.logger
end

# Helper to retrieve whether stickiness cookies are enabled from the configuration stored in
# {ActiveRecordProxyAdapters::Configuration#stickiness_cookie_enabled}.
# @return [Boolean]
def stickiness_cookie_enabled
proxy_config.stickiness_cookie_enabled
end

# Helper to retrieve the timeout strategy from the configuration stored in
# {ActiveRecordProxyAdapters::Configuration#regexp_timeout_strategy}.
# @return [Proc]
Expand Down
21 changes: 21 additions & 0 deletions spec/active_record_proxy_adapters/configuration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

RSpec.describe ActiveRecordProxyAdapters::Configuration do
describe "#stickiness_cookie_enabled" do
subject(:stickiness_cookie_enabled) { configuration.stickiness_cookie_enabled }

let(:configuration) { described_class.new }

it "defaults to true" do
expect(stickiness_cookie_enabled).to be(true)
end

context "when overridden" do
it "equals the overridden value" do
configuration.stickiness_cookie_enabled = false

expect(stickiness_cookie_enabled).to be(false)
end
end
end
end
33 changes: 33 additions & 0 deletions spec/active_record_proxy_adapters/middleware_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,39 @@ def from_cookie_string(cookie_string)
end
end

context "when stickiness_cookie_enabled is false" do
before do
ActiveRecordProxyAdapters.config.stickiness_cookie_enabled = false
end

after do
ActiveRecordProxyAdapters.config.stickiness_cookie_enabled = true
end

it "does not write a Set-Cookie header" do
env = {}
_, headers, = middleware.call(env)

expect(headers["Set-Cookie"]).to be_nil
end

it "creates a fresh context for the request" do
env = {}
middleware.call(env)

expect(middleware.send(:current_context)).to be_a(ActiveRecordProxyAdapters::Context)
end

it "does not read a previous request's cookie into the context" do
cookie_hash = { "sqlite3_primary" => Time.current.utc.to_f }
env = { "HTTP_COOKIE" => to_cookie_string(cookie_hash) }

middleware.call(env)

expect(middleware.send(:current_context).to_h).to eq({})
end
end

context "when request comes from rails asset prefix" do
before do
rails = double("Rails") # rubocop:disable RSpec/VerifiedDoubles
Expand Down