From 5d7345c8b909701cd65385871d6916ac9a6a61b1 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 6 Nov 2026 15:07:49 +0100 Subject: [PATCH] Avoid creating Struct at runtime `Struct.new` create a new class and define new methods, which isn't something you want to happen at runtime as it flushes some VM caches, and can generate an unbounded amount of methods, which may be compiled by the JIT. Since these Struct hold configuration data, we can define a simpler class that rely on `method_missing`. Attribute access are very sligthly slower, but negligible for a network API client, and much more friendly to the Ruby VM. --- gems/aws-sdk-core/CHANGELOG.md | 2 + .../lib/seahorse/client/configuration.rb | 54 ++++++++++++++++++- .../spec/seahorse/client/base_spec.rb | 2 +- .../seahorse/client/configuration_spec.rb | 4 +- 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/gems/aws-sdk-core/CHANGELOG.md b/gems/aws-sdk-core/CHANGELOG.md index f815aff5271..39e30cc8ce2 100644 --- a/gems/aws-sdk-core/CHANGELOG.md +++ b/gems/aws-sdk-core/CHANGELOG.md @@ -1,6 +1,8 @@ Unreleased Changes ------------------ +* Feature - Seahorse::Client::Configuration no longer generate real Ruby Struct at runtime. + 3.265.0 (2026-11-06) ------------------ diff --git a/gems/aws-sdk-core/lib/seahorse/client/configuration.rb b/gems/aws-sdk-core/lib/seahorse/client/configuration.rb index 683a053d26d..7b22e250b57 100644 --- a/gems/aws-sdk-core/lib/seahorse/client/configuration.rb +++ b/gems/aws-sdk-core/lib/seahorse/client/configuration.rb @@ -73,6 +73,58 @@ def call(*args) end end + # @api private + class DynamicStruct + attr_reader :members + + def initialize(members) + @members = members.map(&:to_sym).to_set + @attributes = {} + end + + def [](name) + name = name.to_sym + unless @members.include?(name) + raise NameError, "No member #{name} in Struct" + end + + @attributes[name] + end + + def []=(name, value) + name = name.to_sym + unless @members.include?(name) + raise NameError, "No member #{name} in Struct" + end + + @attributes[name] = value + end + + private + + alias_method :write_attribute, :[]= + alias_method :read_attribute, :[] + + def method_missing(name, *args, **kwargs, &block) + if name.end_with?('=') + attr_name = name[0..-2].to_sym + if @members.include?(attr_name) + write_attribute(attr_name, *args, **kwargs, &block) + else + super + end + elsif @members.include?(name) + read_attribute(name, *args, **kwargs, &block) + else + super + end + end + + def respond_to_missing?(name, include_private = false) + @members.include?(name) || (name.end_with?('=') && @members.include?(name[0..-2].to_sym)) || super + end + end + # @api private def initialize @defaults = Hash.new { |h,k| h[k] = Defaults.new } @@ -156,7 +208,7 @@ def build!(options = {}) private def empty_struct - Struct.new(*@defaults.keys.sort).new + DynamicStruct.new(@defaults.keys.sort) end def apply_options(struct, options) diff --git a/gems/aws-sdk-core/spec/seahorse/client/base_spec.rb b/gems/aws-sdk-core/spec/seahorse/client/base_spec.rb index f500163c256..3bab868e6c2 100644 --- a/gems/aws-sdk-core/spec/seahorse/client/base_spec.rb +++ b/gems/aws-sdk-core/spec/seahorse/client/base_spec.rb @@ -24,7 +24,7 @@ module Client describe '#config' do it 'returns a Configuration struct' do - expect(client.config).to be_kind_of(Struct) + expect(client.config).to be_kind_of(Configuration::DynamicStruct) end it 'contains the client api' do diff --git a/gems/aws-sdk-core/spec/seahorse/client/configuration_spec.rb b/gems/aws-sdk-core/spec/seahorse/client/configuration_spec.rb index 37842a70436..ab4cae5f999 100644 --- a/gems/aws-sdk-core/spec/seahorse/client/configuration_spec.rb +++ b/gems/aws-sdk-core/spec/seahorse/client/configuration_spec.rb @@ -66,7 +66,7 @@ module Client it 'returns self so it can be chained' do c = config.add_option(:name).add_option(:color) expect(c).to be(config) - expect(c.build!.members).to eq([:color, :name]) + expect(c.build!.members).to eq(Set[:color, :name]) end end @@ -74,7 +74,7 @@ module Client describe '#build!' do it 'returns a Struct' do - expect(config.add_option(:opt).build!).to be_kind_of(Struct) + expect(config.add_option(:opt).build!).to be_kind_of(Configuration::DynamicStruct) end it 'accepts a hash of options' do