The Wayback Machine - https://web.archive.org/web/20201020111901/https://github.com/go-sql-driver/mysql/pull/1095
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 checking cancelled connections back into the connection pool #1095

Conversation

@KJTsanaktsidis
Copy link
Contributor

@KJTsanaktsidis KJTsanaktsidis commented May 11, 2020

Description

If

  • BeginTx is called with a non-default isolation level,
  • The context is canceled before SET TRANSACTION ISOLATION LEVEL completes,

then the connection:

  • has the cancelled property set to "context cancelled",
  • has the closed property set to true,

and,

  • BeginTx returns "context canceled"

Because of this, the connection gets put back into the connection pool. When it is checked out again, if BeginTx is called on it again without an isolation level,

  • then we fall into the mc.closed.IsSet() check in begin(),
  • so we return ErrBadConn,
  • so the driver kicks the broken connection out of the pool
  • (and transparently retries to get a new connection that isn't broken too).

However, if BeginTx is called on the connection with an isolation
level, then we return a context canceled error from the SET TRANSACTION
ISOLATION LEVEL call.

That means the broken connection will stick around in the pool forever
(or until it's checked out for some other operation that correctly
returns ErrBadConn).

The fix is to check for the connection being closed before executing SET
TRANSACTION ISOLATION LEVEL.

Checklist

  • Code compiles correctly
  • Created tests which fail without the change (if possible)
  • All tests passing
  • Extended the README / documentation, if necessary
  • Added myself / the copyright holder to the AUTHORS file
@KJTsanaktsidis KJTsanaktsidis force-pushed the zendesk:ktsanaktsidis/PEN-734/fix_isolation_level_cancel branch from 649efc7 to dee6989 May 11, 2020
@KJTsanaktsidis
Copy link
Contributor Author

@KJTsanaktsidis KJTsanaktsidis commented May 11, 2020

Github didn't get the memo but if you click through to the travis build it's green.

if err != nil {
dbt.Fatal(err)
}

// Delay execution for just a bit until db.ExecContext has begun.
defer time.AfterFunc(100*time.Millisecond, cancel).Stop()
go time.AfterFunc(100*time.Millisecond, cancel)

This comment has been minimized.

@methane

methane May 12, 2020
Member

Why this change?

This comment has been minimized.

@KJTsanaktsidis

KJTsanaktsidis May 12, 2020
Author Contributor

I actually didn't understand how this was working at all - what it looked like it should be doing was canceling the context whilst the SQL was executing SLEEP(1), so I turned it into this.

I just read the docs for time.AfterFunc more carefully and I think we can just invoke it on the main goroutine instead of calling go?

This comment has been minimized.

@KJTsanaktsidis

KJTsanaktsidis May 12, 2020
Author Contributor

Eurgh - ok the problem is I just don't know how defer works. This is going to cause time.AfterFunc() to run now, and then Stop() to run when the method returns. I'll change it back.

@KJTsanaktsidis KJTsanaktsidis force-pushed the zendesk:ktsanaktsidis/PEN-734/fix_isolation_level_cancel branch from dee6989 to 1b73282 May 12, 2020
driver_test.go Outdated Show resolved Hide resolved
If
    * BeginTx is called with a non-default isolation level,
    * The context is canceled before SET TRANSACTION ISOLATION LEVEL
    completes,
then the connection:
    * has the cancelled property set to "context cancelled",
    * has the closed property set to true,
and,
    * BeginTx returns "context canceled"

Because of this, the connection gets put back into the connection pool.
When it is checked out again, if BeginTx is called on it again _without_
an isolation level,
    * then we fall into the mc.closed.IsSet() check in begin(),
    * so we return ErrBadConn,
    * so the driver kicks the broken connection out of the pool
    * (and transparently retries to get a new connection that isn't
    broken too).

However, if BeginTx is called on the connection _with_ an isolation
level, then we return a context canceled error from the SET TRANSACTION
ISOLATION LEVEL call.

That means the broken connection will stick around in the pool forever
(or until it's checked out for some other operation that correctly
returns ErrBadConn).

The fix is to check for the connection being closed before executing SET
TRANSACTION ISOLATION LEVEL.
@KJTsanaktsidis KJTsanaktsidis force-pushed the zendesk:ktsanaktsidis/PEN-734/fix_isolation_level_cancel branch from 1b73282 to 978b6bd May 12, 2020
@methane methane merged commit 6313f20 into go-sql-driver:master May 12, 2020
3 checks passed
3 checks passed
Travis CI - Pull Request Build Passed
Details
WIP Ready for review
Details
coverage/coveralls Coverage increased (+0.02%) to 81.672%
Details
@KJTsanaktsidis
Copy link
Contributor Author

@KJTsanaktsidis KJTsanaktsidis commented May 12, 2020

Thank you for a quick review & merge!

@julienschmidt julienschmidt added the bug label May 12, 2020
@julienschmidt julienschmidt added this to the v1.6.0 milestone May 12, 2020
tz70s added a commit to tz70s/mysql that referenced this pull request Sep 5, 2020
…sql-driver#1095)

If
    * BeginTx is called with a non-default isolation level,
    * The context is canceled before SET TRANSACTION ISOLATION LEVEL
    completes,
then the connection:
    * has the cancelled property set to "context cancelled",
    * has the closed property set to true,
and,
    * BeginTx returns "context canceled"

Because of this, the connection gets put back into the connection pool.
When it is checked out again, if BeginTx is called on it again _without_
an isolation level,
    * then we fall into the mc.closed.IsSet() check in begin(),
    * so we return ErrBadConn,
    * so the driver kicks the broken connection out of the pool
    * (and transparently retries to get a new connection that isn't
    broken too).

However, if BeginTx is called on the connection _with_ an isolation
level, then we return a context canceled error from the SET TRANSACTION
ISOLATION LEVEL call.

That means the broken connection will stick around in the pool forever
(or until it's checked out for some other operation that correctly
returns ErrBadConn).

The fix is to check for the connection being closed before executing SET
TRANSACTION ISOLATION LEVEL.
tz70s added a commit to tz70s/mysql that referenced this pull request Sep 5, 2020
…sql-driver#1095)

If
    * BeginTx is called with a non-default isolation level,
    * The context is canceled before SET TRANSACTION ISOLATION LEVEL
    completes,
then the connection:
    * has the cancelled property set to "context cancelled",
    * has the closed property set to true,
and,
    * BeginTx returns "context canceled"

Because of this, the connection gets put back into the connection pool.
When it is checked out again, if BeginTx is called on it again _without_
an isolation level,
    * then we fall into the mc.closed.IsSet() check in begin(),
    * so we return ErrBadConn,
    * so the driver kicks the broken connection out of the pool
    * (and transparently retries to get a new connection that isn't
    broken too).

However, if BeginTx is called on the connection _with_ an isolation
level, then we return a context canceled error from the SET TRANSACTION
ISOLATION LEVEL call.

That means the broken connection will stick around in the pool forever
(or until it's checked out for some other operation that correctly
returns ErrBadConn).

The fix is to check for the connection being closed before executing SET
TRANSACTION ISOLATION LEVEL.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

4 participants
You can’t perform that action at this time.