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

Add context methods #535

Merged
merged 1 commit into from
Feb 1, 2017
Merged

Add context methods #535

merged 1 commit into from
Feb 1, 2017

Conversation

maddyblue
Copy link
Collaborator

@maddyblue maddyblue commented Nov 7, 2016

Progress on #506.


This change is Reviewable

@maddyblue maddyblue mentioned this pull request Dec 1, 2016
@rikonor
Copy link

rikonor commented Dec 22, 2016

Is this WIP?

@maddyblue
Copy link
Collaborator Author

maddyblue commented Dec 22, 2016 via email

@maddyblue
Copy link
Collaborator Author

maddyblue commented Dec 22, 2016 via email

@tamird
Copy link
Collaborator

tamird commented Dec 22, 2016 via email

@jhorowitz
Copy link

Is there a reason not to add something which would send a cancel backend signal to postgres? Unless I'm misunderstanding (which I probably am), you seem to already have the PID. From there, canceling seems pretty simple. As long as you're a superuser, you could call pg_cancel_backend or pg_terminate_backend.

@maddyblue
Copy link
Collaborator Author

No, it does indeed cancel queries on the server. See the conn.Cancel function in conn.go. It opens a new connection instructing the server to cancel the original connection. See https://www.postgresql.org/docs/current/static/protocol-flow.html#AEN112861

@tamird
Copy link
Collaborator

tamird commented Dec 22, 2016 via email

conn.go Outdated
@@ -22,6 22,8 @@ import (
"time"
"unicode"

"golang.org/x/net/context"
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a new dependency? If we're only using this for the interface, it would also be possible to copy just the interface definition into this package, and then avoid depending on golang.org/x/net/context.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We do use it for more than just the interface: see the creations of context.Background(). However since those are empty, it seems not horrible to copy both the interface definition as you describe and the implementation for context.Background into our own file here and just use those. That would allow for not having this dependency. I've made that change.

conn.go Outdated
w.int32(cn.secretKey)

can.sendStartupPacket(w)
_ = can.c.Close()
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems wasteful to open a connection just to send one command, then immediately tear down the whole connection. Is there some way to save this conn for some time so it can potentially be used by the pool?

Copy link
Contributor

Choose a reason for hiding this comment

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

Nope! The protocol for cancellation is to open a new connection that will immediately be closed.

conn.go Outdated
}
}

if r != nil && ctx.Done() != context.Background().Done() {
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this is trying to avoid the overhead of an extra goroutine in the case where the context will never be canceled. It would be better to write it as:

if r != nil && ctx.Done() != nil

Same for the other places that do this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I remember reading some stuff from that it is bad form to do ctx.Done() != nil which is why it was done this way. But I do prefer the way you describe. Changed.

conn.go Outdated
case <-closed:
}
}()
r.close = closed
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe consider pulling this whole blob of code out into a function? It is a bit subtle, and it's repeated several times. Maybe something like:

if r != nil && ctx.Done() != nil {
	r.close = watchCancel(ctx, cn.Cancel)
}

with

func watchCancel(ctx context.Context, cancel func()) chan<- struct{} {
	closed := make(chan struct{})
	go func() {
		select {
		case <-ctx.Done():
			cancel()
		case <-closed:
		}
	}()
	return closed
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Copy link
Collaborator Author

@maddyblue maddyblue left a comment

Choose a reason for hiding this comment

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

RFAL.

Comments about the new context.go would be appreciated. Not sure if it's a good idea, but it does remove the new dependency.

conn.go Outdated
}
}

if r != nil && ctx.Done() != context.Background().Done() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I remember reading some stuff from that it is bad form to do ctx.Done() != nil which is why it was done this way. But I do prefer the way you describe. Changed.

conn.go Outdated
@@ -22,6 22,8 @@ import (
"time"
"unicode"

"golang.org/x/net/context"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We do use it for more than just the interface: see the creations of context.Background(). However since those are empty, it seems not horrible to copy both the interface definition as you describe and the implementation for context.Background into our own file here and just use those. That would allow for not having this dependency. I've made that change.

conn.go Outdated
case <-closed:
}
}()
r.close = closed
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

context.go Outdated
Value(key interface{}) interface{}
}

type emptyCtx int
Copy link
Contributor

Choose a reason for hiding this comment

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

As I recall, an empty struct is actually the smallest value to have lying around.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You are correct, but in order to be a more direct copy of the context code I kept it as an int: https://tip.golang.org/pkg/context/?m=all#emptyCtx

conn.go Outdated
can := &conn{}
can.c, err = dial(cn.dialer, cn.opts)
if err != nil {
return
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there really nothing to be done here? Retry doesn't seem appropriate...

Is this method exported for the driver package? I don't see it in the docs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not sure what else we would do. The cancellation protocol itself says that the request may or may not be successful. And it's unclear what to do in the face of network error. It doesn't seem like an error here should set an error on cn either. This is currently my best idea.

I've unexported this method.

@tamird
Copy link
Collaborator

tamird commented Jan 30, 2017

Why do we need this "contextInterface"? If we could move all the context-related functionality under the 1.8 build tag, we could just use "context" instead.

Seems like we can achieve that by having an embedded structure in conn which is empty when !go.18.


Reviewed 3 of 4 files at r2.
Review status: 3 of 4 files reviewed at latest revision, 4 unresolved discussions.


conn_go18.go, line 32 at r2 (raw file):

func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
	if opts.Isolation != 0 {
		return nil, errors.New("isolation levels not supported")

seems like we can support both this and the below, but i guess that's not required for this PR.


go18_test.go, line 81 at r2 (raw file):

	// Delay execution for just a bit until db.ExecContext has begun.
	go cancel()

seems inherently flaky.


go18_test.go, line 103 at r2 (raw file):

	ctx, cancel := context.WithCancel(context.Background())
	tx, err := db.BeginTx(ctx, nil)

you need to clean up this tx to avoid goroutine leaks, I think.


go18_test.go, line 109 at r2 (raw file):

	// Delay execution for just a bit until tx.Exec has begun.
	go cancel()

ditto


Comments from Reviewable

@maddyblue
Copy link
Collaborator Author

I've moved all the context stuff to a go 1.8-only file. I like this design much more. It also fleshed out a bug.


Review status: 3 of 4 files reviewed at latest revision, 4 unresolved discussions.


conn_go18.go, line 32 at r2 (raw file):

Previously, tamird (Tamir Duberstein) wrote…

seems like we can support both this and the below, but i guess that's not required for this PR.

another pr for sure


go18_test.go, line 81 at r2 (raw file):

Previously, tamird (Tamir Duberstein) wrote…

seems inherently flaky.

I've added a 10ms sleep here. It's difficult to test this correctly.


go18_test.go, line 103 at r2 (raw file):

Previously, tamird (Tamir Duberstein) wrote…

you need to clean up this tx to avoid goroutine leaks, I think.

I added leaktest to this test and it passed without any cleanup. I don't think a Tx starts any goroutines.


go18_test.go, line 109 at r2 (raw file):

Previously, tamird (Tamir Duberstein) wrote…

ditto

Done.


Comments from Reviewable

@tamird
Copy link
Collaborator

tamird commented Jan 31, 2017

:lgtm:

Let's squash this down, too.


Reviewed 3 of 4 files at r3.
Review status: all files reviewed at latest revision, 6 unresolved discussions.


conn.go, line 101 at r3 (raw file):

	scratch   [512]byte
	txnStatus transactionStatus
	txnClose  chan<- struct{}

txnClosed for consistency?


conn.go, line 871 at r3 (raw file):

	if len(args) == 0 {
		// ignore commandTag, our caller doesn't care
		res, _, err = cn.simpleExec(query)

unrelated change?


conn.go, line 889 at r3 (raw file):

		// used.
		st := cn.prepareTo(query, "")
		res, err = st.Exec(args)

also unrelated?


conn.go, line 1330 at r3 (raw file):

type rows struct {
	cn       *conn
	close    chan<- struct{}

consider calling this closed for consistency


conn_go18.go, line 18 at r3 (raw file):

	}
	var closed chan<- struct{}
	if ctx.Done() != nil {

move this below r, err := ... and then you can just set r.close directly instead of having closed in scope


go18_test.go, line 103 at r2 (raw file):

Previously, mjibson (Matt Jibson) wrote…

I added leaktest to this test and it passed without any cleanup. I don't think a Tx starts any goroutines.

Evidence to the contrary: cockroachdb/cockroach@9bf770c

Cancelling the contexts in these tests is likely what's making it work.


Comments from Reviewable

@maddyblue
Copy link
Collaborator Author

Review status: all files reviewed at latest revision, 6 unresolved discussions.


conn.go, line 101 at r3 (raw file):

Previously, tamird (Tamir Duberstein) wrote…

txnClosed for consistency?

Done.


conn.go, line 871 at r3 (raw file):

Previously, tamird (Tamir Duberstein) wrote…

unrelated change?

yes, removed


conn.go, line 889 at r3 (raw file):

Previously, tamird (Tamir Duberstein) wrote…

also unrelated?

removed


conn.go, line 1330 at r3 (raw file):

Previously, tamird (Tamir Duberstein) wrote…

consider calling this closed for consistency

Done.


conn_go18.go, line 18 at r3 (raw file):

Previously, tamird (Tamir Duberstein) wrote…

move this below r, err := ... and then you can just set r.close directly instead of having closed in scope

That's how it was before, but it's a bug. We need to start the cancellation listening before the first row is returned. If this is moved below the cn.query line, then only when we have a rows object are we able to cancel a query.


go18_test.go, line 103 at r2 (raw file):

Previously, tamird (Tamir Duberstein) wrote…

Evidence to the contrary: cockroachdb/cockroach@9bf770c

Cancelling the contexts in these tests is likely what's making it work.

Adding tx.Rollback() at the end produces an error: sql: Transaction has already been committed or rolled back. I'm not convinced adding a check to make sure tx.Rollback returns that error is useful. But I'm getting pretty convinced it doesn't have a leaky go routine if it already thinks it's been rolled back.


Comments from Reviewable

@tamird
Copy link
Collaborator

tamird commented Jan 31, 2017

Can you add 1.8.x to the travis list? In fact, all the versions should end in .x, and tip is apparently now spelled master. https://docs.travis-ci.com/user/languages/go#Specifying-a-Go-version-to-use


Reviewed 2 of 2 files at r4.
Review status: all files reviewed at latest revision, 1 unresolved discussion.


conn_go18.go, line 18 at r3 (raw file):

Previously, mjibson (Matt Jibson) wrote…

That's how it was before, but it's a bug. We need to start the cancellation listening before the first row is returned. If this is moved below the cn.query line, then only when we have a rows object are we able to cancel a query.

Ah, thanks for explaining.


Comments from Reviewable

@maddyblue maddyblue merged commit a6657b2 into lib:master Feb 1, 2017
@maddyblue maddyblue deleted the ctx branch February 1, 2017 01:16
@nathany nathany mentioned this pull request Feb 3, 2017
@nathany
Copy link

nathany commented Feb 3, 2017

thanks! ref #531

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.

7 participants