aws_advanced_ruby_driver_wrapper 1.0.0 → 1.0.1
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/CHANGELOG.md +17 -1
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_action_cable_postgresql_support.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_connection_handler.rb +47 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +23 -9
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +51 -10
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/type_adapter_alias.rb +61 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +5 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +10 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +10 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +10 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +14 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +6 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +10 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +4 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +5 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +9 -1
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +8 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +22 -3
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +18 -7
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +20 -3
- data/lib/aws_advanced_ruby_driver_wrapper/version.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper.rb +38 -0
- metadata +5 -3
- data/lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb +0 -25
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6da8d07797b41d2bef7c26ac10bff8a3fcf6050d098344d3e850cf80d88305a9
|
|
4
|
+
data.tar.gz: 2f17adb4cca840143bacbf16056d67748f12204f22a5beb71ddb8bdc445aca3e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc3f77408c42d2a2dde71cce86af1c0bb294380b6e7c679c51118e0fd2383f4d677e95d77521cbe974bf3c584e5f7af7b65244834a0489034d7ab9541982dcc6
|
|
7
|
+
data.tar.gz: fcfaa69564b9abfeed8095e5995a9893a636def8ea0f8c1ad471bbcf69a0f36f7593090b42f76083556c7ee834350238bc73c8e090408572ccf78b8a01ee858f
|
data/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,22 @@ All notable changes to this project will be documented in this file.
|
|
|
3
3
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/#semantic-versioning-200).
|
|
5
5
|
|
|
6
|
-
## [1.0.
|
|
6
|
+
## [1.0.1] - 2026-10-07
|
|
7
|
+
|
|
8
|
+
### :bug: Fixed
|
|
9
|
+
- Fixed the `aws_postgresql` ActiveRecord adapter passing ActiveRecord-only `database.yml` keys, such as the Rails 8.1 pool settings (`max_connections`, `min_connections`, `keepalive`, `max_age`) and multi-database settings (`replica`, `migrations_paths`, `database_tasks`), to `pg`, which rejected them with `PG::Error: invalid connection option` ([PR #191](https://github.com/aws/aws-advanced-ruby-driver-wrapper/pull/191)).
|
|
10
|
+
- Fixed the `aws_mysql2` ActiveRecord adapter dropping the `flags`, `encoding`, `socket`, and `reconnect` client options, which removed ActiveRecord's `FOUND_ROWS` flag so that statements such as `update_all` reported changed rows instead of matched rows ([PR #193](https://github.com/aws/aws-advanced-ruby-driver-wrapper/pull/193)).
|
|
11
|
+
- Fixed the `aws_postgresql` and `aws_mysql2` ActiveRecord adapters not translating connection errors the way the standard adapters do, so a missing database was not reported as `ActiveRecord::NoDatabaseError` and `db:prepare` (and `bin/setup`) failed instead of creating it ([PR #193](https://github.com/aws/aws-advanced-ruby-driver-wrapper/pull/193)).
|
|
12
|
+
- Fixed the `aws_postgresql` and `aws_mysql2` ActiveRecord adapters keeping ActiveRecord's prepared statement cache after a successful failover, so every query prepared before the failover failed on that connection with `Method invoked against old connection` until the process restarted ([PR #194](https://github.com/aws/aws-advanced-ruby-driver-wrapper/pull/194), [documentation](https://aws.github.io/aws-advanced-wrapper-docs/ruby/enhanced-failover)).
|
|
13
|
+
- Fixed `db:drop`, `db:reset`, and `db:test:prepare` failing on Aurora PostgreSQL with `database "<name>" is being accessed by other users`. The same error occurred when running `bin/rails test` after a schema change. The cause was the wrapper's topology and Blue/Green monitors, which kept their own connections open to the database being dropped. The wrapper now stops these monitors before dropping a database and when ActiveRecord clears all its connections ([PR #195](https://github.com/aws/aws-advanced-ruby-driver-wrapper/pull/195)).
|
|
14
|
+
- Fixed forked processes, such as Puma workers with `preload_app!`, Unicorn workers, and Resque jobs, inheriting the parent's background monitors after their threads had stopped, so the cluster topology never refreshed in the child and failover there could not find the new writer. A forked child now starts its own monitors and leaves the parent's monitoring connections open. A forked child also no longer waits on a Secrets Manager fetch that was in progress in the parent when it forked ([PR #196](https://github.com/aws/aws-advanced-ruby-driver-wrapper/pull/196)).
|
|
15
|
+
- Fixed the AWS Secrets Manager plugin not reporting a timed-out secret fetch as a timeout. A fetch that did not finish within 60 seconds failed with a generic `SecretsManagerAuthError` (`Failed to fetch database credentials from AWS Secrets Manager`) instead of `Timed out fetching secret after 60s` ([PR #196](https://github.com/aws/aws-advanced-ruby-driver-wrapper/pull/196)).
|
|
16
|
+
- Fixed Action Cable's PostgreSQL subscription adapter (`adapter: postgresql` in `cable.yml`) refusing to run on the `aws_postgresql` ActiveRecord adapter with `The Active Record database must be PostgreSQL in order to use the PostgreSQL Action Cable storage adapter`, which also stopped Turbo Stream broadcasts ([PR #197](https://github.com/aws/aws-advanced-ruby-driver-wrapper/pull/197)).
|
|
17
|
+
- Fixed the `aws_postgresql` and `aws_mysql2` ActiveRecord adapters raising `Unknown type` for adapter-specific attribute types and columns. ActiveRecord keys its type registry by the connection's adapter name, and the wrapped adapters registered none of their own, so types such as PostgreSQL `:interval`, `:point`, `:uuid`, array, and range, and MySQL `:unsigned_integer`, could not be resolved. The wrapped adapter names are now aliased to the underlying `postgresql` and `mysql2` adapters for type registration and lookup, so the wrapped adapters resolve the same types as the standard adapters ([PR #200](https://github.com/aws/aws-advanced-ruby-driver-wrapper/pull/200)).
|
|
18
|
+
- **Side effect:** in an application that uses both a standard (`postgresql`/`mysql2`) connection and a wrapped (`aws_postgresql`/`aws_mysql2`) connection, a type registered for one is now shared with the other, because both resolve under the same underlying adapter key.
|
|
19
|
+
- Changed the `aws_postgresql` and `aws_mysql2` ActiveRecord adapters to report the underlying `adapter_name` (`"PostgreSQL"` and `"Mysql2"`) instead of `"AwsPostgreSQL"` and `"AwsMySQL2"`. ActiveRecord's migration compatibility layer gates PostgreSQL- and MySQL-specific behavior on `connection.adapter_name` string comparisons. Legacy versioned migrations (`ActiveRecord::Migration[5.0]` through `[7.0]`) now complete as expected ([PR #200](https://github.com/aws/aws-advanced-ruby-driver-wrapper/pull/200)).
|
|
20
|
+
|
|
21
|
+
## [1.0.0] - 2026-10-05
|
|
7
22
|
|
|
8
23
|
The Amazon Web Services (AWS) Advanced Ruby Driver Wrapper allows an application to take advantage of the features of clustered Aurora databases.
|
|
9
24
|
|
|
@@ -20,4 +35,5 @@ The Amazon Web Services (AWS) Advanced Ruby Driver Wrapper allows an application
|
|
|
20
35
|
- [Initial Connection Strategy Plugin](https://aws.github.io/aws-advanced-wrapper-docs/ruby/using-plugins/initial-connection-strategy).
|
|
21
36
|
- [KMS Encryption Plugin](https://aws.github.io/aws-advanced-wrapper-docs/ruby/using-plugins/kms-encryption).
|
|
22
37
|
|
|
38
|
+
[1.0.1]: https://github.com/aws/aws-advanced-ruby-driver-wrapper/releases/tag/1.0.1
|
|
23
39
|
[1.0.0]: https://github.com/aws/aws-advanced-ruby-driver-wrapper/releases/tag/1.0.0
|
data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_action_cable_postgresql_support.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require_relative '../postgresql'
|
|
18
|
+
|
|
19
|
+
module ActiveRecord
|
|
20
|
+
module ConnectionAdapters
|
|
21
|
+
# Lets Action Cable's PostgreSQL subscription adapter (cable.yml `adapter: postgresql`) run on the
|
|
22
|
+
# aws_postgresql adapter. Action Cable refuses any raw connection that is not a PG::Connection, and
|
|
23
|
+
# aws_postgresql hands out a WrapperPgConnection, which forwards the calls Action Cable makes
|
|
24
|
+
# (exec, escape_identifier, escape_string, wait_for_notify) to the PG::Connection it wraps.
|
|
25
|
+
module AwsActionCablePostgreSQLSupport
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def verify!(pg_conn)
|
|
29
|
+
super unless pg_conn.is_a?(AwsAdvancedRubyDriverWrapper::WrapperPgConnection)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Action Cable runs this hook when its server loads, before it loads a subscription adapter, so the
|
|
36
|
+
# PostgreSQL one is loaded here to patch it. Applications without Action Cable never run the hook.
|
|
37
|
+
ActiveSupport.on_load(:action_cable) do
|
|
38
|
+
require 'action_cable/subscription_adapter/postgresql'
|
|
39
|
+
ActionCable::SubscriptionAdapter::PostgreSQL.prepend(ActiveRecord::ConnectionAdapters::AwsActionCablePostgreSQLSupport)
|
|
40
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require 'active_record'
|
|
18
|
+
require_relative '../services/service_utility'
|
|
19
|
+
require_relative '../host/rds_host_list_provider'
|
|
20
|
+
require_relative '../plugins/blue_green/blue_green_plugin'
|
|
21
|
+
|
|
22
|
+
module ActiveRecord
|
|
23
|
+
module ConnectionAdapters
|
|
24
|
+
# Stops the wrapper's background monitors and Blue/Green status providers when ActiveRecord closes every
|
|
25
|
+
# connection pool, and forgets the state they cached, so that the next connection starts new ones straight away.
|
|
26
|
+
#
|
|
27
|
+
# The monitors keep their own connections to the database, outside ActiveRecord's pools. Closing
|
|
28
|
+
# everything is what ActiveRecord does before another process takes over the database, for example
|
|
29
|
+
# when bin/rails test runs db:test:prepare in a child process to drop and reload the test database,
|
|
30
|
+
# and PostgreSQL refuses to drop a database that a monitor is still connected to. The next
|
|
31
|
+
# connection that needs a monitor starts a new one.
|
|
32
|
+
module AwsConnectionHandler
|
|
33
|
+
def clear_all_connections!(...)
|
|
34
|
+
super
|
|
35
|
+
core = AwsAdvancedRubyDriverWrapper::Services::CoreServices
|
|
36
|
+
core.monitor_service.stop_and_remove_all
|
|
37
|
+
AwsAdvancedRubyDriverWrapper::Plugins::BlueGreen::BlueGreenPlugin.clean_up_providers
|
|
38
|
+
# Without this, connections would keep reading cached state that nothing refreshes any more.
|
|
39
|
+
cached = [AwsAdvancedRubyDriverWrapper::Host::RdsHostListProvider::TOPOLOGY_CACHE_NAME,
|
|
40
|
+
AwsAdvancedRubyDriverWrapper::Plugins::BlueGreen::BlueGreenPlugin::BLUE_GREEN_NAME]
|
|
41
|
+
cached.each { |name| core.storage_service.clear(name) if core.storage_service.registered?(name) }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
ConnectionHandler.prepend(AwsConnectionHandler)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -17,20 +17,31 @@
|
|
|
17
17
|
require 'active_record/connection_adapters/mysql2_adapter'
|
|
18
18
|
require_relative '../mysql'
|
|
19
19
|
require_relative '../errors'
|
|
20
|
-
require_relative '
|
|
20
|
+
require_relative 'type_adapter_alias'
|
|
21
|
+
require_relative 'aws_connection_handler'
|
|
21
22
|
|
|
22
23
|
module ActiveRecord
|
|
23
24
|
module ConnectionAdapters
|
|
24
25
|
class AwsMysql2Adapter < Mysql2Adapter
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# ActiveRecord-only keys
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
# Passes the whole config on, as the parent adapter does: the wrapper takes its own properties out,
|
|
27
|
+
# and mysql2 reads the client options it knows (including the FOUND_ROWS flag the parent adds) and
|
|
28
|
+
# ignores the ActiveRecord-only keys.
|
|
29
|
+
#
|
|
30
|
+
# Translates connection errors the same way the parent adapter does, so ActiveRecord can tell a
|
|
31
|
+
# missing database (which db:prepare creates) or rejected credentials from other failures.
|
|
32
32
|
def self.new_client(config)
|
|
33
|
-
AwsAdvancedRubyDriverWrapper::WrapperMysql2Client.new(**config
|
|
33
|
+
AwsAdvancedRubyDriverWrapper::WrapperMysql2Client.new(**config)
|
|
34
|
+
rescue ::Mysql2::Error => e
|
|
35
|
+
case e.error_number
|
|
36
|
+
when ER_BAD_DB_ERROR
|
|
37
|
+
raise ActiveRecord::NoDatabaseError.db_error(config[:database])
|
|
38
|
+
when ER_DBACCESS_DENIED_ERROR, ER_ACCESS_DENIED_ERROR
|
|
39
|
+
raise ActiveRecord::DatabaseConnectionError.username_error(config[:username])
|
|
40
|
+
when ER_CONN_HOST_ERROR, ER_UNKNOWN_HOST_ERROR
|
|
41
|
+
raise ActiveRecord::DatabaseConnectionError.hostname_error(config[:host])
|
|
42
|
+
else
|
|
43
|
+
raise ActiveRecord::ConnectionNotEstablished, e.message
|
|
44
|
+
end
|
|
34
45
|
end
|
|
35
46
|
|
|
36
47
|
def connect
|
|
@@ -53,6 +64,9 @@ module ActiveRecord
|
|
|
53
64
|
return super unless exception.is_a?(AwsAdvancedRubyDriverWrapper::Errors::AwsError)
|
|
54
65
|
|
|
55
66
|
if exception.needs_reconfiguration
|
|
67
|
+
# The wrapper has reconnected this connection object to a new physical connection, where the
|
|
68
|
+
# statements prepared on the old one do not exist. Forget them so they are prepared again.
|
|
69
|
+
clear_cache!(new_connection: true)
|
|
56
70
|
configure_connection
|
|
57
71
|
exception
|
|
58
72
|
elsif exception.is_a?(AwsAdvancedRubyDriverWrapper::Errors::FailoverFailedError)
|
|
@@ -17,39 +17,67 @@
|
|
|
17
17
|
require 'active_record/connection_adapters/postgresql_adapter'
|
|
18
18
|
require_relative '../postgresql'
|
|
19
19
|
require_relative '../errors'
|
|
20
|
-
require_relative '
|
|
20
|
+
require_relative 'type_adapter_alias'
|
|
21
|
+
require_relative 'aws_connection_handler'
|
|
22
|
+
require_relative 'aws_action_cable_postgresql_support'
|
|
21
23
|
|
|
22
24
|
module ActiveRecord
|
|
23
25
|
module ConnectionAdapters
|
|
24
26
|
class AwsPostgreSQLAdapter < PostgreSQLAdapter
|
|
25
|
-
ADAPTER_NAME = 'AwsPostgreSQL'
|
|
26
|
-
|
|
27
|
-
# ActiveRecord-only keys that should not be passed to the wrapper or native driver.
|
|
28
|
-
AR_ONLY_KEYS = (AwsAdvancedRubyDriverWrapper::Utils::AR_COMMON_KEYS + %i[advisory_locks schema_search_path]).freeze
|
|
29
|
-
|
|
30
27
|
# ActiveRecord uses :username and :database, but the PG gem expects :user and :dbname.
|
|
31
28
|
# The parent adapter translates these in @connection_parameters, but also strips
|
|
32
29
|
# non-PG keys (including wrapper properties) via slice!. Since we rebuild from @config
|
|
33
30
|
# to preserve wrapper properties, we need to redo this translation ourselves.
|
|
34
31
|
AR_TO_PG_KEY_MAP = { username: :user, database: :dbname }.freeze
|
|
35
32
|
|
|
33
|
+
# The connection options PG accepts, the same set the parent adapter forwards.
|
|
34
|
+
PG_CONNECTION_KEYS = (PG::Connection.conndefaults_hash.keys + [:requiressl]).to_set.freeze
|
|
35
|
+
|
|
36
36
|
def initialize(...)
|
|
37
37
|
# Capture the full config before the parent's initialize strips it
|
|
38
38
|
# down to only PG-recognized keys in @connection_parameters.
|
|
39
39
|
super
|
|
40
40
|
@connection_broken = false
|
|
41
|
-
|
|
42
|
-
wrapper_config = @config.compact.except(*AR_ONLY_KEYS)
|
|
41
|
+
wrapper_config = @config.compact
|
|
43
42
|
# Remap ActiveRecord key names to PG gem key names.
|
|
44
43
|
AR_TO_PG_KEY_MAP.each { |ar_key, pg_key| wrapper_config[pg_key] = wrapper_config.delete(ar_key) if wrapper_config.key?(ar_key) }
|
|
45
|
-
|
|
44
|
+
# Keep only what PG or the wrapper understands. Every other key is an ActiveRecord setting,
|
|
45
|
+
# and PG rejects any connection option it does not recognize.
|
|
46
|
+
@wrapper_config = wrapper_config.select { |key, _| self.class.forwarded_key?(key) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Boolean] whether the config key is passed on to the wrapper connection
|
|
50
|
+
def self.forwarded_key?(key)
|
|
51
|
+
PG_CONNECTION_KEYS.include?(key) ||
|
|
52
|
+
AwsAdvancedRubyDriverWrapper::PropertyDefinition.wrapper_property?(key) ||
|
|
53
|
+
AwsAdvancedRubyDriverWrapper::PropertyDefinition::KNOWN_PREFIXES.any? { |prefix| key.to_s.start_with?(prefix) }
|
|
46
54
|
end
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
# Resolve native database types from the wrapped PostgreSQLAdapter, overriding only :datetime.
|
|
57
|
+
# PostgreSQLAdapter builds its type hash from two things: the shared NATIVE_DATABASE_TYPES
|
|
58
|
+
# constant and the per-class datetime_type class_attribute. Delegating to the parent keeps a
|
|
59
|
+
# single source of truth, but the parent reads *its own* datetime_type, so this adapter's
|
|
60
|
+
# datetime_type (e.g. :timestamptz) must be re-applied here or it is silently ignored and
|
|
61
|
+
# t.datetime columns are created as plain timestamp.
|
|
62
|
+
def self.native_database_types
|
|
63
|
+
superclass.native_database_types.merge(datetime: NATIVE_DATABASE_TYPES[datetime_type])
|
|
64
|
+
end
|
|
49
65
|
|
|
50
66
|
# Note that this config includes wrapper properties.
|
|
67
|
+
#
|
|
68
|
+
# Translates connection errors the same way the parent adapter does, so ActiveRecord can tell a
|
|
69
|
+
# missing database (which db:prepare creates) or rejected credentials from other failures.
|
|
51
70
|
def self.new_client(config)
|
|
52
71
|
AwsAdvancedRubyDriverWrapper::WrapperPgConnection.new(**config)
|
|
72
|
+
rescue ::PG::Error => e
|
|
73
|
+
dbname, user, host = config.values_at(:dbname, :user, :host)
|
|
74
|
+
# The postgres maintenance database always exists, so a failure there is not a missing database.
|
|
75
|
+
raise ActiveRecord::ConnectionNotEstablished, e.message if dbname == 'postgres'
|
|
76
|
+
raise ActiveRecord::NoDatabaseError.db_error(dbname) if dbname && e.message.include?(dbname)
|
|
77
|
+
raise ActiveRecord::DatabaseConnectionError.username_error(user) if user && e.message.include?(user)
|
|
78
|
+
raise ActiveRecord::DatabaseConnectionError.hostname_error(host) if host && e.message.include?(host)
|
|
79
|
+
|
|
80
|
+
raise ActiveRecord::ConnectionNotEstablished, e.message
|
|
53
81
|
end
|
|
54
82
|
|
|
55
83
|
def connect
|
|
@@ -71,10 +99,23 @@ module ActiveRecord
|
|
|
71
99
|
super
|
|
72
100
|
end
|
|
73
101
|
|
|
102
|
+
# Used by db:drop, db:reset, db:test:prepare and db:purge. ActiveRecord disconnects its own
|
|
103
|
+
# connections first, but the topology monitor shared by this cluster's connections, and the Blue/Green
|
|
104
|
+
# status providers, keep their own connections to the database, and PostgreSQL refuses to drop a
|
|
105
|
+
# database other sessions are using.
|
|
106
|
+
def drop_database(name)
|
|
107
|
+
raw_connection.stop_topology_monitor
|
|
108
|
+
AwsAdvancedRubyDriverWrapper::Plugins::BlueGreen::BlueGreenPlugin.clean_up_providers
|
|
109
|
+
super
|
|
110
|
+
end
|
|
111
|
+
|
|
74
112
|
def translate_exception(exception, message:, sql:, binds:)
|
|
75
113
|
return super unless exception.is_a?(AwsAdvancedRubyDriverWrapper::Errors::AwsError)
|
|
76
114
|
|
|
77
115
|
if exception.needs_reconfiguration
|
|
116
|
+
# The wrapper has reconnected this connection object to a new physical connection, where the
|
|
117
|
+
# statements prepared on the old one do not exist. Forget them so they are prepared again.
|
|
118
|
+
clear_cache!(new_connection: true)
|
|
78
119
|
configure_connection
|
|
79
120
|
exception
|
|
80
121
|
elsif exception.is_a?(AwsAdvancedRubyDriverWrapper::Errors::FailoverFailedError)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require 'active_record'
|
|
18
|
+
|
|
19
|
+
module AwsAdvancedRubyDriverWrapper
|
|
20
|
+
# ActiveRecord's adapter-specific type registry (ActiveRecord::Type) keys every type
|
|
21
|
+
# registration and lookup by the *config adapter name*. The wrapped adapters register nothing of
|
|
22
|
+
# their own: they rely entirely on the vanilla PostgreSQL/Mysql2 adapters, which register their
|
|
23
|
+
# OID / native types under adapter: :postgresql and adapter: :mysql2. A lookup for an
|
|
24
|
+
# :aws_postgresql / :aws_mysql2 connection would therefore find none of them and raise
|
|
25
|
+
# "Unknown type" for things like :interval, :point, array/range columns, or MySQL's
|
|
26
|
+
# :unsigned_integer.
|
|
27
|
+
#
|
|
28
|
+
# Rather than copy the parent's registrations (which misses types registered after the adapter
|
|
29
|
+
# loads, needs a duplicate guard, and reaches into the registry's private internals), alias the
|
|
30
|
+
# wrapper adapter names to their parents inside the registry's own register/add_modifier/lookup.
|
|
31
|
+
# Every registration and every lookup — including the ones ActiveRecord routes through
|
|
32
|
+
# Type.adapter_name_from — passes through the registry, so normalizing the adapter there makes the
|
|
33
|
+
# wrapped adapters resolve the exact same types as the vanilla ones, for all current and future
|
|
34
|
+
# types, across Rails 7.2–8.1.
|
|
35
|
+
#
|
|
36
|
+
# Side effect: in an app that uses BOTH a plain postgresql and an aws_postgresql connection, a
|
|
37
|
+
# type explicitly registered for :aws_postgresql is normalized to :postgresql and so also applies
|
|
38
|
+
# to the plain postgresql connections (and vice versa). This is documented in the CHANGELOG.
|
|
39
|
+
module TypeAdapterAlias
|
|
40
|
+
ADAPTER_ALIASES = { aws_postgresql: :postgresql, aws_mysql2: :mysql2 }.freeze
|
|
41
|
+
|
|
42
|
+
# Prepended onto ActiveRecord::Type::AdapterSpecificRegistry. Normalizing the adapter in lookup
|
|
43
|
+
# covers both the framework path and a direct Type.lookup(..., adapter: :aws_postgresql); any
|
|
44
|
+
# type registered explicitly for a wrapper adapter lands under the parent's key.
|
|
45
|
+
module RegistryAlias
|
|
46
|
+
def register(type_name, klass = nil, adapter: nil, **, &)
|
|
47
|
+
super(type_name, klass, adapter: ADAPTER_ALIASES.fetch(adapter, adapter), **, &)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def add_modifier(options, klass, adapter: nil, **args)
|
|
51
|
+
super(options, klass, adapter: ADAPTER_ALIASES.fetch(adapter, adapter), **args)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def lookup(symbol, *, adapter: nil, **)
|
|
55
|
+
super(symbol, *, adapter: ADAPTER_ALIASES.fetch(adapter, adapter), **)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
ActiveRecord::Type::AdapterSpecificRegistry.prepend(AwsAdvancedRubyDriverWrapper::TypeAdapterAlias::RegistryAlias)
|
|
@@ -115,6 +115,11 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
115
115
|
raise NotImplementedError
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
+
# Releases a connection inherited from the parent process after a fork, without telling the
|
|
119
|
+
# server: the parent still owns the session. Garbage collection or process exit in the child
|
|
120
|
+
# must not end it.
|
|
121
|
+
def abandon_connection(connection); end
|
|
122
|
+
|
|
118
123
|
def sql_state(_exception)
|
|
119
124
|
nil
|
|
120
125
|
end
|
|
@@ -131,6 +131,16 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
131
131
|
logger.error("Failed to close MySQL connection: #{e.message}")
|
|
132
132
|
end
|
|
133
133
|
|
|
134
|
+
# With automatic_close off, mysql2 invalidates the socket instead of sending COM_QUIT when
|
|
135
|
+
# the client is collected, leaving the parent's session open.
|
|
136
|
+
def abandon_connection(connection)
|
|
137
|
+
return if connection.nil? || connection.closed?
|
|
138
|
+
|
|
139
|
+
connection.automatic_close = false
|
|
140
|
+
rescue StandardError => e
|
|
141
|
+
logger.debug("Failed to abandon MySQL connection: #{e.message}")
|
|
142
|
+
end
|
|
143
|
+
|
|
134
144
|
def sql_state(exception)
|
|
135
145
|
return nil unless exception.respond_to?(:sql_state)
|
|
136
146
|
|
|
@@ -162,6 +162,16 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
162
162
|
logger.error("Failed to close PostgreSQL connection: #{e.message}")
|
|
163
163
|
end
|
|
164
164
|
|
|
165
|
+
# Points the socket at /dev/null, so the Terminate message libpq sends when the connection
|
|
166
|
+
# is finished or collected never reaches the server.
|
|
167
|
+
def abandon_connection(connection)
|
|
168
|
+
return if connection.nil? || connection.finished?
|
|
169
|
+
|
|
170
|
+
connection.socket_io.reopen(IO::NULL)
|
|
171
|
+
rescue StandardError => e
|
|
172
|
+
logger.debug("Failed to abandon PostgreSQL connection: #{e.message}")
|
|
173
|
+
end
|
|
174
|
+
|
|
165
175
|
def sql_state(exception)
|
|
166
176
|
return nil unless exception.is_a?(::PG::Error) && exception.result
|
|
167
177
|
|
|
@@ -131,6 +131,16 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
131
131
|
@instance_monitors_writer_conn.close
|
|
132
132
|
end
|
|
133
133
|
|
|
134
|
+
def abandon_connections
|
|
135
|
+
@monitoring_connection.abandon
|
|
136
|
+
@instance_monitors_writer_conn.abandon
|
|
137
|
+
driver_dialect = @service_container.dialect_service.driver_dialect
|
|
138
|
+
@instance_monitor_connections.each_pair do |host, conn|
|
|
139
|
+
driver_dialect.abandon_connection(conn)
|
|
140
|
+
@instance_monitor_connections.delete(host)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
134
144
|
private
|
|
135
145
|
|
|
136
146
|
# Main monitoring loop.
|
|
@@ -73,6 +73,20 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
73
73
|
|
|
74
74
|
def close; end
|
|
75
75
|
|
|
76
|
+
# Detaches this monitor in a forked child. Its thread did not survive the fork and its
|
|
77
|
+
# connections still belong to the parent, so the thread reference is dropped and the
|
|
78
|
+
# connections are abandoned rather than closed: closing them would end the parent's sessions.
|
|
79
|
+
def release_after_fork
|
|
80
|
+
@stop_flag.make_true
|
|
81
|
+
@thread = nil
|
|
82
|
+
@state.set(MonitorState::STOPPED)
|
|
83
|
+
abandon_connections
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Releases connections inherited from the parent process without closing them on the server.
|
|
87
|
+
# Subclasses that hold connections override this.
|
|
88
|
+
def abandon_connections; end
|
|
89
|
+
|
|
76
90
|
private
|
|
77
91
|
|
|
78
92
|
def run
|
|
@@ -52,6 +52,12 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
52
52
|
def close
|
|
53
53
|
set(nil)
|
|
54
54
|
end
|
|
55
|
+
|
|
56
|
+
# Releases a connection inherited across a fork without closing it on the server. The reference
|
|
57
|
+
# is dropped too, so a later close or set cannot reach the parent's session.
|
|
58
|
+
def abandon
|
|
59
|
+
@driver_dialect.abandon_connection(@connection.get_and_set(nil))
|
|
60
|
+
end
|
|
55
61
|
end
|
|
56
62
|
end
|
|
57
63
|
end
|
|
@@ -130,6 +130,16 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
130
130
|
PROVIDERS.each_key { |k| PROVIDERS.delete(k)&.stop }
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
+
# Forgets the providers inherited by a forked child so the child starts its own on first use.
|
|
134
|
+
# A provider that fails to release is still forgotten, and the rest are released.
|
|
135
|
+
def self.release_providers_after_fork
|
|
136
|
+
PROVIDERS.each_key do |key|
|
|
137
|
+
PROVIDERS.delete(key)&.release_after_fork
|
|
138
|
+
rescue StandardError => e
|
|
139
|
+
AwsAdvancedRubyDriverWrapper.logger.warn("Failed to release Blue/Green status provider #{key} after fork: #{e.message}")
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
133
143
|
private
|
|
134
144
|
|
|
135
145
|
def route_connect(host_info, driver_props, is_initial_connection, pipeline_callable, is_internal: false)
|
|
@@ -93,6 +93,11 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
93
93
|
@monitors.each_value { |m| m&.stop }
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
+
# Detaches the monitors inherited by a forked child without closing the parent's connections.
|
|
97
|
+
def release_after_fork
|
|
98
|
+
@monitors.each_value { |m| m&.release_after_fork }
|
|
99
|
+
end
|
|
100
|
+
|
|
96
101
|
def log_switchover_final_summary
|
|
97
102
|
return unless switchover_finalized?
|
|
98
103
|
|
|
@@ -79,6 +79,12 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
79
79
|
storage_service.clear(SECRETS_MANAGER_CACHE_NAME)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
+
# Forgets fetches inherited by a forked child. A fetch running at fork time lost its thread,
|
|
83
|
+
# so its future never resolves and would otherwise be returned to every later fetch for its key.
|
|
84
|
+
def release_pending_refreshes_after_fork
|
|
85
|
+
@pending_refreshes.clear
|
|
86
|
+
end
|
|
87
|
+
|
|
82
88
|
# The secret's value depends on which secret is read, from which region and endpoint, and
|
|
83
89
|
# with which AWS credentials, so all of them are part of the key. Connections that differ in
|
|
84
90
|
# any of them neither share a cached secret nor wait on each other's fetch.
|
|
@@ -244,7 +250,9 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
244
250
|
else
|
|
245
251
|
Concurrent::Promises.future_on(:io) { fetch_and_store_secret(nil, credentials) }
|
|
246
252
|
end
|
|
247
|
-
|
|
253
|
+
# value! returns nil instead of raising when the timeout elapses; a completed fetch always
|
|
254
|
+
# returns an entry or raises.
|
|
255
|
+
future.value!(SYNC_FETCH_TIMEOUT_SEC) || raise(Timeout::Error)
|
|
248
256
|
rescue Concurrent::CancelledOperationError, Timeout::Error
|
|
249
257
|
raise Errors::SecretsManagerAuthError,
|
|
250
258
|
"Timed out fetching secret after #{SYNC_FETCH_TIMEOUT_SEC}s"
|
|
@@ -224,6 +224,14 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
224
224
|
|
|
225
225
|
alias finish close
|
|
226
226
|
|
|
227
|
+
# Stops the background topology monitor of this connection's cluster, which closes the connections it
|
|
228
|
+
# holds to the database it was started for. The next connection that needs topology starts a new one.
|
|
229
|
+
# PostgreSQL refuses to drop a database that other sessions are connected to, so this has to happen
|
|
230
|
+
# before dropping the database the monitor was started for.
|
|
231
|
+
def stop_topology_monitor
|
|
232
|
+
@service_container.host_service.host_list_provider&.stop_monitor
|
|
233
|
+
end
|
|
234
|
+
|
|
227
235
|
# Resets the connection through the pipeline and returns this wrapper, so the reset connection stays
|
|
228
236
|
# usable through it. The driver's own reset tears down and re-establishes the underlying socket, which
|
|
229
237
|
# clears any server-side session state, so the tracked session state is reset to match.
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
|
+
require 'concurrent'
|
|
17
18
|
require_relative '../monitoring/monitor_state'
|
|
18
19
|
require_relative '../logging'
|
|
19
20
|
require_relative '../utils/storage/sliding_expiration_cache'
|
|
@@ -36,7 +37,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
36
37
|
def initialize(event_publisher:)
|
|
37
38
|
@caches = {}
|
|
38
39
|
@lock = Mutex.new
|
|
39
|
-
@running = true
|
|
40
|
+
@running = Concurrent::AtomicBoolean.new(true)
|
|
40
41
|
@cleanup_thread = start_cleanup_thread
|
|
41
42
|
event_publisher.subscribe(
|
|
42
43
|
self,
|
|
@@ -112,7 +113,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
112
113
|
end
|
|
113
114
|
|
|
114
115
|
def shutdown(grace_period:)
|
|
115
|
-
@running
|
|
116
|
+
@running.make_false
|
|
116
117
|
begin
|
|
117
118
|
@cleanup_thread&.wakeup
|
|
118
119
|
rescue ThreadError
|
|
@@ -122,6 +123,24 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
122
123
|
stop_and_remove_all
|
|
123
124
|
end
|
|
124
125
|
|
|
126
|
+
# Resets this service in a forked child. Monitor threads do not survive a fork, so the inherited
|
|
127
|
+
# monitors are detached (keeping the parent's connections open) and forgotten, letting
|
|
128
|
+
# run_if_absent start live ones. A monitor that fails to detach is still forgotten. The cleanup
|
|
129
|
+
# thread is restarted unless it is still alive, so calling this again does not start a second one.
|
|
130
|
+
def restart_after_fork
|
|
131
|
+
@lock.synchronize { @caches.values }.each do |container|
|
|
132
|
+
container.cache.entries.each_key do |key|
|
|
133
|
+
container.cache.remove(key)&.release_after_fork
|
|
134
|
+
rescue StandardError => e
|
|
135
|
+
logger.warn("Failed to release monitor #{key} after fork: #{e.message}")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
return if @cleanup_thread&.alive?
|
|
139
|
+
|
|
140
|
+
@running.make_true
|
|
141
|
+
@cleanup_thread = start_cleanup_thread
|
|
142
|
+
end
|
|
143
|
+
|
|
125
144
|
# Processes events from the event publisher.
|
|
126
145
|
# @param event [Event] the event to process.
|
|
127
146
|
def process_event(event)
|
|
@@ -142,7 +161,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
142
161
|
|
|
143
162
|
def start_cleanup_thread
|
|
144
163
|
thread = Thread.new do
|
|
145
|
-
while @running
|
|
164
|
+
while @running.true?
|
|
146
165
|
sleep(CLEANUP_INTERVAL_SEC)
|
|
147
166
|
run_cleanup
|
|
148
167
|
end
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
|
+
require 'concurrent'
|
|
17
18
|
require_relative '../../logging'
|
|
18
19
|
|
|
19
20
|
module AwsAdvancedRubyDriverWrapper
|
|
@@ -23,10 +24,11 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
23
24
|
# Batches deduplicate events via Set semantics (eql?/hash).
|
|
24
25
|
#
|
|
25
26
|
# Public API:
|
|
26
|
-
# subscribe(subscriber, event_classes)
|
|
27
|
-
# unsubscribe(subscriber, event_classes)
|
|
28
|
-
# publish(event)
|
|
29
|
-
# release_resources
|
|
27
|
+
# subscribe(subscriber, event_classes) - register for event types
|
|
28
|
+
# unsubscribe(subscriber, event_classes) - deregister
|
|
29
|
+
# publish(event) - deliver immediate or queue batched
|
|
30
|
+
# release_resources - stop background thread
|
|
31
|
+
# restart_after_fork - restart background thread in a forked child
|
|
30
32
|
class BatchingEventPublisher
|
|
31
33
|
include Logging
|
|
32
34
|
|
|
@@ -37,7 +39,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
37
39
|
@subscribers = {}
|
|
38
40
|
@event_queue = Set.new
|
|
39
41
|
@lock = Mutex.new
|
|
40
|
-
@running = true
|
|
42
|
+
@running = Concurrent::AtomicBoolean.new(true)
|
|
41
43
|
@thread = start_publishing_thread
|
|
42
44
|
end
|
|
43
45
|
|
|
@@ -70,7 +72,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
70
72
|
end
|
|
71
73
|
|
|
72
74
|
def release_resources
|
|
73
|
-
@
|
|
75
|
+
@running.make_false
|
|
74
76
|
begin
|
|
75
77
|
@thread&.wakeup
|
|
76
78
|
rescue ThreadError
|
|
@@ -79,11 +81,20 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
79
81
|
@thread&.join(@message_interval_sec)
|
|
80
82
|
end
|
|
81
83
|
|
|
84
|
+
# Restarts the publishing thread in a forked child, where it no longer runs. A thread that is
|
|
85
|
+
# still alive is kept, so calling this again does not start a second one.
|
|
86
|
+
def restart_after_fork
|
|
87
|
+
return if @thread&.alive?
|
|
88
|
+
|
|
89
|
+
@running.make_true
|
|
90
|
+
@thread = start_publishing_thread
|
|
91
|
+
end
|
|
92
|
+
|
|
82
93
|
private
|
|
83
94
|
|
|
84
95
|
def start_publishing_thread
|
|
85
96
|
thread = Thread.new do
|
|
86
|
-
while @
|
|
97
|
+
while @running.true?
|
|
87
98
|
sleep(@message_interval_sec)
|
|
88
99
|
send_messages
|
|
89
100
|
end
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
|
+
require 'concurrent'
|
|
17
18
|
require_relative 'expiration_cache'
|
|
18
19
|
require_relative '../../logging'
|
|
19
20
|
require_relative '../events/data_access_event'
|
|
@@ -34,7 +35,8 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
34
35
|
@caches = {}
|
|
35
36
|
@event_publisher = event_publisher
|
|
36
37
|
@lock = Mutex.new
|
|
37
|
-
@running = true
|
|
38
|
+
@running = Concurrent::AtomicBoolean.new(true)
|
|
39
|
+
@cleanup_interval = cleanup_interval
|
|
38
40
|
@cleanup_thread = start_cleanup_thread(cleanup_interval)
|
|
39
41
|
end
|
|
40
42
|
|
|
@@ -107,6 +109,12 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
107
109
|
fetch_cache!(name).remove(key)
|
|
108
110
|
end
|
|
109
111
|
|
|
112
|
+
# @param name [Symbol] cache name.
|
|
113
|
+
# @return [Boolean] whether a cache is registered under the name
|
|
114
|
+
def registered?(name)
|
|
115
|
+
@lock.synchronize { @caches.key?(name) }
|
|
116
|
+
end
|
|
117
|
+
|
|
110
118
|
# Clears all items for a given name.
|
|
111
119
|
# @param name [Symbol] registered cache name.
|
|
112
120
|
def clear(name)
|
|
@@ -127,7 +135,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
127
135
|
|
|
128
136
|
# Stops the cleanup thread.
|
|
129
137
|
def shutdown
|
|
130
|
-
@running
|
|
138
|
+
@running.make_false
|
|
131
139
|
begin
|
|
132
140
|
@cleanup_thread&.wakeup
|
|
133
141
|
rescue ThreadError
|
|
@@ -136,6 +144,15 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
136
144
|
@cleanup_thread&.join(5)
|
|
137
145
|
end
|
|
138
146
|
|
|
147
|
+
# Restarts the cleanup thread in a forked child, where it no longer runs. A thread that is
|
|
148
|
+
# still alive is kept, so calling this again does not start a second one.
|
|
149
|
+
def restart_after_fork
|
|
150
|
+
return if @cleanup_thread&.alive?
|
|
151
|
+
|
|
152
|
+
@running.make_true
|
|
153
|
+
@cleanup_thread = start_cleanup_thread(@cleanup_interval)
|
|
154
|
+
end
|
|
155
|
+
|
|
139
156
|
private
|
|
140
157
|
|
|
141
158
|
def fetch_cache!(name)
|
|
@@ -147,7 +164,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
147
164
|
|
|
148
165
|
def start_cleanup_thread(interval)
|
|
149
166
|
thread = Thread.new do
|
|
150
|
-
while @running
|
|
167
|
+
while @running.true?
|
|
151
168
|
sleep(interval)
|
|
152
169
|
remove_expired_items
|
|
153
170
|
end
|
|
@@ -73,6 +73,38 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
73
73
|
Services::HostService.clear_id_cache
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
# Runs in a forked child (see ForkHook). Only the forking thread survives a fork, so every background
|
|
77
|
+
# thread is gone and the inherited monitors look alive but never refresh. The monitors and Blue/Green
|
|
78
|
+
# providers are forgotten without closing their connections, which the parent still uses, Secrets
|
|
79
|
+
# Manager fetches that were in flight are forgotten, and the shared services restart their threads.
|
|
80
|
+
# Cached data such as topology stays valid and is kept.
|
|
81
|
+
def self.after_fork
|
|
82
|
+
Plugins::BlueGreen::BlueGreenPlugin.release_providers_after_fork
|
|
83
|
+
Plugins::SecretsManagerPlugin.release_pending_refreshes_after_fork if defined?(Plugins::SecretsManagerPlugin)
|
|
84
|
+
return unless defined?(Services::CoreServices)
|
|
85
|
+
|
|
86
|
+
Services::CoreServices.monitor_service.restart_after_fork
|
|
87
|
+
Services::CoreServices.event_publisher.restart_after_fork
|
|
88
|
+
Services::CoreServices.storage_service.restart_after_fork
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Process._fork is the single entry point for Kernel#fork, Process.fork and IO.popen('-'), so
|
|
92
|
+
# hooking it covers every way an application server or job runner forks a worker. An error in the
|
|
93
|
+
# child is logged rather than raised: raising here would fail the application's own fork call.
|
|
94
|
+
module ForkHook
|
|
95
|
+
def _fork
|
|
96
|
+
pid = super
|
|
97
|
+
if pid.zero?
|
|
98
|
+
begin
|
|
99
|
+
AwsAdvancedRubyDriverWrapper.after_fork
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
AwsAdvancedRubyDriverWrapper.logger.error("Failed to reset the wrapper's background services after fork: #{e.message}")
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
pid
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
76
108
|
def self.release_resources(grace_period_sec: 5)
|
|
77
109
|
require_relative 'aws_advanced_ruby_driver_wrapper/services/service_utility'
|
|
78
110
|
Services::CoreServices.monitor_service.shutdown(grace_period: grace_period_sec)
|
|
@@ -88,6 +120,8 @@ end
|
|
|
88
120
|
|
|
89
121
|
at_exit { AwsAdvancedRubyDriverWrapper.shutdown }
|
|
90
122
|
|
|
123
|
+
Process.singleton_class.prepend(AwsAdvancedRubyDriverWrapper::ForkHook)
|
|
124
|
+
|
|
91
125
|
# Register adapters with ActiveRecord via a lazy-load hook so the require order between this file and
|
|
92
126
|
# ActiveRecord does not matter. The block runs the first time ActiveRecord::Base is referenced, whether
|
|
93
127
|
# ActiveRecord is loaded before or after this file. The register call is itself lazy — each adapter file
|
|
@@ -101,6 +135,8 @@ end
|
|
|
101
135
|
|
|
102
136
|
if defined?(ActiveSupport) && ActiveSupport.respond_to?(:on_load)
|
|
103
137
|
ActiveSupport.on_load(:active_record) do
|
|
138
|
+
require_relative 'aws_advanced_ruby_driver_wrapper/active_record/type_adapter_alias'
|
|
139
|
+
|
|
104
140
|
ActiveRecord::ConnectionAdapters.register(
|
|
105
141
|
'aws_postgresql',
|
|
106
142
|
'ActiveRecord::ConnectionAdapters::AwsPostgreSQLAdapter',
|
|
@@ -114,3 +150,5 @@ if defined?(ActiveSupport) && ActiveSupport.respond_to?(:on_load)
|
|
|
114
150
|
)
|
|
115
151
|
end
|
|
116
152
|
end
|
|
153
|
+
|
|
154
|
+
require_relative 'aws_advanced_ruby_driver_wrapper/active_record/type_adapter_alias' if defined?(ActiveRecord)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aws_advanced_ruby_driver_wrapper
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amazon Web Services
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: concurrent-ruby
|
|
@@ -44,8 +44,11 @@ files:
|
|
|
44
44
|
- THIRD-PARTY-LICENSES
|
|
45
45
|
- aws_advanced_ruby_driver_wrapper.gemspec
|
|
46
46
|
- lib/aws_advanced_ruby_driver_wrapper.rb
|
|
47
|
+
- lib/aws_advanced_ruby_driver_wrapper/active_record/aws_action_cable_postgresql_support.rb
|
|
48
|
+
- lib/aws_advanced_ruby_driver_wrapper/active_record/aws_connection_handler.rb
|
|
47
49
|
- lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb
|
|
48
50
|
- lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb
|
|
51
|
+
- lib/aws_advanced_ruby_driver_wrapper/active_record/type_adapter_alias.rb
|
|
49
52
|
- lib/aws_advanced_ruby_driver_wrapper/custom_configuration.rb
|
|
50
53
|
- lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_mysql_dialect.rb
|
|
51
54
|
- lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_pg_dialect.rb
|
|
@@ -152,7 +155,6 @@ files:
|
|
|
152
155
|
- lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb
|
|
153
156
|
- lib/aws_advanced_ruby_driver_wrapper/services/session_state_service.rb
|
|
154
157
|
- lib/aws_advanced_ruby_driver_wrapper/utils/accessible_regions.rb
|
|
155
|
-
- lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb
|
|
156
158
|
- lib/aws_advanced_ruby_driver_wrapper/utils/aurora_topology_utils.rb
|
|
157
159
|
- lib/aws_advanced_ruby_driver_wrapper/utils/aws_credentials_utils.rb
|
|
158
160
|
- lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
-
#
|
|
5
|
-
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
-
# You may not use this file except in compliance with the License.
|
|
7
|
-
# You may obtain a copy of the License at
|
|
8
|
-
#
|
|
9
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
#
|
|
11
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
# See the License for the specific language governing permissions and
|
|
15
|
-
# limitations under the License.
|
|
16
|
-
|
|
17
|
-
module AwsAdvancedRubyDriverWrapper
|
|
18
|
-
module Utils
|
|
19
|
-
# ActiveRecord-only keys that should not be passed to the wrapper or native driver.
|
|
20
|
-
AR_COMMON_KEYS = %i[
|
|
21
|
-
adapter pool checkout_timeout idle_timeout reaping_frequency
|
|
22
|
-
variables prepared_statements encoding reconnect socket flags
|
|
23
|
-
].freeze
|
|
24
|
-
end
|
|
25
|
-
end
|