10

As my understanding, if we have any code inside this transaction and when it happens any error with (save!, ...) in that block the entire code will revert, here the problem is if any timeout (rack timeout = 12) happens in this block.

def create
  ActiveRecord::Base.transaction do
   // timeout happens
  end
end

How can we rollback a code with a transaction when a Rack::Timeout occurs?

Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
Developer
  • 561
  • 7
  • 29
  • I think the transaction *will* revert if there is a timeout exception raised, no? – Tom Lord May 10 '17 at 12:20
  • But if I'm mistaken, then you can manually `raise ActiveRecord::Rollback` – Tom Lord May 10 '17 at 12:21
  • You meant, when we catch this timeout we have raise rollback exception? – Developer May 10 '17 at 12:42
  • Maybe... But like I say, I think this will be happening already! (If not, can you provide a complete example?) – Tom Lord May 10 '17 at 13:06
  • 1
    if you are referring to th H12 heroku error, than this wont work AFAIK. the error will be raised from outside of your transaction, so no rollback will be issued. you will need to have your own timeout handling within the transaction block. having said this, it's a really bad idea to keep any transaction open for that long. so rather invest your time into building something that is recoverabel – phoet Jan 24 '19 at 21:47
  • How is your controller action able to live long enough for a `Rack::Timeout`? As @phoet suggest you should look to move long running transactions out of your controller. Instead look to create the smallest object required to process the job and give the user a way of tracking it. Creating a job which can be performed asynchronously is a lot more reliable and a better user experience. – TomDunning Jan 25 '19 at 01:45
  • Not what the OP asked, but the only problem I see here is that timeout occurs *after* the transaction block (so the record is created correctly) and *before* the response is sent (so it will throw a timeout error but the record was indeed created). – Redithion Jan 25 '19 at 13:58

3 Answers3

3

When a Rack timeout happens, any transaction in process will be rolled back, but transactions that have already been committed will, of course remain committed. You should not have to worry about it.

Once you start a database transaction, it will eventually either be committed or rolled back. Those are the only two possibilities for ending a transaction. When you commit the transaction, you are saying that you want those changes to be saved regardless of what happens next. If you do not commit the transaction, the database will automatically rollback the transaction once it gets into any state where the transaction cannot move forward, such as a broken network connection.

ActiveRecord automatically commits the transaction when the ActiveRecord::Base.transaction do block exits normally. Abnormal exits may cause ActiveRecord to issue a ROLLBACK command to the database, which is efficient and good practice and returns the connection to a ready state, but it is not strictly necessary, because unless the transaction is explicitly committed, the database will eventually automatically roll it back.

Old Pro
  • 24,624
  • 7
  • 58
  • 106
1

If you look at ActiveRecord::ConnectionAdapters::TransactionManager#within_new_transaction on line 270 and 283 Rails is rescuing Exception. Rescuing Exception will catch anything and everything including kill commands and should generally be avoided. It is used in this case to ensure that no matter what is raised (including Rack::Timeout) the transaction will rollback.

Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
Tom
  • 1,311
  • 10
  • 18
  • Of course, there is nothing to guarantee the `rollback` will succeed at that point, or even that the connection to the database is still open. At some point you just have to rely on the database to automatically roll back an uncommitted transaction. – Old Pro Jan 31 '19 at 00:03
0

You need to explicitly specify an error class, then it will be rescued and ActiveRecord will rollback the transaction.

e.g. Timeout.timeout(1, Timeout::Error) do

https://ruby-doc.org/stdlib-2.4.0/libdoc/timeout/rdoc/Timeout.html

The exception thrown to terminate the given block cannot be rescued inside the block unless klass is given explicitly.

Without it ActiveRecord thinks that there is no error and makes a COMMIT.

It seems it was a default behavior until ruby 2.1

Sergii Mostovyi
  • 1,361
  • 1
  • 15
  • 19