aws_advanced_ruby_driver_wrapper 1.0.0
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 +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +175 -0
- data/NOTICE +1 -0
- data/README.md +168 -0
- data/THIRD-PARTY-LICENSES +473 -0
- data/aws_advanced_ruby_driver_wrapper.gemspec +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/custom_configuration.rb +58 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_mysql_dialect.rb +103 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_pg_dialect.rb +124 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/dialect_codes.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_mysql_dialect.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_pg_dialect.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_mysql_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/mysql_dialect.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/pg_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_mysql_dialect.rb +88 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/unknown_dialect.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/utils/dialect_utils.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +154 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +55 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +165 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +201 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +80 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/pg_error_handler.rb +126 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +59 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/connection_string_host_list_provider.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/global_aurora_host_list_provider.rb +65 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability.rb +24 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability_strategy.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_info.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_role.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/random_host_selector.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/rds_host_list_provider.rb +206 -0
- data/lib/aws_advanced_ruby_driver_wrapper/logging.rb +110 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +709 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/global_cluster_topology_monitor.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +57 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_state.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +429 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +205 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +132 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/iam_host_tracker.rb +84 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interim_status.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interval_rate.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase.rb +69 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_event_log.rb +85 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_time_info.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/role.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/reject_connect_routing.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +136 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +53 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +244 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_info.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +564 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +414 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_state.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_timer.rb +46 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +266 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +158 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/info.rb +111 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/member_list_type.rb +31 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/role.rb +45 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_mode.rb +43 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +467 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_mode.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +403 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +485 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/audit_logger.rb +157 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_cipher.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_encryption_config.rb +61 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/connection_source.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/data_key_cache.rb +220 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_algorithm.rb +75 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_config.rb +146 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_service.rb +391 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/error_context.rb +198 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/errors.rb +259 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +435 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +378 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_metadata.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +890 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_utility.rb +281 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/metadata_manager.rb +332 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sanitizer.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_name.rb +70 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_validator.rb +211 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql_runner.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/type_marker.rb +109 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +358 -0
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +659 -0
- data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +409 -0
- data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +122 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/connection_service.rb +143 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +267 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +199 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +186 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_call_context.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +273 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +78 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/session_state_service.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/accessible_regions.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aurora_topology_utils.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aws_credentials_utils.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +368 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/conversion_utils.rb +51 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +119 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/data_access_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/monitor_reset_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +185 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/host_list_utils.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +112 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/multi_az_topology_utils.rb +117 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +641 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +502 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_type.rb +35 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint_parser.rb +50 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +139 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_url_type.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_utils.rb +575 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +153 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_encoding.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_method_analyzer.rb +195 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/cache_entry.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/expiration_cache.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/sliding_expiration_cache.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +172 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +127 -0
- data/lib/aws_advanced_ruby_driver_wrapper/version.rb +19 -0
- data/lib/aws_advanced_ruby_driver_wrapper/wrapper_property.rb +64 -0
- data/lib/aws_advanced_ruby_driver_wrapper.rb +116 -0
- metadata +227 -0
|
@@ -0,0 +1,709 @@
|
|
|
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 'monitor'
|
|
18
|
+
require_relative 'monitor_connection'
|
|
19
|
+
require_relative '../host/host_role'
|
|
20
|
+
require_relative '../host/host_availability'
|
|
21
|
+
require_relative '../utils/events/monitor_reset_event'
|
|
22
|
+
require_relative '../utils/rds_utils'
|
|
23
|
+
require_relative '../property_definition'
|
|
24
|
+
require 'timeout'
|
|
25
|
+
|
|
26
|
+
module AwsAdvancedRubyDriverWrapper
|
|
27
|
+
module Monitoring
|
|
28
|
+
# Continuously monitors cluster topology, caches it in StorageService, and provides
|
|
29
|
+
# fast writer re-discovery during failover via parallel host probing (panic mode).
|
|
30
|
+
class ClusterTopologyMonitor < Monitor
|
|
31
|
+
TERMINATION_TIMEOUT_SEC = 30.0
|
|
32
|
+
HIGH_REFRESH_DURATION_SEC = 30.0
|
|
33
|
+
STABLE_TOPOLOGIES_DURATION_SEC = 15.0
|
|
34
|
+
TOPOLOGY_CACHE_NAME = :topology
|
|
35
|
+
INSTANCE_MONITOR_SLEEP_SEC = 0.1
|
|
36
|
+
INITIAL_BACKOFF_SEC = 0.1
|
|
37
|
+
MAX_BACKOFF_SEC = 10
|
|
38
|
+
|
|
39
|
+
# @param service_container [Services::ServiceContainer]
|
|
40
|
+
# @param cluster_id [String]
|
|
41
|
+
# @param instance_template [Host::HostInfo]
|
|
42
|
+
# @param topology_utils [Object] responds to #query_topology, #writer_instance?
|
|
43
|
+
# @param monitoring_driver_props [Hash] driver props with prefixed driver overrides merged in
|
|
44
|
+
# @param monitoring_wrapper_props [Hash] wrapper prop overrides extracted from prefixed props
|
|
45
|
+
def initialize(
|
|
46
|
+
service_container:,
|
|
47
|
+
cluster_id:,
|
|
48
|
+
instance_template:,
|
|
49
|
+
topology_utils:,
|
|
50
|
+
monitoring_driver_props:,
|
|
51
|
+
monitoring_wrapper_props: {}
|
|
52
|
+
)
|
|
53
|
+
super(termination_timeout_sec: TERMINATION_TIMEOUT_SEC)
|
|
54
|
+
|
|
55
|
+
@service_container = service_container
|
|
56
|
+
@cluster_id = cluster_id
|
|
57
|
+
@instance_template = instance_template
|
|
58
|
+
@topology_utils = topology_utils
|
|
59
|
+
@monitoring_driver_props = monitoring_driver_props
|
|
60
|
+
@monitoring_wrapper_props = monitoring_wrapper_props
|
|
61
|
+
|
|
62
|
+
# Apply default socket/connect timeouts for monitoring connections if not set by user.
|
|
63
|
+
service_container.dialect_service.driver_dialect.apply_monitoring_defaults(@monitoring_driver_props)
|
|
64
|
+
|
|
65
|
+
props = service_container.connection_service.wrapper_props
|
|
66
|
+
@refresh_rate_sec = PropertyDefinition::CLUSTER_TOPOLOGY_REFRESH_RATE_SEC.get_float(props)
|
|
67
|
+
@high_refresh_rate_sec = PropertyDefinition::CLUSTER_TOPOLOGY_HIGH_REFRESH_RATE_SEC.get_float(props)
|
|
68
|
+
@max_instance_monitors = PropertyDefinition::CLUSTER_TOPOLOGY_MAX_INSTANCE_MONITORS.get_int(props)
|
|
69
|
+
|
|
70
|
+
@monitoring_connection = MonitorConnection.new(service_container.dialect_service.driver_dialect)
|
|
71
|
+
@monitoring_conn_lock = Mutex.new
|
|
72
|
+
@writer_info = nil
|
|
73
|
+
@verified_writer = false
|
|
74
|
+
@high_refresh_end_time = 0
|
|
75
|
+
|
|
76
|
+
@topology_mutex = Mutex.new
|
|
77
|
+
@topology_cv = ConditionVariable.new
|
|
78
|
+
@update_requested = false
|
|
79
|
+
|
|
80
|
+
@instance_monitors = {} # { host_string => Thread }
|
|
81
|
+
@instance_monitor_connections = Concurrent::Map.new # { host_string => raw connection }
|
|
82
|
+
@stop_instance_monitors = false
|
|
83
|
+
@instance_monitors_writer_conn = MonitorConnection.new(service_container.dialect_service.driver_dialect)
|
|
84
|
+
@panic_mutex = Mutex.new
|
|
85
|
+
@instance_monitors_writer_info = nil
|
|
86
|
+
@instance_monitor_topologies = {}
|
|
87
|
+
# Tracks whether all instance monitors have completed at least one work cycle, even if an error occurs.
|
|
88
|
+
# This guards against concluding all instance monitor topologies are stable before all of them have initialized.
|
|
89
|
+
@completed_one_cycle = {}
|
|
90
|
+
@latest_topology = nil
|
|
91
|
+
@stable_start_time = 0
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Forces a topology refresh, ignoring any cached topology. Blocks until updated or raises on timeout.
|
|
95
|
+
# @param verify_writer [Boolean] if true, enters panic mode to re-verify the writer.
|
|
96
|
+
# @param timeout_sec [Float] max time in seconds to wait for the update.
|
|
97
|
+
# @return [Array<Host::HostInfo>] the updated topology.
|
|
98
|
+
# @raise [Timeout::Error] if the topology is not updated within timeout_sec.
|
|
99
|
+
def force_refresh(verify_writer, timeout_sec)
|
|
100
|
+
if verify_writer
|
|
101
|
+
if @monitoring_conn_lock.try_lock
|
|
102
|
+
# Monitoring thread is not mid-query; safe to close the connection.
|
|
103
|
+
begin
|
|
104
|
+
@monitoring_connection.set(nil)
|
|
105
|
+
ensure
|
|
106
|
+
@monitoring_conn_lock.unlock
|
|
107
|
+
end
|
|
108
|
+
else
|
|
109
|
+
# Monitoring thread is mid-query. Detach without closing — the monitoring thread will close the
|
|
110
|
+
# connection after its query completes (via read_timeout for MySQL or keepalives for PG).
|
|
111
|
+
@monitoring_connection.set(nil, close_old: false)
|
|
112
|
+
end
|
|
113
|
+
@verified_writer = false
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
wait_for_topology_update(timeout_sec, verify_writer)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Event subscriber callback.
|
|
120
|
+
# @param event [Object]
|
|
121
|
+
def process_event(event)
|
|
122
|
+
return unless event.is_a?(Utils::Events::MonitorResetEvent)
|
|
123
|
+
return unless event.cluster_id == @cluster_id
|
|
124
|
+
|
|
125
|
+
reset!
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def close
|
|
129
|
+
close_instance_monitors
|
|
130
|
+
@monitoring_conn_lock.synchronize { @monitoring_connection.close }
|
|
131
|
+
@instance_monitors_writer_conn.close
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
# Main monitoring loop.
|
|
137
|
+
def monitor
|
|
138
|
+
event_publisher.subscribe(self, Set[Utils::Events::MonitorResetEvent])
|
|
139
|
+
logger.debug("[#{@cluster_id}] Started cluster topology monitor")
|
|
140
|
+
|
|
141
|
+
until stopped?
|
|
142
|
+
update_activity
|
|
143
|
+
|
|
144
|
+
if panic_mode?
|
|
145
|
+
run_panic_mode_iteration
|
|
146
|
+
else
|
|
147
|
+
run_regular_mode_iteration
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
ensure
|
|
151
|
+
@stop_instance_monitors = true
|
|
152
|
+
close_instance_monitors
|
|
153
|
+
@monitoring_conn_lock.synchronize { @monitoring_connection.close }
|
|
154
|
+
@instance_monitors_writer_conn.close
|
|
155
|
+
event_publisher.unsubscribe(self, Set[Utils::Events::MonitorResetEvent])
|
|
156
|
+
logger.debug("[#{@cluster_id}] Stopped cluster topology monitor")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# --- Mode determination ---
|
|
160
|
+
|
|
161
|
+
def panic_mode?
|
|
162
|
+
@monitoring_connection.get.nil? || !@verified_writer
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# --- Regular mode ---
|
|
166
|
+
|
|
167
|
+
def run_regular_mode_iteration
|
|
168
|
+
close_instance_monitors unless @instance_monitors.empty?
|
|
169
|
+
|
|
170
|
+
conn = @monitoring_connection.get
|
|
171
|
+
hosts = nil
|
|
172
|
+
|
|
173
|
+
# Hold the lock while querying so force_refresh knows not to close this connection.
|
|
174
|
+
@monitoring_conn_lock.synchronize do
|
|
175
|
+
hosts = fetch_topology_and_update_cache(conn)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# After releasing the lock, check if force_refresh detached this connection while
|
|
179
|
+
# we were querying (set it to nil with close_old: false). If so, we must close it
|
|
180
|
+
# since we're the only thread that held a reference during the query.
|
|
181
|
+
if conn && @monitoring_connection.get != conn
|
|
182
|
+
safe_close_connection(conn)
|
|
183
|
+
@verified_writer = false
|
|
184
|
+
@writer_info = nil
|
|
185
|
+
return
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
if hosts.nil?
|
|
189
|
+
# Unable to fetch topology. Enter panic mode.
|
|
190
|
+
if conn
|
|
191
|
+
@monitoring_connection.compare_and_set(conn, nil)
|
|
192
|
+
safe_close_connection(conn)
|
|
193
|
+
end
|
|
194
|
+
@verified_writer = false
|
|
195
|
+
@writer_info = nil
|
|
196
|
+
return
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
@high_refresh_end_time = 0 if @high_refresh_end_time.positive? && monotonic_time > @high_refresh_end_time
|
|
200
|
+
|
|
201
|
+
delay(use_high_rate: @high_refresh_end_time&.positive?)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# --- Panic mode ---
|
|
205
|
+
|
|
206
|
+
def run_panic_mode_iteration
|
|
207
|
+
if @instance_monitors.empty?
|
|
208
|
+
start_instance_monitors
|
|
209
|
+
else
|
|
210
|
+
check_new_hosts_for_instance_monitors unless writer_picked_up_from_instance_monitors?
|
|
211
|
+
check_stable_instance_monitor_topologies
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
delay(use_high_rate: true)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def start_instance_monitors
|
|
218
|
+
@instance_monitors_writer_info = nil
|
|
219
|
+
@latest_topology = nil
|
|
220
|
+
cleanup_host_writer_connection
|
|
221
|
+
|
|
222
|
+
hosts = stored_hosts || open_any_connection_and_update_topology
|
|
223
|
+
# Close any previously running instance monitors.
|
|
224
|
+
close_instance_monitors
|
|
225
|
+
@stop_instance_monitors = false
|
|
226
|
+
|
|
227
|
+
return if hosts.nil? || @verified_writer
|
|
228
|
+
|
|
229
|
+
hosts.first(@max_instance_monitors).each do |host_info|
|
|
230
|
+
spawn_instance_monitor(host_info)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Returns true if writer was picked up from instance monitors (caller should skip further checks).
|
|
235
|
+
def writer_picked_up_from_instance_monitors?
|
|
236
|
+
writer_conn = @instance_monitors_writer_conn.get
|
|
237
|
+
writer_host = @instance_monitors_writer_info
|
|
238
|
+
return false unless writer_conn && writer_host
|
|
239
|
+
|
|
240
|
+
logger.info("[#{@cluster_id}] Writer found: #{writer_host.host}")
|
|
241
|
+
@monitoring_conn_lock.synchronize { @monitoring_connection.set(writer_conn, close_old: true) }
|
|
242
|
+
# Avoid double-close: clear host reference without closing since we transferred ownership.
|
|
243
|
+
@instance_monitors_writer_conn.set(nil, close_old: false)
|
|
244
|
+
@writer_info = writer_host
|
|
245
|
+
@verified_writer = true
|
|
246
|
+
@high_refresh_end_time = monotonic_time + HIGH_REFRESH_DURATION_SEC
|
|
247
|
+
|
|
248
|
+
@stop_instance_monitors = true
|
|
249
|
+
close_instance_monitors
|
|
250
|
+
true
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def check_new_hosts_for_instance_monitors
|
|
254
|
+
hosts = @latest_topology
|
|
255
|
+
return if hosts.nil? || @stop_instance_monitors
|
|
256
|
+
|
|
257
|
+
hosts.first(@max_instance_monitors).each do |host_info|
|
|
258
|
+
spawn_instance_monitor(host_info) unless @instance_monitors.key?(host_info.host) ||
|
|
259
|
+
@instance_monitors.size >= @max_instance_monitors
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def spawn_instance_monitor(host_info)
|
|
264
|
+
thread = Thread.new { instance_monitor(host_info) }
|
|
265
|
+
thread.name = "instance-monitor-#{host_info.host}"
|
|
266
|
+
@instance_monitors[host_info.host] = thread
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# --- Stable reader topologies consensus ---
|
|
270
|
+
|
|
271
|
+
def check_stable_instance_monitor_topologies
|
|
272
|
+
hosts = stored_hosts
|
|
273
|
+
if hosts.nil? || hosts.empty?
|
|
274
|
+
reset_stable_state
|
|
275
|
+
return
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
monitored_ids = hosts.first(@max_instance_monitors).map(&:id)
|
|
279
|
+
|
|
280
|
+
completed, topologies = @panic_mutex.synchronize { [@completed_one_cycle.dup, @instance_monitor_topologies.dup] }
|
|
281
|
+
|
|
282
|
+
unless monitored_ids.all? { |id| completed[id] }
|
|
283
|
+
reset_stable_state
|
|
284
|
+
return
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
if topologies.empty?
|
|
288
|
+
reset_stable_state
|
|
289
|
+
return
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Check if all instance monitor topologies match (by host, port, role, availability).
|
|
293
|
+
reference_topology = topologies.values.first
|
|
294
|
+
all_match = topologies.values.all? do |topo|
|
|
295
|
+
topo.size == reference_topology.size && topo.zip(reference_topology).all? do |a, b|
|
|
296
|
+
a.host == b.host && a.port == b.port && a.role == b.role && a.availability == b.availability
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
unless all_match
|
|
301
|
+
reset_stable_state
|
|
302
|
+
return
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
@stable_start_time = monotonic_time if @stable_start_time.zero?
|
|
306
|
+
|
|
307
|
+
return unless monotonic_time > @stable_start_time + STABLE_TOPOLOGIES_DURATION_SEC
|
|
308
|
+
|
|
309
|
+
@stable_start_time = 0
|
|
310
|
+
update_hosts_availability(reference_topology)
|
|
311
|
+
update_topology_cache(reference_topology)
|
|
312
|
+
logger.debug("[#{@cluster_id}] Stable instance monitor topologies accepted")
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def reset_stable_state
|
|
316
|
+
@stable_start_time = 0
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# --- Instance Monitor ---
|
|
320
|
+
|
|
321
|
+
def instance_monitor(host_info)
|
|
322
|
+
conn = nil
|
|
323
|
+
connection_attempts = 0
|
|
324
|
+
writer_changed = false
|
|
325
|
+
|
|
326
|
+
until @stop_instance_monitors
|
|
327
|
+
if conn.nil?
|
|
328
|
+
conn = attempt_host_connection(host_info, connection_attempts)
|
|
329
|
+
if conn.nil?
|
|
330
|
+
connection_attempts += 1
|
|
331
|
+
@panic_mutex.synchronize do
|
|
332
|
+
@completed_one_cycle[host_info.id] = true
|
|
333
|
+
@instance_monitor_topologies.delete(host_info.id)
|
|
334
|
+
end
|
|
335
|
+
next
|
|
336
|
+
end
|
|
337
|
+
connection_attempts = 0
|
|
338
|
+
@instance_monitor_connections[host_info.host] = conn
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# If the connection was closed externally abandon it and reconnect on the next iteration.
|
|
342
|
+
if @service_container.dialect_service.driver_dialect.closed?(conn)
|
|
343
|
+
conn = nil
|
|
344
|
+
@instance_monitor_connections.delete(host_info.host)
|
|
345
|
+
next
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
role = check_host_role(conn)
|
|
349
|
+
if role.nil?
|
|
350
|
+
safe_close_connection(conn)
|
|
351
|
+
conn = nil
|
|
352
|
+
@instance_monitor_connections.delete(host_info.host)
|
|
353
|
+
next
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
if role == Host::HostRole::WRITER
|
|
357
|
+
handle_writer_found(host_info, conn)
|
|
358
|
+
return
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Reader path — writer_changed is sticky: once true, all subsequent fetches update the cache immediately.
|
|
362
|
+
writer_changed = reader_fetch_topology(conn, host_info, writer_changed)
|
|
363
|
+
@panic_mutex.synchronize { @completed_one_cycle[host_info.id] = true }
|
|
364
|
+
sleep(INSTANCE_MONITOR_SLEEP_SEC)
|
|
365
|
+
end
|
|
366
|
+
rescue StandardError => e
|
|
367
|
+
logger.debug("[#{@cluster_id}] Instance Monitor #{host_info.host}: #{e.message}")
|
|
368
|
+
ensure
|
|
369
|
+
@instance_monitor_connections.delete(host_info.host)
|
|
370
|
+
@panic_mutex.synchronize do
|
|
371
|
+
@completed_one_cycle[host_info.id] = true
|
|
372
|
+
@instance_monitor_topologies.delete(host_info.id)
|
|
373
|
+
end
|
|
374
|
+
safe_close_connection(conn) if conn && conn != @instance_monitors_writer_conn.get
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def attempt_host_connection(host_info, attempts)
|
|
378
|
+
internal_connect(host_info)
|
|
379
|
+
rescue StandardError => e
|
|
380
|
+
raise if @service_container.dialect_service.login_error?(e)
|
|
381
|
+
|
|
382
|
+
logger.debug("[#{@cluster_id}] Connection to #{host_info.host} failed: #{e.message}")
|
|
383
|
+
sleep(calculate_backoff(attempts))
|
|
384
|
+
nil
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def check_host_role(conn)
|
|
388
|
+
@service_container.dialect_service.db_dialect.host_role(conn)
|
|
389
|
+
rescue StandardError => e
|
|
390
|
+
logger.debug("[#{@cluster_id}] host_role check failed: #{e.message}")
|
|
391
|
+
nil
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def handle_writer_found(host_info, conn)
|
|
395
|
+
# If compare_and_set fails, another instance monitor already found the writer. Connection will be closed in
|
|
396
|
+
# caller's ensure block.
|
|
397
|
+
return unless @instance_monitors_writer_conn.compare_and_set(nil, conn)
|
|
398
|
+
|
|
399
|
+
fetch_topology_and_update_cache(conn)
|
|
400
|
+
host_info.availability = Host::HostAvailability::AVAILABLE
|
|
401
|
+
@instance_monitors_writer_info = host_info
|
|
402
|
+
@stop_instance_monitors = true
|
|
403
|
+
logger.info("[#{@cluster_id}] Writer verified: #{host_info.host}")
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def reader_fetch_topology(conn, host_info, writer_changed)
|
|
407
|
+
hosts = query_topology(conn)
|
|
408
|
+
return writer_changed if hosts.nil?
|
|
409
|
+
|
|
410
|
+
@panic_mutex.synchronize do
|
|
411
|
+
@latest_topology = hosts
|
|
412
|
+
@instance_monitor_topologies[host_info.id] = hosts
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
if writer_changed
|
|
416
|
+
update_hosts_availability(hosts)
|
|
417
|
+
update_topology_cache(hosts)
|
|
418
|
+
return true
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
latest_writer = hosts.find { |h| h.role == Host::HostRole::WRITER }
|
|
422
|
+
if latest_writer && @writer_info &&
|
|
423
|
+
latest_writer.host != @writer_info&.host
|
|
424
|
+
logger.info("[#{@cluster_id}] Writer changed: #{@writer_info&.host} -> #{latest_writer.host}")
|
|
425
|
+
update_hosts_availability(hosts)
|
|
426
|
+
update_topology_cache(hosts)
|
|
427
|
+
return true
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
writer_changed
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# --- Topology operations ---
|
|
434
|
+
|
|
435
|
+
def open_any_connection_and_update_topology
|
|
436
|
+
@monitoring_conn_lock.synchronize do
|
|
437
|
+
existing_conn = @monitoring_connection.get
|
|
438
|
+
return fetch_topology_and_update_cache(existing_conn) if existing_conn
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
conn = internal_connect(initial_host_info)
|
|
442
|
+
return nil if conn.nil?
|
|
443
|
+
|
|
444
|
+
@monitoring_conn_lock.synchronize do
|
|
445
|
+
unless @monitoring_connection.compare_and_set(nil, conn)
|
|
446
|
+
safe_close_connection(conn)
|
|
447
|
+
return fetch_topology_and_update_cache(@monitoring_connection.get)
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
hosts = fetch_topology_and_update_cache(conn)
|
|
451
|
+
if hosts.nil?
|
|
452
|
+
@monitoring_connection.set(nil)
|
|
453
|
+
@verified_writer = false
|
|
454
|
+
@writer_info = nil
|
|
455
|
+
return nil
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# When the connected endpoint is the writer, record the writer as the actual instance from the
|
|
459
|
+
# fetched topology rather than initial_host_info, which is typically the cluster endpoint. The
|
|
460
|
+
# writer-verification check compares against instance-level topology entries, so recording the
|
|
461
|
+
# cluster endpoint (which never appears in the topology) would block verification until timeout.
|
|
462
|
+
if check_host_role(conn) == Host::HostRole::WRITER
|
|
463
|
+
writer = hosts.find { |h| h.role == Host::HostRole::WRITER }
|
|
464
|
+
if writer
|
|
465
|
+
@verified_writer = true
|
|
466
|
+
@writer_info = writer
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
hosts
|
|
471
|
+
end
|
|
472
|
+
rescue StandardError => e
|
|
473
|
+
logger.debug("[#{@cluster_id}] Failed to open monitoring connection: #{e.message}")
|
|
474
|
+
nil
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def fetch_topology_and_update_cache(conn)
|
|
478
|
+
return nil if conn.nil?
|
|
479
|
+
|
|
480
|
+
driver_dialect = @service_container.dialect_service.driver_dialect
|
|
481
|
+
if driver_dialect.closed?(conn)
|
|
482
|
+
logger.debug("[#{@cluster_id}] Monitoring connection is closed; skipping topology fetch")
|
|
483
|
+
return nil
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
hosts = query_topology(conn)
|
|
487
|
+
update_topology_cache(hosts) if hosts && !hosts.empty?
|
|
488
|
+
hosts
|
|
489
|
+
rescue StandardError => e
|
|
490
|
+
logger.debug("[#{@cluster_id}] Topology fetch failed: #{e.message}")
|
|
491
|
+
nil
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def query_topology(conn)
|
|
495
|
+
@topology_utils.query_topology(conn, @initial_host_info, @instance_template)
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def stored_hosts
|
|
499
|
+
storage_service.get(TOPOLOGY_CACHE_NAME, @cluster_id, register_access: false)
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def update_topology_cache(hosts)
|
|
503
|
+
@topology_mutex.synchronize do
|
|
504
|
+
storage_service.set(TOPOLOGY_CACHE_NAME, @cluster_id, hosts)
|
|
505
|
+
@update_requested = false
|
|
506
|
+
@topology_cv.broadcast
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def clear_topology_cache
|
|
511
|
+
@topology_mutex.synchronize do
|
|
512
|
+
storage_service.remove(TOPOLOGY_CACHE_NAME, @cluster_id)
|
|
513
|
+
@update_requested = false
|
|
514
|
+
@topology_cv.broadcast
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def update_hosts_availability(hosts)
|
|
519
|
+
return if hosts.nil? || hosts.empty?
|
|
520
|
+
|
|
521
|
+
hosts.each do |host|
|
|
522
|
+
available = @instance_monitor_topologies.key?(host.id)
|
|
523
|
+
host.availability = available ? Host::HostAvailability::AVAILABLE : Host::HostAvailability::UNAVAILABLE
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# --- force_refresh support ---
|
|
528
|
+
|
|
529
|
+
def wait_for_topology_update(timeout_sec, verify_writer = false)
|
|
530
|
+
current_hosts = stored_hosts
|
|
531
|
+
|
|
532
|
+
@topology_mutex.synchronize do
|
|
533
|
+
@update_requested = true
|
|
534
|
+
@topology_cv.broadcast
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
return current_hosts if timeout_sec.zero?
|
|
538
|
+
|
|
539
|
+
deadline = monotonic_time + timeout_sec
|
|
540
|
+
@topology_mutex.synchronize do
|
|
541
|
+
# We are checking reference equality instead of value equality. We will break out of the loop if there is a
|
|
542
|
+
# new entry in the topology cache, even if current_hosts contains the same hosts as stored_hosts.
|
|
543
|
+
#
|
|
544
|
+
# When verify_writer is set, a fresh cache entry is not enough: right after failover the topology query can
|
|
545
|
+
# still report the demoted writer as the writer (e.g. Aurora MySQL's replica_host_status is eventually
|
|
546
|
+
# consistent), so we keep waiting until the cached writer matches the writer this monitor has independently
|
|
547
|
+
# verified by probing instances directly, or the timeout is hit.
|
|
548
|
+
while wait_for_topology?(current_hosts, verify_writer) && monotonic_time < deadline && !stopped?
|
|
549
|
+
remaining = deadline - monotonic_time
|
|
550
|
+
break if remaining <= 0
|
|
551
|
+
|
|
552
|
+
@topology_cv.wait(@topology_mutex, remaining)
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
latest = stored_hosts
|
|
557
|
+
if latest.equal?(current_hosts)
|
|
558
|
+
raise Timeout::Error, "Topology not updated within #{timeout_sec}s for cluster #{@cluster_id}"
|
|
559
|
+
elsif verify_writer && !cached_writer_verified?(latest)
|
|
560
|
+
raise Timeout::Error, "Topology writer did not match the verified writer within #{timeout_sec}s for cluster #{@cluster_id}"
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
latest
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
# Returns true while force_refresh should keep waiting for a topology update.
|
|
567
|
+
def wait_for_topology?(current_hosts, verify_writer)
|
|
568
|
+
return true if stored_hosts.equal?(current_hosts)
|
|
569
|
+
|
|
570
|
+
# A new cache entry arrived. When verifying the writer, keep waiting until the topology writer matches the
|
|
571
|
+
# writer the monitor has verified directly.
|
|
572
|
+
verify_writer && !cached_writer_verified?(stored_hosts)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
# Returns true when the writer in the given topology matches the writer this monitor has verified by probing
|
|
576
|
+
# instances directly. Returns false when either is missing so the caller keeps waiting.
|
|
577
|
+
def cached_writer_verified?(hosts)
|
|
578
|
+
return false unless @verified_writer
|
|
579
|
+
|
|
580
|
+
verified_writer = @writer_info
|
|
581
|
+
return false if verified_writer.nil? || hosts.nil?
|
|
582
|
+
|
|
583
|
+
cached_writer = hosts.find { |h| h.role == Host::HostRole::WRITER }
|
|
584
|
+
return false if cached_writer.nil?
|
|
585
|
+
|
|
586
|
+
# During a Blue/Green switchover the freshly cached topology is read from a green node, so its
|
|
587
|
+
# writer carries a green-prefixed identifier (e.g. "instance-green-abc123") while the writer this
|
|
588
|
+
# monitor verified by probing is the blue identifier. Strip the green marker from both sides so
|
|
589
|
+
# the same logical instance matches across the rename.
|
|
590
|
+
return true if green_normalized(cached_writer.host) == green_normalized(verified_writer.host)
|
|
591
|
+
|
|
592
|
+
cached_writer.id && verified_writer.id &&
|
|
593
|
+
green_normalized(cached_writer.id) == green_normalized(verified_writer.id)
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
# Strips a Blue/Green "-green-xxxxxx" marker from a host or instance id so blue and green
|
|
597
|
+
# identifiers for the same instance compare equal. Non-green values are returned unchanged.
|
|
598
|
+
def green_normalized(value)
|
|
599
|
+
return value if value.nil?
|
|
600
|
+
|
|
601
|
+
Utils::RdsUtils.remove_green_instance_prefix(value)
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
# --- Reset ---
|
|
605
|
+
|
|
606
|
+
def reset!
|
|
607
|
+
logger.debug("[#{@cluster_id}] Monitor reset")
|
|
608
|
+
@stop_instance_monitors = true
|
|
609
|
+
close_instance_monitor_connections
|
|
610
|
+
close_instance_monitors
|
|
611
|
+
@stop_instance_monitors = false
|
|
612
|
+
@monitoring_conn_lock.synchronize { @monitoring_connection.set(nil) }
|
|
613
|
+
@verified_writer = false
|
|
614
|
+
@writer_info = nil
|
|
615
|
+
@high_refresh_end_time = 0
|
|
616
|
+
clear_topology_cache
|
|
617
|
+
|
|
618
|
+
@topology_mutex.synchronize do
|
|
619
|
+
@update_requested = true
|
|
620
|
+
@topology_cv.broadcast
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
# --- Cleanup helpers ---
|
|
625
|
+
|
|
626
|
+
# Closes all instance monitor connections so that any in-flight queries exits safely.
|
|
627
|
+
# This MUST be called before join/kill on the threads.
|
|
628
|
+
def close_instance_monitor_connections
|
|
629
|
+
@instance_monitor_connections.each_pair do |host, conn|
|
|
630
|
+
safe_close_connection(conn)
|
|
631
|
+
@instance_monitor_connections.delete(host)
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
def close_instance_monitors
|
|
636
|
+
@stop_instance_monitors = true
|
|
637
|
+
@instance_monitors.each_value do |t|
|
|
638
|
+
t.join(5)
|
|
639
|
+
t.kill if t.alive?
|
|
640
|
+
end
|
|
641
|
+
@instance_monitors.clear
|
|
642
|
+
cleanup_host_writer_connection
|
|
643
|
+
@stable_start_time = 0
|
|
644
|
+
@instance_monitor_topologies.clear
|
|
645
|
+
@completed_one_cycle.clear
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
def cleanup_host_writer_connection
|
|
649
|
+
# Avoid double-close if the monitoring connection already owns this reference.
|
|
650
|
+
@monitoring_conn_lock.synchronize do
|
|
651
|
+
if @monitoring_connection.get.equal?(@instance_monitors_writer_conn.get)
|
|
652
|
+
@instance_monitors_writer_conn.set(nil, close_old: false)
|
|
653
|
+
else
|
|
654
|
+
@instance_monitors_writer_conn.set(nil)
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
# --- Delay ---
|
|
660
|
+
|
|
661
|
+
def delay(use_high_rate:)
|
|
662
|
+
use_high_rate = true if @high_refresh_end_time.positive? && monotonic_time < @high_refresh_end_time
|
|
663
|
+
|
|
664
|
+
@topology_mutex.synchronize do
|
|
665
|
+
if @update_requested
|
|
666
|
+
use_high_rate = true
|
|
667
|
+
@update_requested = false
|
|
668
|
+
end
|
|
669
|
+
duration = use_high_rate ? @high_refresh_rate_sec : @refresh_rate_sec
|
|
670
|
+
@topology_cv.wait(@topology_mutex, duration) unless stopped?
|
|
671
|
+
end
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
# --- Utilities ---
|
|
675
|
+
|
|
676
|
+
def monotonic_time
|
|
677
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
def safe_close_connection(conn)
|
|
681
|
+
@service_container.dialect_service.driver_dialect.close_connection(conn) if conn
|
|
682
|
+
rescue StandardError
|
|
683
|
+
nil
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
def calculate_backoff(attempt)
|
|
687
|
+
backoff = INITIAL_BACKOFF_SEC * (2**[attempt, 6].min)
|
|
688
|
+
backoff = [backoff, MAX_BACKOFF_SEC].min
|
|
689
|
+
backoff * (0.5 + (rand * 0.5))
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
def event_publisher
|
|
693
|
+
@service_container.event_publisher
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
def storage_service
|
|
697
|
+
@service_container.storage_service
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
def initial_host_info
|
|
701
|
+
@service_container.connection_service.initial_host_info
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def internal_connect(host_info)
|
|
705
|
+
@service_container.plugin_manager.internal_connect(host_info, @monitoring_driver_props.dup, @monitoring_wrapper_props, false)
|
|
706
|
+
end
|
|
707
|
+
end
|
|
708
|
+
end
|
|
709
|
+
end
|