From a6addbf917b79c09d571207225dce7bf1ce3887a Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Fri, 16 Jan 2026 12:10:04 -0700 Subject: [PATCH 1/4] RUBY-3552 Deprecate support for server version 3.6 Adds a new Mongo::Deprecations module as well --- lib/mongo.rb | 1 + lib/mongo/deprecations.rb | 78 +++++++++++++++++++ lib/mongo/server/description/features.rb | 35 +++++++-- .../mongo/server/description/features_spec.rb | 20 +++++ 4 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 lib/mongo/deprecations.rb diff --git a/lib/mongo.rb b/lib/mongo.rb index 0596f75839..f6111aae3c 100644 --- a/lib/mongo.rb +++ b/lib/mongo.rb @@ -34,6 +34,7 @@ require 'bson' +require 'mongo/deprecations' require 'mongo/id' require 'mongo/bson' require 'mongo/semaphore' diff --git a/lib/mongo/deprecations.rb b/lib/mongo/deprecations.rb new file mode 100644 index 0000000000..5bd16e8ea1 --- /dev/null +++ b/lib/mongo/deprecations.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'mongo/loggable' + +module Mongo + # Used for reporting deprecated behavior in the driver. When it is possible + # to detect that a deprecated feature is being used, a warning should be issued + # through this module. + # + # The warning will be issued no more than once for that feature, regardless + # of how many times Mongo::Deprecations.warn is called. + # + # @example Issue a deprecation warning. + # Mongo::Deprecations.warn(:old_feature, "The old_feature is deprecated, use new_feature instead.") + # + # @api private + module Deprecations + extend self + + include Mongo::Loggable + + # Mutex for synchronizing access to warned features. + # @api private + MUTEX = Thread::Mutex.new + + # Issue a warning about a deprecated feature. The warning is written to the + # logger, and will not be written more than once per feature. + def warn(feature, message) + MUTEX.synchronize do + return if _warned?(feature) + + _warned!(feature) + log_warn("[DEPRECATION:#{feature}] #{message}") + end + end + + # Check if a warning for a given deprecated feature has already been issued. + # + # @param [ String | Symbol ] feature The deprecated feature. + # + # @return [ true | false ] If a warning has already been issued. + def warned?(feature, prefix: false) + MUTEX.synchronize { _warned?(feature, prefix: prefix) } + end + + # Mark that a warning for a given deprecated feature has been issued. + # + # @param [ String | Symbol ] feature The deprecated feature. + def warned!(feature) + MUTEX.synchronize { _warned!(feature) } + end + + # Clears all memory of previously warned features. + def clear! + MUTEX.synchronize { warned_features reset: true } + true + end + + private + + def warned_features(reset: false) + @warned_features = nil if reset + @warned_features ||= Set.new + end + + def _warned?(feature, prefix: false) + if prefix + warned_features.any? { |f| feature.to_s.start_with?(f) } + else + warned_features.include?(feature.to_s) + end + end + + def _warned!(feature) + warned_features.add(feature.to_s) + end + end +end diff --git a/lib/mongo/server/description/features.rb b/lib/mongo/server/description/features.rb index cfae85c7a2..9110494512 100644 --- a/lib/mongo/server/description/features.rb +++ b/lib/mongo/server/description/features.rb @@ -74,6 +74,11 @@ class Features SERVER_TOO_OLD = "Server at (%s) reports wire version (%s), but this version of the Ruby driver " + "requires at least (%s)." + # Warning message if the server version is deprecated. + SERVER_DEPRECATED = 'Server at (%s) reports wire version (%s), but support for that wire version ' \ + 'is deprecated and will be removed in a future version of the Ruby driver. ' \ + 'Please upgrade your MongoDB server to a newer version soon.' + # Error message if the driver is too old for the version of the server. # # @since 2.5.0 @@ -83,7 +88,20 @@ class Features # The wire protocol versions that this version of the driver supports. # # @since 2.0.0 - DRIVER_WIRE_VERSIONS = (6..25).freeze + DRIVER_WIRE_VERSIONS = 6..25 + + # The wire protocol versions that are deprecated in this version of the + # driver. Support for these versions will be removed in the future. + # + # If there are multiple currently-deprecated wire versions, this should + # be set to a range of those versions. + # + # If there is only a single currently-deprecated wire version, this should + # be set to a range where the min and max are the same value. + # + # If there are no currently-deprecated wire versions, this should be + # set to an empty array. + DEPRECATED_WIRE_VERSIONS = 6..6 # Create the methods for each mapping to tell if they are supported. # @@ -131,20 +149,21 @@ def initialize(server_wire_versions, address = nil) end # Check that there is an overlap between the driver supported wire - # version range and the server wire version range. - # - # @example Verify the wire version overlap. - # features.check_driver_support! + # version range and the server wire version range. Also checks to see + # if the server is using a deprecated wire version. # # @raise [ Error::UnsupportedFeatures ] If the wire version range is # not covered by the driver. - # - # @since 2.5.1 def check_driver_support! - if DRIVER_WIRE_VERSIONS.min > @server_wire_versions.max + if DEPRECATED_WIRE_VERSIONS.include?(@server_wire_versions.max) + feature = "wire_version:#{@address}" + Mongo::Deprecations.warn(feature, SERVER_DEPRECATED % [@address, @server_wire_versions.max]) + + elsif DRIVER_WIRE_VERSIONS.min > @server_wire_versions.max raise Error::UnsupportedFeatures.new(SERVER_TOO_OLD % [@address, @server_wire_versions.max, DRIVER_WIRE_VERSIONS.min]) + elsif DRIVER_WIRE_VERSIONS.max < @server_wire_versions.min raise Error::UnsupportedFeatures.new(DRIVER_TOO_OLD % [@address, @server_wire_versions.min, diff --git a/spec/mongo/server/description/features_spec.rb b/spec/mongo/server/description/features_spec.rb index b69beaa1ff..26745fbd02 100644 --- a/spec/mongo/server/description/features_spec.rb +++ b/spec/mongo/server/description/features_spec.rb @@ -35,6 +35,26 @@ end end + if described_class::DEPRECATED_WIRE_VERSIONS.any? + context 'when the max server wire version range is deprecated' do + before do + Mongo::Deprecations.clear! + end + + let(:wire_versions) do + (described_class::DEPRECATED_WIRE_VERSIONS.min - 1)..described_class::DEPRECATED_WIRE_VERSIONS.max + end + + it 'issues a deprecation warning' do + expect { + features.check_driver_support! + }.to change { + Mongo::Deprecations.warned?("wire_version:#{default_address}") + }.from(false).to(true) + end + end + end + context 'when the server wire version range max is higher' do let(:wire_versions) do From e11397199db5a1e85570079d82faebb21ee61128 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Fri, 16 Jan 2026 14:20:07 -0700 Subject: [PATCH 2/4] feedback from copilot --- lib/mongo.rb | 2 +- lib/mongo/deprecations.rb | 23 ++++++++++++++++++++++- lib/mongo/server/description/features.rb | 12 +++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lib/mongo.rb b/lib/mongo.rb index f6111aae3c..86a3056d77 100644 --- a/lib/mongo.rb +++ b/lib/mongo.rb @@ -34,7 +34,6 @@ require 'bson' -require 'mongo/deprecations' require 'mongo/id' require 'mongo/bson' require 'mongo/semaphore' @@ -43,6 +42,7 @@ require 'mongo/csot_timeout_holder' require 'mongo/options' require 'mongo/loggable' +require 'mongo/deprecations' require 'mongo/cluster_time' require 'mongo/topology_version' require 'mongo/monitoring' diff --git a/lib/mongo/deprecations.rb b/lib/mongo/deprecations.rb index 5bd16e8ea1..1b1d156d85 100644 --- a/lib/mongo/deprecations.rb +++ b/lib/mongo/deprecations.rb @@ -25,6 +25,9 @@ module Deprecations # Issue a warning about a deprecated feature. The warning is written to the # logger, and will not be written more than once per feature. + # + # @param [ String | Symbol ] feature The deprecated feature. + # @param [ String ] message The deprecation message. def warn(feature, message) MUTEX.synchronize do return if _warned?(feature) @@ -37,6 +40,7 @@ def warn(feature, message) # Check if a warning for a given deprecated feature has already been issued. # # @param [ String | Symbol ] feature The deprecated feature. + # @param [ true | false ] prefix Whether to check for prefix matches. # # @return [ true | false ] If a warning has already been issued. def warned?(feature, prefix: false) @@ -48,21 +52,34 @@ def warned?(feature, prefix: false) # @param [ String | Symbol ] feature The deprecated feature. def warned!(feature) MUTEX.synchronize { _warned!(feature) } + nil end # Clears all memory of previously warned features. def clear! MUTEX.synchronize { warned_features reset: true } - true + nil end private + # Set of features that have already been warned about. + # + # @param [ true | false ] reset Whether to reset the warned features. + # + # @return [ Set ] The set of warned features. def warned_features(reset: false) @warned_features = nil if reset @warned_features ||= Set.new end + # Check if a warning for a given deprecated feature has already been issued. + # This version is not thread-safe. + # + # @param [ String | Symbol ] feature The deprecated feature. + # @param [ true | false ] prefix Whether to check for prefix matches. + # + # @return [ true | false ] If a warning has already been issued. def _warned?(feature, prefix: false) if prefix warned_features.any? { |f| feature.to_s.start_with?(f) } @@ -71,6 +88,10 @@ def _warned?(feature, prefix: false) end end + # Mark that a warning for a given deprecated feature has been issued. + # This version is not thread-safe. + # + # @param [ String | Symbol ] feature The deprecated feature. def _warned!(feature) warned_features.add(feature.to_s) end diff --git a/lib/mongo/server/description/features.rb b/lib/mongo/server/description/features.rb index 9110494512..85f496f797 100644 --- a/lib/mongo/server/description/features.rb +++ b/lib/mongo/server/description/features.rb @@ -85,6 +85,9 @@ class Features DRIVER_TOO_OLD = "Server at (%s) requires wire version (%s), but this version of the Ruby driver " + "only supports up to (%s)." + # An empty range constant, for use in DEPRECATED_WIRE_VERSIONS. + EMPTY_RANGE = (0...0).freeze + # The wire protocol versions that this version of the driver supports. # # @since 2.0.0 @@ -100,9 +103,16 @@ class Features # be set to a range where the min and max are the same value. # # If there are no currently-deprecated wire versions, this should be - # set to an empty array. + # set to an empty range (e.g. the EMPTY_RANGE constant). DEPRECATED_WIRE_VERSIONS = 6..6 + # make sure the deprecated versions are valid + if DEPRECATED_WIRE_VERSIONS.min + if DRIVER_WIRE_VERSIONS.min > DEPRECATED_WIRE_VERSIONS.max + raise ArgumentError, 'DEPRECATED_WIRE_VERSIONS must be empty, or be within DRIVER_WIRE_VERSIONS' + end + end + # Create the methods for each mapping to tell if they are supported. # # @since 2.0.0 From ffdfdd0982c26ced1546f63e04f33de36827b1c3 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Fri, 23 Jan 2026 10:37:49 -0700 Subject: [PATCH 3/4] extend, not include (for Ruby 2.7) --- lib/mongo/deprecations.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/mongo/deprecations.rb b/lib/mongo/deprecations.rb index 1b1d156d85..9c3c89c70a 100644 --- a/lib/mongo/deprecations.rb +++ b/lib/mongo/deprecations.rb @@ -16,8 +16,7 @@ module Mongo # @api private module Deprecations extend self - - include Mongo::Loggable + extend Mongo::Loggable # Mutex for synchronizing access to warned features. # @api private From c32a07f7e11bdac69e640a3074e354b60f7ce441 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 27 Jan 2026 14:44:34 -0700 Subject: [PATCH 4/4] reverse the prefix-match logic so it actually works --- lib/mongo/deprecations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mongo/deprecations.rb b/lib/mongo/deprecations.rb index 9c3c89c70a..c3d8bb1481 100644 --- a/lib/mongo/deprecations.rb +++ b/lib/mongo/deprecations.rb @@ -81,7 +81,7 @@ def warned_features(reset: false) # @return [ true | false ] If a warning has already been issued. def _warned?(feature, prefix: false) if prefix - warned_features.any? { |f| feature.to_s.start_with?(f) } + warned_features.any? { |f| f.to_s.start_with?(feature) } else warned_features.include?(feature.to_s) end