diff --git a/lib/trailblazer/activity/circuit/ruby_with_unfixed_compaction.rb b/lib/trailblazer/activity/circuit/ruby_with_unfixed_compaction.rb new file mode 100644 index 0000000..1cb0632 --- /dev/null +++ b/lib/trailblazer/activity/circuit/ruby_with_unfixed_compaction.rb @@ -0,0 +1,28 @@ +module Trailblazer + class Activity + # TODO: we can remove this once we drop Ruby <= 3.3.6. + class Circuit + # This is a hot fix for Ruby versions that haven't fixed the GC compaction bug: + # https://redmine.ruby-lang.org/issues/20853 + # https://bugs.ruby-lang.org/issues/20868 + # + # Affected versions might be: 3.1.x, 3.2.?????????, 3.3.0-3.3.6 + # You don't need this fix in the following versions: + # + # If you experience this bug: https://github.com/trailblazer/trailblazer-activity/issues/60 + # + # NoMethodError: undefined method `[]' for nil + # + # you need to do + # + # Trailblazer::Activity::Circuit.include(RubyWithUnfixedCompaction) + module RubyWithUnfixedCompaction + def initialize(wiring, *args, **options) + wiring.compare_by_identity + + super(wiring, *args, **options) + end + end + end + end +end diff --git a/lib/trailblazer/activity/schema/compiler.rb b/lib/trailblazer/activity/schema/compiler.rb index ffa42cf..4873719 100644 --- a/lib/trailblazer/activity/schema/compiler.rb +++ b/lib/trailblazer/activity/schema/compiler.rb @@ -24,7 +24,7 @@ def call(intermediate, implementation, config_merge: {}) # From the intermediate "template" and the actual implementation, compile a {Circuit} instance. def schema_components(intermediate, implementation, config) - wiring = {}.compare_by_identity # https://ruby-doc.org/3.3.0/Hash.html#class-Hash-label-Modifying+an+Active+Hash+Key + wiring = {} nodes_attributes = [] intermediate.wiring.each do |task_ref, outs| diff --git a/test/activity_test.rb b/test/activity_test.rb index f0da037..78a65af 100644 --- a/test/activity_test.rb +++ b/test/activity_test.rb @@ -88,10 +88,12 @@ def call(*) end end - -if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.2") +if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.1") + # TODO: we can remove this once we drop Ruby <= 3.3.6. class GCBugTest < Minitest::Spec it "still finds {.method} tasks after GC compression" do + ruby_version = Gem::Version.new(RUBY_VERSION) + intermediate = Activity::Schema::Intermediate.new( { Activity::Schema::Intermediate::TaskRef(:a) => [Activity::Schema::Intermediate::Out(:success, :b)], @@ -110,21 +112,39 @@ module Step :b => Schema::Implementation::Task(Trailblazer::Activity::End.new(semantic: :success), [], []), } - activity = Activity.new(Activity::Schema::Intermediate::Compiler.(intermediate, implementation)) assert_invoke activity, seq: %([:create]) - # TODO: add tests for different Rubys - GC.verify_compaction_references(expand_heap: true, toward: :empty) + if ruby_version >= Gem::Version.new("3.1") && ruby_version < Gem::Version.new("3.2") + require "trailblazer/activity/circuit/ruby_with_unfixed_compaction" + Trailblazer::Activity::Circuit.prepend(Trailblazer::Activity::Circuit::RubyWithUnfixedCompaction) + elsif ruby_version >= Gem::Version.new("3.2.0") && ruby_version <= Gem::Version.new("3.2.6") + require "trailblazer/activity/circuit/ruby_with_unfixed_compaction" + Trailblazer::Activity::Circuit.prepend(Trailblazer::Activity::Circuit::RubyWithUnfixedCompaction) + elsif ruby_version >= Gem::Version.new("3.3.0") && ruby_version <= Gem::Version.new("3.3.6") + require "trailblazer/activity/circuit/ruby_with_unfixed_compaction" + Trailblazer::Activity::Circuit.prepend(Trailblazer::Activity::Circuit::RubyWithUnfixedCompaction) + end - # Without the fix, this *might* throw the following exception: - # - # NoMethodError: undefined method `[]' for nil - # /home/nick/projects/trailblazer-activity/lib/trailblazer/activity/circuit.rb:80:in `next_for' + activity = Activity.new(Activity::Schema::Intermediate::Compiler.(intermediate, implementation)) + + ruby_version_specific_options = + if ruby_version >= Gem::Version.new("3.2") # FIXME: future + {expand_heap: true, toward: :empty} + else + {} + end + + # Provoke the bug: + GC.verify_compaction_references(**ruby_version_specific_options) + + # Without the fix, this *might* throw the following exception: + # + # NoMethodError: undefined method `[]' for nil + # /home/nick/projects/trailblazer-activity/lib/trailblazer/activity/circuit.rb:80:in `next_for' assert_invoke activity, seq: %([:create]) end - end end