Some background
PG::InFailedSqlTransaction appears when some PG exception is rescued and prevents the transaction from rollback. Simple example:
ActiveRecord::Base.transaction do
ActiveRecord::Base.connection.execute('SELECT nothing FROM nowhere') rescue nil
binding.pry # <- let's assume we're here
User.count # raises PG::InFailedSqlTransaction
end
The first line inside the transaction silently breaks the transaction and proceeds, any SQL statements after the first line will raise PG::InFailedSqlTransaction: ERROR: current transaction is aborted, commands ignored until end of transaction block.
The question
Assuming we're on the binding.pry breakpoint above, are there any ways to debug it and get the initial exception or any other data?
Not quite sure how it is implemented under the hood, but it seems quite possible for PG to cache some metadata about errors.
EDIT: the code above is just an example to demonstrate the general issue, the question is how to get the error in the situation when we cannot easily find the place that rescues the exception
Per #engineersmnky comment, it is possible to get the last error message from the PG::Connection instance with #error_message method, the instance can be obtained from the ActiveRecord connection: ActiveRecord::Base.connection.raw_connection.error_message.
If we run the above command from the pry breakpoint in the example from the question, it will return the following message:
ERROR: relation "nowhere" does not exist
LINE 1: SELECT nothing FROM nowhere
^
Related
I've got a GCP pubsub listener that does some work and then saves to ActiveRecord. I don't want to do that work if the DB connection is down, so I've added a pre-flight check. The pre-flight check checks the DB connection, and if it fails, eats the error and raises a RuntimeError. The DB is flighty though, and to account for the scenario where the pre-flight succeeds, but the DB connection dies while the work is being done, I have the caller rescuing ActiveRecord::ActiveRecordError and PG::Error, so we can log that the work was done, but the receipt couldn't be persisted. It's more important that this work not be duplicated than for the receipt to be persisted, so RuntimeError isn't caught, (causing a retry), but the DB errors are. It looks like this (snipping significantly):
# Service
def process
begin
WorkReceipt.do_work
rescue ActiveRecord::ActiveRecordError, PG::Error
Rails.logger.error("Work was done successfully, but not persisted")
end
end
# Model
class WorkReceipt < ActiveRecord::Base
def self.do_work
if !ActiveRecord::Base.connection.active?
Rails.logger.error("DB connection is inactive. Reconnecting...")
begin
ActiveRecord::Base.connection.reconnect!
rescue => e
Rails.logger.error("Could not reestablish connection: #{e}")
raise "Could not connect to database"
end
end
# Lots of hard work
self.create!(
# Some args
)
end
end
Where things get weird is, while testing this, I brought down the DB and fired off 4 of these tasks. The first one handles correctly ("Could not reestablish connection: server closed the connection unexpectedly"), but then the other 3 get "DB connection is inactive. Reconnecting..." (good) followed by "Work was done successfully, but not persisted" (what?!). Even weirder, is that the work has logging and side-effects which I don't see happening. The pre-flight appears to correctly prevent the work from being done, but the database error is showing up in the outer rescue, preventing the retry and making me sad. There is no database access other than the create at the end.
What is going on here? Why does it seem like the database error is skipping past the inner rescue to be caught by the outer one?
Maybe I don't understand how Ruby works, but changing raise "Could not connect to database" to raise RuntimeError.new "Could not connect to database" fixes the problem. I was under the impression that providing a message to raise caused it to emit a RuntimeError without needing to be explicit about it, but here we are.
So whenever I run a test, Rails seems to be attempting to insert nothing into my PostgreSQL database... causing a syntax error. What could possibly cause this? For some context, this happens with every test, regardless of how simple or complex. For example:
class PlayerTest < ActiveSupport::TestCase
test "should not save empty player" do
assert true
end
end
And then I see the following error message:
Error:
PlayerTest#test_should_not_save_empty_player:
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near ")"
LINE 1: INSERT INTO "players_venues" () VALUES ()
Also, players_venues is a many-to-many join table between players and venues. This problem does not occur outside of tests. Any help would be greatly appreciated. If any more code is required please don't hesitate to ask.
So I eventually figured this out. It turns out Rails generates empty fixtures like so:
one: {}
# column: value
#
two: {}
# column: value
When you run the tests it attempts to insert nothing! Thanks heaps to jdno for giving my a hint to look into the fixtures. Bloody champion.
I'm wondering if rails caches anything in the following case:
Rails.cache.fetch("some_key", expires_in: 1.day) do
service.call # raises exception
[]
end
I'm concerned because if the request inside the Rails.cache.fetch block fails, I want to retry on the next request. Not make the user wait 24HRS to retry.
No. Rails doesn't cache anything if an exception is raised.
Rails Guides says that the return value of the block will be written to the cache.
When a block raises an exception, it doesn't return anything, therefore nothing is cached.
Let's say you have the following example code block:
def next_page(next_token)
client.list_order_items_by_next_token(next_token)
rescue => error
binding.pry
end
Without diving into the issue that this rescue is capturing all errors and how that is bad (this block has been modified) is there a way to determine the method list_order_items_by_next_token caused the issue? I know the stack trace is available but that does not feel right.
Just use
error.backtrace
Don't worry about performance.
By the time you rescue the exception the cost of creating the backtrace has already occurred. The backtrace is created when the exception is raised. And it is expensive! Ruby has to create O(N) new strings whenever an exception is raised, where N is the depth of the stacktrace. Which can be 1000+ in a common Rails application. It gets even worse in a loop. And all these strings are usually never used and just clog up garbage collection. In our production system those backtrace strings are one of the major causes of performance degradation. But as I said, that cost occurs when the exception is raised, accessing the backtrace later is free of cost.
I want to verify that an exception is being thrown in some function tests by calling get :methodname, :params. I had expected just making this call and then verifying the result is a 500 would be sufficient, but rails is failing the test as soon as the exception is reached in the get. While I figure I could go ahead and wrap the get in a rescue and verify the exception is thrown myself, and would hope there's a better method for doing this.
What's the best way to verify an exception is thrown when using a get in your tests?
You can do:
def test_sommat
assert_raises SomeException do
get :methodname, params
end
end