diff --git a/CHANGELOG.md b/CHANGELOG.md index 35ede15d0..313130cda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,5 +11,6 @@ - [#1397](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1397) Treat "DBPROCESS is dead or not enabled" as ConnectionNotEstablished. - [#1406](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1406) Stop deserializing column defaults. - [#1405](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1405) Fix `insert_all`/`upsert_all` for single character, temporary, non-ASCII and three part table names, and stop an aliased target being included in the `MERGE` table name. +- [#1412](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1412) Use TinyTDS `Client#ping(timeout:)` from `active?` when available. Please check [8-1-stable](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/8-1-stable/CHANGELOG.md) for previous changes. diff --git a/lib/active_record/connection_adapters/sqlserver_adapter.rb b/lib/active_record/connection_adapters/sqlserver_adapter.rb index 03f542bd5..b4f1eb0f3 100644 --- a/lib/active_record/connection_adapters/sqlserver_adapter.rb +++ b/lib/active_record/connection_adapters/sqlserver_adapter.rb @@ -50,6 +50,11 @@ class SQLServerAdapter < AbstractAdapter # Default precision for 'time' (See https://docs.microsoft.com/en-us/sql/t-sql/data-types/time-transact-sql) DEFAULT_TIME_PRECISION = 7 + # FreeTDS-bounded checkout ping timeout (seconds) when TinyTDS provides + # Client#ping. Override with config :ping_timeout (0 disables ping). + # See rails-sqlserver/tiny_tds#609 and activerecord-sqlserver-adapter#1396. + DEFAULT_PING_TIMEOUT = 2 + attr_reader :spid cattr_accessor :cs_equality_operator, instance_accessor: false @@ -143,6 +148,13 @@ def initialize(...) @config[:appname] = self.class.rails_application_name unless @config[:appname] @config[:login_timeout] = @config[:login_timeout].present? ? @config[:login_timeout].to_i : nil @config[:timeout] = @config[:timeout].present? ? @config[:timeout].to_i / 1000 : nil + # 0 disables checkout ping; nil uses DEFAULT_PING_TIMEOUT. Avoid .present? + # so an explicit 0 is not treated as missing. + @config[:ping_timeout] = if @config.key?(:ping_timeout) && !@config[:ping_timeout].nil? + @config[:ping_timeout].to_i + else + DEFAULT_PING_TIMEOUT + end @config[:encoding] = @config[:encoding].present? ? @config[:encoding] : nil @connection_parameters ||= @config @@ -285,7 +297,14 @@ def disable_referential_integrity # === Abstract Adapter (Connection Management) ================== # def active? - if @raw_connection&.active? + return false unless @raw_connection + + if raw_connection_ping_enabled? + return false unless @raw_connection.ping(timeout: ping_timeout_seconds) + + verified! + true + elsif @raw_connection.active? verified! true end @@ -518,6 +537,17 @@ def connection_errors end end + def raw_connection_ping_enabled? + @raw_connection.respond_to?(:ping) && ping_timeout_seconds.positive? + end + + def ping_timeout_seconds + seconds = @config[:ping_timeout] + return DEFAULT_PING_TIMEOUT if seconds.nil? + + seconds.to_i + end + def initialize_dateformatter @database_dateformat = user_options_dateformat a, b, c = @database_dateformat.each_char.to_a