Skip to content
Draft
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7b54cd4
allow rails 7.1
ttstarck Jul 30, 2025
f0f958e
segfaulting for some reason
ttstarck Aug 6, 2025
2a36579
add require logger
ttstarck Aug 6, 2025
0a99266
only include em synchrony adapter methods in rails 7.0
ttstarck Aug 6, 2025
d82551c
add rails 7.1 to github workflow
ttstarck Aug 6, 2025
f169f30
add ruby 3.4 to github CI
ttstarck Aug 6, 2025
b2c5089
remove unnecessary em-synchrony code
ttstarck Aug 6, 2025
ea193b9
remove patch for rails 7.1
ttstarck Aug 6, 2025
ad9fe3b
bump bundler version | fix rails 7.0 gemfile to add mutex_m
ttstarck Aug 6, 2025
fa7924a
clean up comments in cp spec
ttstarck Aug 6, 2025
89ed20d
move query stub to before block
ttstarck Aug 6, 2025
6c6f85c
update to use coverall_reborn gem as coveralls was no longer maintained
ttstarck Aug 6, 2025
2e08fa6
use simplecov instead of coveralls
ttstarck Aug 6, 2025
4ff7d7c
remove simplecov
ttstarck Aug 6, 2025
3bc863d
add back in em next ticks
ttstarck Aug 6, 2025
7b3671d
lower rspec
ttstarck Aug 6, 2025
cb90a78
lower rspec to 3.12
ttstarck Aug 6, 2025
886ce61
add bigdecimal to rails 7.0 gemfile
ttstarck Aug 6, 2025
6c51dda
add comment for setting rspec to 3.12
ttstarck Aug 6, 2025
76ddbe7
bump version
ttstarck Aug 6, 2025
645cbfa
add back in double check on connection
ttstarck Aug 6, 2025
13afedc
remove mocks on new
ttstarck Aug 6, 2025
05fd774
use initialize
ttstarck Aug 6, 2025
5acdde2
use separate transaction manager
ttstarck Aug 6, 2025
879f492
bump version
ttstarck Aug 6, 2025
696d2a5
use connect
ttstarck Aug 6, 2025
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
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: [3.1, 3.2, 3.3]
ruby: [3.1, 3.2, 3.3, 3.4]
gemfile:
- Gemfile
- gemfiles/rails_6_1.gemfile
- gemfiles/rails_7_0.gemfile
- gemfiles/rails_7_1.gemfile
env:
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
steps:
Expand Down
6 changes: 5 additions & 1 deletion Appraisals
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@

require "appraisal/matrix"

appraisal_matrix(rails: [">= 6.1", "< 7.1"])
appraisal_matrix(rails: [">= 7.0", "< 7.2"]) do |rails:|
if rails < "7.1"
gem "mutex_m"
end
end
16 changes: 8 additions & 8 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
specs:
fibered_mysql2 (0.3.1)
em-synchrony (~> 1.0)
rails (>= 6.1, < 7.1)
rails (>= 7.0, < 7.2)

GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.8.6)

Check failure on line 11 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L11

actionpack Warning Message: https://github.com/rails/rails/security/advisories/GHSA-vfm5-rmrh-j26v CVE: CVE-2024-54133 Severity:
actionpack (= 7.0.8.6)
activesupport (= 7.0.8.6)
nio4r (~> 2.0)
Expand All @@ -19,7 +19,7 @@
activerecord (= 7.0.8.6)
activestorage (= 7.0.8.6)
activesupport (= 7.0.8.6)
mail (>= 2.7.1)

Check failure on line 22 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L22

net-imap Warning Message: https://github.com/ruby/net-imap/security/advisories/GHSA-7fc5-f82f-cx69 CVE: CVE-2025-25186 Severity: medium

Check failure on line 22 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L22

net-imap Warning Message: https://github.com/ruby/net-imap/security/advisories/GHSA-j3g3-5qv5-52mj CVE: CVE-2025-43857 Severity:
net-imap
net-pop
net-smtp
Expand All @@ -35,17 +35,17 @@
rails-dom-testing (~> 2.0)
actionpack (7.0.8.6)
actionview (= 7.0.8.6)
activesupport (= 7.0.8.6)

Check failure on line 38 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L38

rack Warning Message: https://github.com/rack/rack/security/advisories/GHSA-7g2v-jj9q-g3rg CVE: CVE-2025-25184 Severity:

Check failure on line 38 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L38

rack Warning Message: https://github.com/rack/rack/security/advisories/GHSA-8cgq-6mh2-7j6v CVE: CVE-2025-27111 Severity:

Check failure on line 38 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L38

rack Warning Message: https://github.com/rack/rack/security/advisories/GHSA-7wqh-767x-r66v CVE: CVE-2025-27610 Severity: high

Check failure on line 38 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L38

rack Warning Message: https://github.com/rack/rack-session/security/advisories/GHSA-9j94-67jr-4cqj CVE: CVE-2025-32441 Severity: medium

Check failure on line 38 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L38

rack Warning Message: https://github.com/rack/rack/security/advisories/GHSA-gjh7-p2fx-99vx CVE: CVE-2025-46727 Severity: high
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)

Check failure on line 41 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L41

rails-html-sanitizer Warning Message: https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-w8gc-x259-rc7x CVE: CVE-2024-53985 Severity:

Check failure on line 41 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L41

rails-html-sanitizer Warning Message: https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-638j-pmjw-jq48 CVE: CVE-2024-53986 Severity:

Check failure on line 41 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L41

rails-html-sanitizer Warning Message: https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-2x5m-9ch4-qgrr CVE: CVE-2024-53987 Severity:

Check failure on line 41 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L41

rails-html-sanitizer Warning Message: https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-cfjx-w229-hgx5 CVE: CVE-2024-53988 Severity:

Check failure on line 41 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L41

rails-html-sanitizer Warning Message: https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-rxv5-gxqc-xx8g CVE: CVE-2024-53989 Severity:
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.8.6)
actionpack (= 7.0.8.6)
activerecord (= 7.0.8.6)
activestorage (= 7.0.8.6)
activesupport (= 7.0.8.6)
globalid (>= 0.6.0)

Check failure on line 48 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L48

nokogiri Warning Message: https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-353f-x4gh-cqq8 CVE: GHSA-353f-x4gh-cqq8 Severity:

Check failure on line 48 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L48

nokogiri Warning Message: https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-5w6v-399v-w3cc CVE: GHSA-5w6v-399v-w3cc Severity:

Check failure on line 48 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L48

nokogiri Warning Message: https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-mrxw-mxhj-p664 CVE: GHSA-mrxw-mxhj-p664 Severity: high

Check failure on line 48 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L48

nokogiri Warning Message: https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-vvfq-8hwr-qm4m CVE: GHSA-vvfq-8hwr-qm4m Severity:
nokogiri (>= 1.8.5)
actionview (7.0.8.6)
activesupport (= 7.0.8.6)
Expand Down Expand Up @@ -75,7 +75,7 @@
tzinfo (~> 2.0)
appraisal (2.5.0)
bundler
rake

Check failure on line 78 in Gemfile.lock

View check run for this annotation

Security Scanner as a Service / Bundle Audit

Gemfile.lock#L78

thor Warning Message: https://github.com/advisories/GHSA-mqcp-p2hv-vw6x CVE: CVE-2025-54314 Severity: low
thor (>= 0.14.0)
appraisal-matrix (0.3.0)
appraisal (~> 2.2)
Expand All @@ -92,7 +92,7 @@
tins (~> 1.6)
crass (1.0.6)
date (3.3.4)
diff-lcs (1.5.1)
diff-lcs (1.6.2)
docile (1.4.1)
em-synchrony (1.0.6)
eventmachine (>= 1.0.0.beta.1)
Expand Down Expand Up @@ -169,19 +169,19 @@
thor (~> 1.0)
zeitwerk (~> 2.5)
rake (13.2.1)
rspec (3.13.0)
rspec (3.13.1)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.0)
rspec-core (3.13.5)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.1)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.1)
rspec-mocks (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.1)
rspec-support (3.13.4)
simplecov (0.16.1)
docile (~> 1.1)
json (>= 1.8, < 3)
Expand Down Expand Up @@ -218,4 +218,4 @@
rspec

BUNDLED WITH
2.2.29
2.6.9
2 changes: 1 addition & 1 deletion fibered_mysql2.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]

spec.add_dependency 'em-synchrony', '~> 1.0'
spec.add_dependency 'rails', '>= 6.1', '< 7.1'
spec.add_dependency 'rails', '>= 7.0', '< 7.2'
end
1 change: 1 addition & 0 deletions gemfiles/rails_7_0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ gem "pry-byebug"
gem "rake"
gem "rspec"
gem "rails", "~> 7.0.0"
gem "mutex_m"

gemspec path: "../"
2 changes: 1 addition & 1 deletion gemfiles/rails_6_1.gemfile → gemfiles/rails_7_1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ gem "pry"
gem "pry-byebug"
gem "rake"
gem "rspec"
gem "rails", "~> 6.1.0"
gem "rails", "~> 7.1.0"

gemspec path: "../"
15 changes: 7 additions & 8 deletions lib/active_record/connection_adapters/fibered_mysql2_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
require 'em-synchrony'
require 'active_model'
require 'active_record/errors'

require 'active_record/connection_adapters/mysql2_adapter'
require 'active_record/connection_adapters/em_mysql2_adapter'
require 'em-synchrony/mysql2'

module FiberedMysql2
module FiberedMysql2Adapter_5_2
module FiberedMysql2Adapter_7_0
def lease
if in_use?
msg = "Cannot lease connection, ".dup
Expand Down Expand Up @@ -62,8 +63,10 @@ def owner_fiber
end
end

class FiberedMysql2Adapter < ::ActiveRecord::ConnectionAdapters::EMMysql2Adapter
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't actually providing us anything useful:
https://github.com/igrigorik/em-synchrony/blob/master/lib/active_record/connection_adapters/em_mysql2_adapter.rb#L40-L46

We aren't actually using the EMMysql2Adapter::Client, and the code in EM::Synchrony::ActiveRecord::Adapter_4_2 actually isn't necessary either.

https://github.com/igrigorik/em-synchrony/blob/master/lib/em-synchrony/activerecord_4_2.rb

The only thing this was doing was converting the TransactionManager to the Fiber isolated TransactionManager, however this isn't necessary as the TransactionManager lives on the Connection, and for FiberedMysql2, we already are isolating the connections per Fiber.

Also if we call

ActiveRecord::Base.transaction do
  # query stuff ...
end

This has a requires a lock on the connection to enter the transaction so this is already safe across fibers as another fiber even if it had access to the connection for some reason would not be able to update the state of the TransactionManager

include FiberedMysql2Adapter_5_2
class FiberedMysql2Adapter < ::ActiveRecord::ConnectionAdapters::Mysql2Adapter
if ::ActiveRecord.gem_version < "7.1"
include FiberedMysql2Adapter_7_0
end

class << self
# Copied from Mysql2Adapter, except with the EM Mysql2 client
Expand All @@ -77,9 +80,5 @@ def new_client(config)
end
end
end

def initialize(*args)
super
end
Comment on lines -81 to -83
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant

end
end
85 changes: 35 additions & 50 deletions lib/fibered_mysql2/fibered_database_connection_pool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -184,33 +184,36 @@ def mon_exit_for_cond
end

module FiberedDatabaseConnectionPool
include FiberedMonitorMixin

module Adapter_5_2
def cached_connections
@thread_cached_conns
module Adapter_7_0
def release_connection(owner_thread = Fiber.current)
if (conn = @thread_cached_conns.delete(connection_cache_key(owner_thread)))
checkin(conn)
end
end

def current_connection_id
connection_cache_key(current_thread)
def with_connection
unless (conn = cached_connections[current_connection_id]) # Invoca Patch to use Fiber
conn = connection
fresh_connection = true
end
yield conn
ensure
release_connection if fresh_connection
end

def checkout(checkout_timeout = @checkout_timeout)
begin
reap_connections
rescue => ex
ActiveRecord::Base.logger.error("Exception occurred while executing reap_connections: #{ex}")
end
super
def current_thread
Fiber.current
end

def release_connection(owner_thread = Fiber.current)
if (conn = @thread_cached_conns.delete(connection_cache_key(owner_thread)))
checkin(conn)
end
def connection
cached_connections[current_connection_id] ||= checkout
end
end
include Adapter_5_2

if ::ActiveRecord.gem_version < "7.1"
include Adapter_7_0
end
include FiberedMonitorMixin # This is switches the connection pool's mutex and condition variables to event machine / Fiber compatible ones.

def initialize(pool_config)
if pool_config.db_config.reaping_frequency
Expand All @@ -222,41 +225,23 @@ def initialize(pool_config)
@reaper = nil # no need to keep a reference to this since it does nothing in this sub-class
end

def connection
# this is correctly done double-checked locking
# (ThreadSafe::Cache's lookups have volatile semantics)
if (result = cached_connections[current_connection_id])
result
else
synchronize do
if (result = cached_connections[current_connection_id])
result
else
cached_connections[current_connection_id] = checkout
end
end
end
end
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This double check isn't necessary since the current_connection_id is the current Fiber and we'd only ever be setting this value in the cached_connections in this fiber as well.

This is a legacy of EM::Synchrony's code that was making the connection pool safe to use one fiber across the single thread.


def reap_connections
cached_connections.values.each do |connection|
unless connection.owner.alive?
checkin(connection)
end
end
Comment on lines -241 to -246
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is our own method only used in checkout override, which I've replaced with just calling reap.

def current_connection_id
connection_cache_key(current_thread)
end

private

#--
# This hook-in method allows for easier monkey-patching fixes needed by
# JRuby users that use Fibers.
def connection_cache_key(fiber)
fiber
def cached_connections
@thread_cached_conns
end

def current_thread
Fiber.current
# Invoca patch that reaps orphaned connections on checkout. This lets us immediately use a connection left open by dead fibers
# instead of waiting for all connections to be used in the pool before they are reaped.
def checkout(checkout_timeout = @checkout_timeout)
begin
reap
rescue => ex
ActiveRecord::Base.logger.error("Exception occurred while executing reap_connections: #{ex}")
end
super
end
end
end
Expand Down
95 changes: 0 additions & 95 deletions lib/fibered_mysql2/fibered_mysql2_connection_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,101 +2,6 @@

require_relative '../active_record/connection_adapters/fibered_mysql2_adapter'

module EM::Synchrony
module ActiveRecord
_ = Adapter_4_2
module Adapter_4_2
Comment on lines -5 to -8
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the inclusion of this module as its not needed. The TransactionManager is configured per connection, and the isolating the TransactionManager per Fiber is only necessary if you're sharing the connection across multiple fibers which we're not.

def configure_connection
super # undo EM::Synchrony's override here
end

def transaction(*args)
super # and here
end

_ = TransactionManager
class TransactionManager < _
# Overriding the em-synchrony override to bring it up to rails 6 requirements.
# Changes from the original Rails 6 source are:
# 1. the usage of _current_stack created by em-synchrony instead of the Rails provided @stack instance variable
# 2. the usage of Fiber.current.object_id as a part of the savepoint transaction name
#
# Original EM Synchrony Source:
# https://github.com/igrigorik/em-synchrony/blob/master/lib/em-synchrony/activerecord_4_2.rb#L35-L44
#
# Original Rails Source:
# https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L205-L224
def begin_transaction(isolation: nil, joinable: true, _lazy: true)
@connection.lock.synchronize do
run_commit_callbacks = !current_transaction.joinable?
transaction =
if _current_stack.empty?
::ActiveRecord::ConnectionAdapters::RealTransaction.new(@connection, isolation:, joinable:, run_commit_callbacks: run_commit_callbacks)
else
::ActiveRecord::ConnectionAdapters::SavepointTransaction.new(@connection, "active_record_#{Fiber.current.object_id}_#{open_transactions}", _current_stack.last, isolation:, joinable:, run_commit_callbacks: run_commit_callbacks)
end

if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
@has_unmaterialized_transactions = true
else
transaction.materialize!
end
_current_stack.push(transaction)
transaction
end
end

# Overriding the ActiveRecord::TransactionManager#materialize_transactions method to use
# fiber safe the _current_stack instead of the @stack instance variable. when marterializing
# transactions.
def materialize_transactions
return if @materializing_transactions
return unless @has_unmaterialized_transactions

@connection.lock.synchronize do
begin
@materializing_transactions = true
_current_stack.each { |t| t.materialize! unless t.materialized? }
ensure
@materializing_transactions = false
end
@has_unmaterialized_transactions = false
end
end

# Overriding the ActiveRecord::TransactionManager#commit_transaction method to use
# fiber safe the _current_stack instead of the @stack instance variable. when marterializing
# transactions.
def commit_transaction
@connection.lock.synchronize do
transaction = _current_stack.last

begin
transaction.before_commit_records
ensure
_current_stack.pop
end

transaction.commit
transaction.commit_records
end
end

# Overriding the ActiveRecord::TransactionManager#rollback_transaction method to use
# fiber safe the _current_stack instead of the @stack instance variable. when marterializing
# transactions.
def rollback_transaction(transaction = nil)
@connection.lock.synchronize do
transaction ||= _current_stack.pop
transaction.rollback
transaction.rollback_records
end
end
end
end
end
end

module FiberedMysql2
module FiberedMysql2ConnectionFactory
def fibered_mysql2_connection(raw_config)
Expand Down
6 changes: 6 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Coveralls.wear!

require 'bundler/setup'
require 'logger'
require 'rails'
require 'active_record'
require 'fibered_mysql2'
Expand All @@ -24,4 +25,9 @@
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end

config.before(:all) do
ActiveSupport::IsolatedExecutionState.isolation_level = :fiber
ActiveRecord::Base.logger = Logger.new("/dev/null")
end
end
Loading
Loading