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

api: more informative request canceling #407

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.

### Changed

- More informative request canceling: log the probable reason for unexpected request ID
and add request ID info to context done error message (#407).

### Fixed

## [2.1.0] - 2024-03-06
Expand Down
9 changes: 5 additions & 4 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ func (d defaultLogger) Report(event ConnLogKind, conn *Connection, v ...interfac
conn.Addr(), err)
case LogUnexpectedResultId:
header := v[0].(Header)
log.Printf("tarantool: connection %s got unexpected resultId (%d) in response",
log.Printf("tarantool: connection %s got unexpected request ID (%d) in response "+
"(probably cancelled request)",
conn.Addr(), header.RequestId)
case LogWatchEventReadFailed:
err := v[0].(error)
Expand Down Expand Up @@ -940,7 +941,7 @@ func (conn *Connection) newFuture(req Request) (fut *Future) {
if ctx != nil {
select {
case <-ctx.Done():
fut.SetError(fmt.Errorf("context is done"))
fut.SetError(fmt.Errorf("context is done (request ID %d)", fut.requestId))
shard.rmut.Unlock()
return
default:
Expand Down Expand Up @@ -982,7 +983,7 @@ func (conn *Connection) contextWatchdog(fut *Future, ctx context.Context) {
case <-fut.done:
return
default:
conn.cancelFuture(fut, fmt.Errorf("context is done"))
conn.cancelFuture(fut, fmt.Errorf("context is done (request ID %d)", fut.requestId))
}
}

Expand All @@ -1008,7 +1009,7 @@ func (conn *Connection) send(req Request, streamId uint64) *Future {
if req.Ctx() != nil {
select {
case <-req.Ctx().Done():
conn.cancelFuture(fut, fmt.Errorf("context is done"))
conn.cancelFuture(fut, fmt.Errorf("context is done (request ID %d)", fut.requestId))
return fut
default:
}
Expand Down
5 changes: 3 additions & 2 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net"
"regexp"
"time"

"github.com/tarantool/go-iproto"
Expand Down Expand Up @@ -163,10 +164,10 @@ func ExamplePingRequest_Context() {
// Ping a Tarantool instance to check connection.
data, err := conn.Do(req).Get()
fmt.Println("Ping Resp data", data)
fmt.Println("Ping Error", err)
fmt.Println("Ping Error", regexp.MustCompile("[0-9]+").ReplaceAllString(err.Error(), "N"))
// Output:
// Ping Resp data []
// Ping Error context is done
// Ping Error context is done (request ID N)
}

func ExampleSelectRequest() {
Expand Down
9 changes: 6 additions & 3 deletions tarantool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os/exec"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strings"
"sync"
Expand Down Expand Up @@ -47,6 +48,8 @@ type Member struct {
Val uint
}

var contextDoneErrRegexp = regexp.MustCompile(`^context is done \(request ID [0-9]+\)$`)

Choose a reason for hiding this comment

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

I'm a little confused by the use of regular expressions to calculate the error type. regexp is slow compared to errors.Is. Why can't we use the errors.Is construct in this case?

Copy link
Collaborator

@oleg-jukovec oleg-jukovec Sep 21, 2024

Choose a reason for hiding this comment

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

We could extend the ClientError & errors.As to catch it:

go-tarantool/errors.go

Lines 25 to 30 in 0ccdee2

// ClientError is connection error produced by this client,
// i.e. connection failures or timeouts.
type ClientError struct {
Code uint32
Msg string
}

go-tarantool/errors.go

Lines 55 to 64 in 0ccdee2

// Tarantool client error codes.
const (
ErrConnectionNotReady = 0x4000 + iota
ErrConnectionClosed = 0x4000 + iota
ErrProtocolError = 0x4000 + iota
ErrTimeouted = 0x4000 + iota
ErrRateLimited = 0x4000 + iota
ErrConnectionShutdown = 0x4000 + iota
ErrIoError = 0x4000 + iota
)

Copy link
Author

@nurzhan-saktaganov nurzhan-saktaganov Sep 21, 2024

Choose a reason for hiding this comment

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

Since it's tests, there is no performance issue is questioned. So using regexp is ok here.
Returning here a ClientError or something else changes API.
I'm not intended to change the existing API, I'm intended just to make more informative the existing one (to avoid misleading and questions by users of library in the future).


func (m *Member) EncodeMsgpack(e *msgpack.Encoder) error {
if err := e.EncodeArrayLen(2); err != nil {
return err
Expand Down Expand Up @@ -2731,7 +2734,7 @@ func TestClientRequestObjectsWithPassedCanceledContext(t *testing.T) {
req := NewPingRequest().Context(ctx)
cancel()
resp, err := conn.Do(req).Get()
if err.Error() != "context is done" {
if !contextDoneErrRegexp.Match([]byte(err.Error())) {
t.Fatalf("Failed to catch an error from done context")
}
if resp != nil {
Expand Down Expand Up @@ -2802,7 +2805,7 @@ func TestClientRequestObjectsWithContext(t *testing.T) {
if err == nil {
t.Fatalf("caught nil error")
}
if err.Error() != "context is done" {
if !contextDoneErrRegexp.Match([]byte(err.Error())) {
t.Fatalf("wrong error caught: %v", err)
}
}
Expand Down Expand Up @@ -3295,7 +3298,7 @@ func TestClientIdRequestObjectWithPassedCanceledContext(t *testing.T) {
resp, err := conn.Do(req).Get()
require.Nilf(t, resp, "Response is empty")
require.NotNilf(t, err, "Error is not empty")
require.Equal(t, err.Error(), "context is done")
require.Regexp(t, contextDoneErrRegexp, err.Error())
}

func TestConnectionProtocolInfoUnsupported(t *testing.T) {
Expand Down
Loading