Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix kwargs passing on Ruby 3.0 for sucker_punch #1495

Merged
merged 2 commits into from
May 10, 2021

Conversation

lloeki
Copy link
Contributor

@lloeki lloeki commented May 5, 2021

The kwargs separation change for Ruby 3.0 is tricky: there are different behaviours on 2.6, 2.7, and 3.0. While ruby2_keywords attempts to smooth that out, it is due to disappear on 3.2, therefore relying on it produces surefire broken code when 3.2 is released and 2.6 EOL'd.

ruby2_keywords also suffers from subtle brokenness by virtue of leveraging an internal stateful flag attached to the hash. This is generally OK for very local uses but gets troublesome with complex delegations and even more for instrumentors. Case in point, attempting to use ruby2_keywords here works for perform_in and perform_async, but does not solve the issue in the __run_perform direct call case.

Thus the only future-proof and reliable syntax on Ruby >= 3 is to capture the arguments with *args, **kwargs and forward them the same way, without ruby2_keywords.

Conversely, the only working syntax on Ruby < 3 is capturing both with *args and forwarding them the same way, using ruby2_keywords on 2.7 to restore the previous behaviour.

Therefore dynamic function definition per version is sadly necessary because of the syntactic difference in argument passing and forwarding.

Fixes #1372

@lloeki lloeki requested review from marcotc, delner and a team May 5, 2021 15:50
@marcotc marcotc added bug Involves a bug integrations Involves tracing integrations labels May 5, 2021
@marcotc marcotc added this to In review in Active work May 5, 2021
@lloeki lloeki force-pushed the fix/1372-sucker_punch-kwargs-on-ruby3 branch from e517a24 to 4048849 Compare May 5, 2021 17:09
@ivoanjo
Copy link
Member

ivoanjo commented May 6, 2021

While ruby2_keywords attempts to smooth that out, it is due to disappear on 3.2

Really? :o

I hadn't spotted this, do you remember where you saw it originally? I'd be really curious to follow the discussion.

@lloeki
Copy link
Contributor Author

lloeki commented May 6, 2021

Really? :o

Yep indeed, straight from the source :)

If you really worry about the portability, use ruby2_keywords. (Acknowledge that Ruby 2.6 or before themselves have tons of corner cases in keyword arguments. :-) ruby2_keywords might be removed in the future after Ruby 2.6 reaches end-of-life. At that point, we recommend to explicitly delegate keyword arguments (see Ruby 3 code above).

Sadly I can't remember where I saw the "might" becoming less evasive and a more definitive "shall", including mention of Ruby 3.2 (the one which will EOL 2.6).

@lloeki lloeki force-pushed the fix/1372-sucker_punch-kwargs-on-ruby3 branch 2 times, most recently from 1a69a0d to 895b190 Compare May 6, 2021 12:41
@lloeki
Copy link
Contributor Author

lloeki commented May 6, 2021

Silly me made a mistake on perform_async, so __run_perform was in fact unaffected (thanks @ivoanjo!).

I dropped the complexity down by removing the *args, **kwargs cases but introduced a version cap to limit max version to 3.2, guaranteed to have ruby2_keywords, thus not break upon upstream release. This should also cover other ruby2_keyword usage in the codebase.

I'm interested in feedback about which would be preferable (version cap or kwargs syntax behind a conditional).

lib/ddtrace/version.rb Outdated Show resolved Hide resolved
Copy link
Member

@ivoanjo ivoanjo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! I think the extra context is really valuable to make it extremely clear why the maximum version is there, but otherwise this is good to go :)

lib/ddtrace/version.rb Outdated Show resolved Hide resolved
lib/ddtrace/version.rb Outdated Show resolved Hide resolved
The kwargs separation change for Ruby 3.0 is tricky: there are different
behaviours on 2.6, 2.7, and 3.0. While ruby2_keywords attempts to smooth
that out, it is due to disappear on 3.2, therefore relying on it
produces surefire broken code when 3.2 is released and 2.6 EOL'd.

Thus the only future-proof and reliable syntax on Ruby >= 3 is to
capture the arguments with *args, **kwargs and forward them the same
way, without ruby2_keywords.

Conversely, the only working syntax on Ruby < 3 is capturing both with
*args and forwarding them the same way, using ruby2_keywords on 2.7 to
restore the previous behaviour.

Therefore either dynamic function definition per version is necessary
because of the syntactic difference in argument passing and forwarding.

An alternative is to cap the gem's maximum Ruby version.
Ruby 3.2 is scheduled to drop ruby2_kwargs, as it would EOL 2.6. At that
point, code depending on *args and respond_to?(:ruby2_keywords) would
silently revert behaviour to a non-3.x compatible one.

Guarantee kwargs behaviour on 3.x can rely on ruby2_keywords being
there. This simplifies the sucker_punch fix without version
conditionals, and protects other uses of ruby2_keywords throughout the
code base.
@lloeki lloeki force-pushed the fix/1372-sucker_punch-kwargs-on-ruby3 branch from d6788c3 to 30bae5e Compare May 10, 2021 12:14
@lloeki lloeki requested a review from ivoanjo May 10, 2021 12:52
@codecov-commenter
Copy link

Codecov Report

Merging #1495 (30bae5e) into master (480ebe2) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1495   +/-   ##
=======================================
  Coverage   98.23%   98.23%           
=======================================
  Files         863      863           
  Lines       41582    41616   +34     
=======================================
+ Hits        40849    40883   +34     
  Misses        733      733           
Impacted Files Coverage Δ
...ib/ddtrace/contrib/sucker_punch/instrumentation.rb 97.87% <100.00%> (+0.14%) ⬆️
spec/ddtrace/contrib/sucker_punch/patcher_spec.rb 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 480ebe2...30bae5e. Read the comment docs.

Copy link
Member

@ivoanjo ivoanjo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome 🚀 💯 👍

@lloeki lloeki merged commit 54981e4 into master May 10, 2021
Active work automation moved this from In review to Merged & awaiting release May 10, 2021
@lloeki lloeki deleted the fix/1372-sucker_punch-kwargs-on-ruby3 branch May 10, 2021 17:01
@github-actions github-actions bot added this to the 0.49.0 milestone May 10, 2021
@ivoanjo ivoanjo moved this from Merged & awaiting release to Released in Active work Aug 9, 2021
ivoanjo added a commit that referenced this pull request Apr 12, 2022
A maximum version was initially added in
#1495 because we expected
the `ruby2_keywords` method to be removed (see the PR for the
discussion).

Now Ruby 3.2.0-preview1 is out and `ruby2_keywords` are still there,
and there's even a recent change for it in
ruby/ruby#5684 that is documented as
"ruby2_keywords needed in 3.2+".

So for now let's bump the maximum version to < 3.3 to allow the
Ruby 3.2 series to be supported and we can keep an eye on the Ruby 3.2
test releases to see if anything changes.

(Otherwise, once Ruby 3.2.0 stable is out, we should probably bump
this to 3.4, and so on...)
ivoanjo added a commit that referenced this pull request May 4, 2022
A maximum version was initially added in
#1495 because we expected
the `ruby2_keywords` method to be removed (see the PR for the
discussion).

Now Ruby 3.2.0-preview1 is out and `ruby2_keywords` are still there,
and there's even a recent change for it in
ruby/ruby#5684 that is documented as
"ruby2_keywords needed in 3.2+".

So for now let's bump the maximum version to < 3.3 to allow the
Ruby 3.2 series to be supported and we can keep an eye on the Ruby 3.2
test releases to see if anything changes.

(Otherwise, once Ruby 3.2.0 stable is out, we should probably bump
this to 3.4, and so on...)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Involves a bug integrations Involves tracing integrations
Projects
Active work
  
Released
Development

Successfully merging this pull request may close these issues.

sucker_punch instrumentation broken for keyword args on Ruby 3
4 participants