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

Use Thread.handle_interrupt to protect query #592

Merged
merged 3 commits into from
Jun 5, 2015
Merged

Use Thread.handle_interrupt to protect query #592

merged 3 commits into from
Jun 5, 2015

Conversation

tamird
Copy link
Contributor

@tamird tamird commented Mar 11, 2015

When available, we prevent Timeout.timeout from corrupting
connections using Thread.handle_interrupt. Fixes #542.

/cc @sodabrew @eam @rudle @ggilder @zanker

@sodabrew
Copy link
Collaborator

This is amazing, thank you. Does this change mean that d0a5199 can be reverted, or is that still a separate issue?

@tamird
Copy link
Contributor Author

tamird commented Mar 12, 2015

yeah, probably. let me push a commit

@tamird
Copy link
Contributor Author

tamird commented Mar 12, 2015

@sodabrew accidentally broke 1.8.7 with a stabby lambda -- should be green now

client.query("SELECT sleep(2)")
end
rescue Timeout::Error
context 'when a non-standard exception class is raised' do
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sodabrew i think this is the real behaviour we want to demonstrate and test for - we will protect the connection from regular Timeout.timeout but not weird arbitrary exceptions

@tamird tamird mentioned this pull request Mar 12, 2015
@tamird
Copy link
Contributor Author

tamird commented Mar 12, 2015

@sodabrew thoughts?

@tamird tamird mentioned this pull request Mar 13, 2015
@tamird
Copy link
Contributor Author

tamird commented Mar 13, 2015

@sodabrew @brianmario how can I help get this merged?

@sodabrew
Copy link
Collaborator

I've been super busy at work this week and have a kid with an ear infection at home. This is a high priority item on a queue that's not getting any cycles :)

@brianmario
Copy link
Owner

Apologies from me as well, I need to read up on the handle_interrupt
stuff to think about what the implications are. Could you provide a little
more information about what the side effects of this are?

On Fri, Mar 13, 2015 at 9:41 AM, Aaron Stone notifications@github.com
wrote:

I've been super busy at work this week and have a kid with an ear
infection at home. This is a high priority item on a queue that's not
getting any cycles :)

Reply to this email directly or view it on GitHub
#592 (comment).

@tamird
Copy link
Contributor Author

tamird commented Mar 13, 2015

@tamird
Copy link
Contributor Author

tamird commented Mar 18, 2015

@sodabrew @brianmario ping, any thoughts on this?

@brianmario
Copy link
Owner

Sorry I've been meaning to get to this but haven't had the time to spend on
thinking about this. Since this is a newer API I just want to make sure I
fully understand the implications. We've had a lot of issues around this
codepath and timeouts/signals in the past and I'd rather not deal with
introducing any new ones if we can ;)

On Wed, Mar 18, 2015 at 8:39 AM, Tamir Duberstein notifications@github.com
wrote:

@sodabrew https://github.com/sodabrew @brianmario
https://github.com/brianmario ping, any thoughts on this?

Reply to this email directly or view it on GitHub
#592 (comment).

@sodabrew
Copy link
Collaborator

If I understand this correctly, the query simply won't be interrupted by a timeout? The wall-clock time for this would be 5 seconds:

expect { Timeout.timeout(1) { @client.query('SELECT SLEEP(5)') } }.to raise_error(Timeout::Error)

My major concern is documenting that Mysql2::Client#query cannot be interrupted by a Timeout.timeout. This may surprise some users and they will ask for an alternative way to prevent their application from blocking forever on a database query. In many cases, returning failure in a timely manner (even causing the screwy connection state) is more important than succeeding.

I've read this over a few times, and I like it. I think readability is improved by handling the query_options merge in Ruby space before kicking over to C, nice work on that.

@sodabrew
Copy link
Collaborator

Turns out upstream is working on this! MySQL 5.7, Percona 5.6, and MariaDB 10.1 all have max_statement_time for SELECT slated for future GA releases (the syntax looks like it will change a bit before finalized):
http://mysqlserverteam.com/server-side-select-statement-timeouts/
https://mariadb.com/kb/en/mariadb/aborting-statements/

I figure this means a hard timeout at the client end was never supposed to work in the first place.

@tamird
Copy link
Contributor Author

tamird commented Mar 26, 2015

@sodabrew nice find. Is this good to land?

@brianmario
Copy link
Owner

My major concern is documenting that Mysql2::Client#query cannot be interrupted by a Timeout.timeout.

This was my concern as well. If a run-away query is holding up a ruby process in a pre-fork environment, a user could continue making requests until all of the processes were starved and take the site down.

Where otherwise, if we let the caller timeout a query with a Timeout block we can document that they must close and create a new connection as the protocol would be in a broken state. That's a little more complex for them to deal with but I'd say it's a fair trade vs not being able to stop their processes from being used up by a query they can't stop.

What do you guys think?

@charliesome @dbussink @samlambert @tmm1 do you guys have any thoughts on this?

That new max_statement_time definitely looks interesting and feels like a much better way to handle this. That way the server can reset its protocol state and allow more commands to be sent on that connection.

@haileys
Copy link

haileys commented Mar 26, 2015

The big issue I see with using max_statement_time instead is that it only works if the MySQL server is up and responsive. It won't help protect us against timing out due to other network errors.

@tamird
Copy link
Contributor Author

tamird commented Mar 27, 2015

Doesn't read_timeout already provide a sane variety of timeout? https://github.com/brianmario/mysql2/blob/master/spec/mysql2/client_spec.rb#L416:L421

@JoshMcKin
Copy link

read_timeout only interrupts the connection it does not terminate the query. We use it extensively for some sketch setups during scaling projects and often see the timeout error in the rails project but the query is persists in Mysql. I image max_statement_time is will actually terminate the query.

@tamird
Copy link
Contributor Author

tamird commented Mar 27, 2015

@JoshMcKin agreed, but Timeout.timeout also does not terminate the query - I'm only suggesting that it's OK to make #query uninterruptible because read_timeout provides a different mechanism for preventing pre-fork worker starvation, addressing @brianmario's concern.

@JoshMcKin
Copy link

Hi @tamird , sorry if I wasn't clear my intention was to agree read_timeout works well ensure long connections don't block a Rails process indefinitely.

@tamird
Copy link
Contributor Author

tamird commented Apr 2, 2015

ping @brianmario, @charliesome et al. any more thoughts?

@sodabrew
Copy link
Collaborator

sodabrew commented Jun 1, 2015

Would you do a rebase to master now that prepared statements have landed?

tamird added 3 commits June 1, 2015 20:03
When available, we prevent `Timeout.timeout` from corrupting
connections using `Thread.handle_interrupt`. Fixes #542.

Revert "Update specs for Ruby 2.1 Timeout behavior by specifying 'Timeout::Error' as the klass parameter."

This reverts commit d0a5199.
@tamird
Copy link
Contributor Author

tamird commented Jun 2, 2015

Rebased.

@sodabrew
Copy link
Collaborator

sodabrew commented Jun 2, 2015

Locally, I am getting this unit test failure. Ruby 2.0 from OS X Yosemite, MySQL 5.5.

  1) Mysql2::Client#query when a non-standard exception class is raised should handle Timeouts without leaving the connection hanging if reconnect is true
     Failure/Error: expect { client.query('SELECT 1') }.to_not raise_error
       expected no Exception, got #<Mysql2::Error: Lost connection to MySQL server during query>
     # ./spec/mysql2/client_spec.rb:485:in `block (4 levels) in <top (required)>'

This is the test where :reconnect => true is passed to the connection. Odd. Debugging.

@tamird
Copy link
Contributor Author

tamird commented Jun 3, 2015

Any luck, @sodabrew?

@sodabrew
Copy link
Collaborator

sodabrew commented Jun 5, 2015

I found that my local MySQL always fails to reconnect – will have to track down whatever else I was hacking on. Not a blocker to merge. Merging now!

sodabrew added a commit that referenced this pull request Jun 5, 2015
Use `Thread.handle_interrupt` to protect `query`
@sodabrew sodabrew merged commit 9f00684 into brianmario:master Jun 5, 2015
@tamird
Copy link
Contributor Author

tamird commented Jun 5, 2015

\o/ this is huge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

connection fails to clean up in-use state after interrupt
5 participants