diff --git a/gems/aws-sdk-core/CHANGELOG.md b/gems/aws-sdk-core/CHANGELOG.md index e9eac7fa8e4..c7f09a60473 100644 --- a/gems/aws-sdk-core/CHANGELOG.md +++ b/gems/aws-sdk-core/CHANGELOG.md @@ -1,6 +1,8 @@ Unreleased Changes ------------------ +* Issue - Mix `PageableResponse` into `Seahorse::Client::Response` once instead of extending every response object, which cleared Ruby's method cache for each of the extension's methods on every request and made YJIT discard compiled code. + 3.257.0 (2026-09-14) ------------------ diff --git a/gems/aws-sdk-core/lib/aws-sdk-core/pageable_response.rb b/gems/aws-sdk-core/lib/aws-sdk-core/pageable_response.rb index 564054495c0..f2ca7f527ce 100644 --- a/gems/aws-sdk-core/lib/aws-sdk-core/pageable_response.rb +++ b/gems/aws-sdk-core/lib/aws-sdk-core/pageable_response.rb @@ -49,7 +49,7 @@ module Aws module PageableResponse def self.apply(base) - base.extend Extension + base.extend Extension unless Extension === base base.instance_variable_set(:@last_page, nil) base.instance_variable_set(:@more_results, nil) base @@ -217,5 +217,10 @@ def next_page_params(params) end end + + # Mixed into the response class once rather than extended onto each + # response: Kernel#extend clears the method cache for every method the + # extension defines, process-wide, on every request. + Seahorse::Client::Response.include(Extension) end end diff --git a/gems/aws-sdk-core/spec/aws/pageable_response_spec.rb b/gems/aws-sdk-core/spec/aws/pageable_response_spec.rb index 34fa1a77885..a8a082e2852 100644 --- a/gems/aws-sdk-core/spec/aws/pageable_response_spec.rb +++ b/gems/aws-sdk-core/spec/aws/pageable_response_spec.rb @@ -274,6 +274,27 @@ def pageable(resp, pager) PageableResponse.apply(object) }.to_not change { RubyVM.stat(key) } end + + # Kernel#extend creates a singleton class and clears the method cache for every + # method the module defines (each, count, to_h, respond_to?, all of Enumerable...), + # process-wide, so it must not happen once per response. + it 'does not give a Seahorse::Client::Response a singleton class' do + skip 'Only applies to MRI' unless defined? RubyVM.stat + + resp = Seahorse::Client::Response.new + GC.disable + classes_before = ObjectSpace.count_objects.values_at(:T_CLASS, :T_ICLASS) + PageableResponse.apply(resp) + expect(ObjectSpace.count_objects.values_at(:T_CLASS, :T_ICLASS)).to eq(classes_before) + ensure + GC.enable + end + + it 'leaves every Seahorse::Client::Response pageable' do + resp = Seahorse::Client::Response.new + expect(resp).to be_kind_of(PageableResponse::Extension) + expect(resp).to be_kind_of(Enumerable) + end end end