-
Notifications
You must be signed in to change notification settings - Fork 419
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
undefined method `exception' for nil:NilClass with Ruby 3.1.0 #931
Comments
ProblemI'm having the same error via
WorkaroundChanging |
Here is a repro: https://github.com/aaronjensen/concurrent-repro Install gems, then run ::Rails.application.assets_manifest.find_sources("application.css").first |
I'm still tracking this down in a few spare moments. Via In cases where this is successful, |
@agrberg Right, because for some reason the concurrent-ruby/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb Lines 23 to 29 in 52c08fc
The furthest I see things going is https://github.com/rails/sprockets/blob/v4.0.2/lib/sprockets/manifest.rb#L143 and then next thing I know it's returning from |
I found the same thing! I disabled the source of the error by changing this line to
My hunch is that there is a race condition between this file being requested/accessed as when I "slow" the process down with my workaround, the information is found and everything works. |
I believe that error only happens because of the thing I described. It is not the actual error as far as I can tell, rather the natural consequence of the asset loader returning nil rather than an asset. |
I narrowed the repro down to just concurrent-ruby: https://github.com/aaronjensen/concurrent-repro require "concurrent-ruby"
def sub
yield
end
def run
return to_enum(__method__) unless block_given?
executor = :fast
# This works
# executor = :immediate
promises = [
Concurrent::Promise.execute(executor: executor) do
# This version fails on Ruby 3.0.3 as well
# yield "some-value"
# This version only fails in Ruby 3.1.0
sub do |value|
yield "some-value"
end
end
]
promises.each(&:wait!)
nil
end
# This does not work
run.first
# This works
# run.to_a.first
puts "It worked"
|
This was introduced by this commit:
cc @tenderlove |
It's seeming like this is a bug in concurrent-ruby, which does not account for the local jump that happens when an enumerator breaks when |
#932 should fix it |
Thanks will have to find some time to get my head around the issue and the PR. |
Awesome job debugging this dude! 🙇 |
This weekend I upgraded my tiny app to Rails 7 and transitioned from Webpacker to importmaps. I exercised the latter by precompiling my assets locally and stumbled onto another work around when attempting the Ruby 3.1 upgrade. The specific case w/ The underlying bug still remains where the assets are considered missing when they're intended to be generated dynamically. |
@chrisseaton have you had a chance to look at this? This is the only thing blocking us from upgrading to 3.1.0 and I'd rather not have to patch premailer-rails, though if you are thinking of not accepting this, please let me know and I can open a PR there to work around this. Thank you! |
@aaronjensen, did you find a solution to make this work while we wait for the PR to be reviewed? |
@thomasvanholder because it's ruby, you can redefine We have been holding on monkey-patching hoping this gets merged, but we may need to just do it if we want to upgrade Ruby (or submit a workaround to premailer-rails). |
@eregon it looks like Chris may be busy, would you be able to take a look at this by chance? |
Sorry I will manage to take a look today. |
No problem, and thank you. |
Hi @chrisseaton and @eregon sorry to prod, but would it be possible to take a look at this? |
It would be really good if this could either move forward, or we could find out what else is being done to unblock Ruby 3.1.0 upgrades... |
Very sorry! I'll definitely take a look in the next 24 hours. |
I can reproduce. Sorry I know that's just a start but evidence I'm on it. |
Great, thanks @chrisseaton. Not sure if you saw my PR or not, but it fixes it w/ minimal changes and has a test: #932 |
While there may be an underlying issue in |
That’s great news, thank you @tomstuart |
PR was merged, thanks @chrisseaton |
The test added in #932 is causing various problems, for instance it doesn't work on JRuby (#932), it doesn't work on TruffleRuby (https://github.com/ruby-concurrency/concurrent-ruby/runs/5554594154?check_suite_focus=true), and a fairly small variation of that test segfaults on CRuby 3.0 (https://bugs.ruby-lang.org/issues/18637) or behaves incorrectly (https://bugs.ruby-lang.org/issues/18474). That test seems too crazy, it's creating an Enumerator with I'm inclined to remove the test (cost is too high compared to value, and it's extremely difficult to understand what the test is trying to do), unless we find a way to rewrite so it doesn't involve a yield from another Thread. |
Sorry, I missed you reported this issue to CRuby, that gives more details: https://bugs.ruby-lang.org/issues/18474 So in CRuby 3.1 and in TruffleRuby, this code gives a puts RUBY_DESCRIPTION
def execute
Thread.new do
yield 42
end.join
end
p first: to_enum(:execute).first
I think what we'd need is to tweak the test to just raise |
@eregon If you could instead limit the test to running only on the affected environment (CRuby >= 3.1) then it could maintain the shape of the actual problem. I don't know if just raising LocalJumpError actually does what you would think (I believe I tried that, but I could be mistaken). |
Good point, I'll try to repro the original problem by locally reverting the fix on CRuby 3.1, and see if I can simplify the test. |
So, using the minimal example from mame in https://bugs.ruby-lang.org/issues/18474: def foo
Thread.new do
1.times do
yield 42
end
p "should_not_reach_here"
end.join
end
p to_enum(:foo).first Here are the results:
CRuby 3.0 behavior is a clear bug, it prints def first
each { |e| break e } # and somehow return nil if no element not sure how CRuby does that but irrelevant here
end and the break, instead of breaking out of that CRuby 3.1 doesn't have this bug, instead it's a LocalJumpError which means it can't find where to break, and indeed it would need to break/jump to another thread stack, which is of course impossible. TruffleRuby also raises a LocalJumpError but it's about def first(n=undefined)
return __take__(n) unless Primitive.undefined?(n)
each do
o = Primitive.single_block_arg
return o
end
nil
end JRuby uses a separate exception, a ThreadError, which is arguably more helpful, so that behavior sounds fine too. So it's a bug of CRuby < 3.1. |
If I make a reproducer closer to the actual code, I see that CRuby is still broken :/ def synchronize
yield
end
def execute(task)
success = true
value = reason = nil
end_sync = false
synchronize do
begin
p :before
value = task.call
p :never_reached
success = true
rescue StandardError => ex
p [:rescue, ex]
reason = ex
success = false
end
end_sync = true
p :end_sync
end
p :should_not_reach_here! unless end_sync
[success, value, reason]
end
def foo
Thread.new do
result = execute(-> { yield 42 })
p [:result, result]
end.join
end
p [:first, to_enum(:foo).first] Results:
CRuby (3.0 and 3.1) print |
My conclusion is:
|
@eregon Thank you for investigating this and for your very complete explanation of what was going on. I suspect CRuby's behaviour comes down to the reality that CRuby's thread handling is, and always has been, not-really-thread handling! |
Thanks @eregon, that sounds great. |
After upgrading to Ruby 3.1.0 I'm getting a very strange, very hard (for me) to debug error with premailer+sprockets that ultimately manifests with concurrent failing to provide a reason for a promise rejection.
The error is strange enough that it seems like it could be a ruby bug.
When stepping through with byebug I get here:
https://github.com/rails/sprockets/blob/v4.0.2/lib/sprockets/manifest.rb#L143
Then, drilling into
asset.source
, it returns the source fine. Theyield
however causes a jump all the way out of thebegin
inSafeTaskExecutor#execute
. If I put anensure
on that begin, it runs. Nothing after@task.call
in the block runs. No exception can be caught.I then get this:
I may be able to narrow down a repro if that would be useful, but I wanted to file this now in case there are already any known issues or work arounds I'm not aware of.
Thanks!
The text was updated successfully, but these errors were encountered: