Description
What version of Go are you using (go version
)?
1.10.1
Does this issue reproduce with the latest release?
Probably?
What operating system and processor architecture are you using (go env
)?
linux amd64; running on AWS Elastic Container Service Fargate.
What did you do?
(This is mostly a copy-paste from a golang-sql post, as requested by @kardianos .)
It’s pretty common (for client reasons) for our API server to get context.Canceled errors when clients terminate their connections before server response processing is complete. We detect and treat these differently (as warnings rather than errors, in a logging sense), since they’re client triggered and don’t indicate something wrong with the running of the server. So that’s fine.
But… Every now and then the context getting canceled will first surface as a DB error, like: “QueryRowContext failed: sql: Rows are closed”. It seems like a sort of race condition as to where the canceled state is first detected.
This case looks unavoidable, but, as above, we’d still prefer to detect and downgrade it to a warning. So… Is there a robust way to do this?
The origin of the error suggests that there’s no unique error type for “rows are closed” to compare against. So, is the string used for the error considered stable enough to compare against to detect the error type?
Or maybe any DB error could fall into checking “are the rows closed?” But I don’t see an API for checking that (either in database/sql or pgx).
Or maybe any DB error could fall into checking if the context has been canceled (ctx.Err() == context.Canceled
)? But that seems like it might mask real DB errors if the context was only coincidentally cancelled. Low probability of that occurring, though.
But is there a right way to do this?
Thanks in advance.
Activity
[-]Dealing with "QueryRowContext failed: sql: Rows are closed" due to context canceled[/-][+]database/sql: dealing with "QueryRowContext failed: sql: Rows are closed" due to context canceled[/+]ianlancetaylor commentedon Jun 11, 2018
I'm not clear on what the bug is here. Perhaps @kardianos can clarify.
(Questions rather than bug reports normally go to a forum, not the issue tracker; see https://golang.org/wiki/Questions. But I see that @kardianos suggested posting there.)
kardianos commentedon Jun 11, 2018
The poster wants to be able to differentiate errors caused by context cancelations and other database errors. I don't think we always pass back the context error, esp with the closed rows.
I'll assign to me and look into.
changpingc commentedon Jun 18, 2018
I think here's a reproduction for this error we've been seeing.
dsymonds commentedon Jun 28, 2018
If you want to behave differently based on whether a context has ended, check
ctx.Err() != nil
at the caller. You can't rely oncontext.Canceled
being the error returned, since (a) that would prevent the things you call from ever adding context, and (b) that's not the only error that can signal that a context is done (e.g.context.DeadlineExceeded
, or any error defined by a custom context implementation).adam-p commentedon Jun 28, 2018
@dsymonds For my purposes, it's specifically
context.Canceled
that I care about -- that I want to downgrade to warning. If the server (or DB) is exceeding deadline, then that is a server error and I want it to be an error. If a client app or web page gets closed or navigated away from while the request is being processed (so context canceled), that's not a server error and I want to downgrade it.(But I'm sure your advice will be useful to others who come looking for this stuff.)
Except...
Can you elaborate? I'm not sure I understand.
dsymonds commentedon Jun 28, 2018
If a client provides an arbitrarily short deadline, you'll get
context.DeadlineExceeded
, but it's not the server's fault.My point about the extra context is that it is common for Go errors to be returned up the stack with extra information added. That loses the error type and value identity. For instance, if I wrote a function like this:
Now if
g
orh
fail because the context was canceled, the error returned fromf
won't becontext.Canceled
. If you insist thatf
only return the plain error fromg
orh
then you lose the context of which of those functions was failing, for instance. Or have to overload the error handling inf
to be quite different.changpingc commentedon Jul 5, 2018
@dsymonds I agree I don't think it must return ctx.Err(). However, I would like to see the error to be consistent regardless of when the context is canceled (super short deadline or long deadline) during a query, not sometimes ctx.Err() and sometimes "Rows are closed". I believe the issue summary has additional concerns.
While crafting the test case, I remember among various cases I came up with, only one resulted in "Rows are closed" error instead of ctx.Err(). That leads me to think it's very tricky to foresee the need to check the context, as we'd see ctx.Err() coming back most of the time in testing.
"sql: Rows are closed" also does not indicate the cause of closing. It'd be nice if the error extends ctx.Err() like in your example. Something like go/grpc's statusError seems nice.
kardianos commentedon Jul 5, 2018
@dsymonds I don't think you're wrong, but I think you might be missing the point.
@changpingc I appreciate the test case. I'm not 100% certain we can address the issue, but I'll see what can be reasonably be done. I agree that when possible, it would be good to have the error that caused the abort to be represented by the error returned.
I think the conversation about what to do with errors when they are returned is less relevant here.
adam-p commentedon Jul 24, 2018
We now have now seen another DB error that seems to be caused by context canceled and we're downgrading:
[-]database/sql: dealing with "QueryRowContext failed: sql: Rows are closed" due to context canceled[/-][+]database/sql: if a context is cancelled, then pass back that error[/+]gopherbot commentedon Oct 27, 2018
Change https://golang.org/cl/145204 mentions this issue:
database/sql: prefer to return Rows.lasterr rather then a static error
kardianos commentedon Oct 27, 2018
@adam-p Are you able to review / test the CL https://go-review.googlesource.com/c/go/+/145204 ? I think it would solve your issue.
adam-p commentedon Oct 29, 2018
Getting the DB errors to manifest in my local machine is pretty difficult, so I can't really test it. I read the diff, but it's hard to intuit what
rs.lasterr
will be, as it seems to mostly come from the driver. (I'll try it out in our dev env after release, but that's too late to be very useful.)Question: Do you want to set
rs.lasterr
to theerr
param inlasterrOrErrLocked()
? (You're not. I just wonder if you want the param to becomelasterr
when there isn't already one.)kardianos commentedon Oct 29, 2018
Nope.
If you look at
(*Rows) close(err error)
, it takes an optional error which is set "why" it closed. This is set when the context cancels fromctx.Err()
. This setsrs.lasterr
. Then we check this err msg before return a generic error message (maybe there is a more specific error we can return).