Skip to content

Commit

Permalink
move the compaction bug fix in Circuit to a separate module `RubyWi…
Browse files Browse the repository at this point in the history
…thUnfixedCompaction`

and move logic to the `Circuit` constructor instead of the `Compiler.`
don't include this fix per default, as this is going to be fixed in Ruby 3.2.7 and 3.3.7.
  • Loading branch information
apotonick committed Nov 6, 2024
1 parent 00f3500 commit 200a7e3
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 11 deletions.
28 changes: 28 additions & 0 deletions lib/trailblazer/activity/circuit/ruby_with_unfixed_compaction.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion lib/trailblazer/activity/schema/compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
40 changes: 30 additions & 10 deletions test/activity_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)],
Expand All @@ -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

0 comments on commit 200a7e3

Please sign in to comment.