diff --git a/CHANGELOG.md b/CHANGELOG.md index 00049a9c..c7befdb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. ### Fixed +- Fixed panic when calling NewWatcher() during reconnection or after + connection is closed (#438). + ## [v2.3.0] - 2025-03-11 The release extends box.info responses and ConnectionPool.GetInfo return data. diff --git a/connection.go b/connection.go index 97f9dbbc..3cda4f4f 100644 --- a/connection.go +++ b/connection.go @@ -1454,6 +1454,9 @@ func isFeatureInSlice(expected iproto.Feature, actualSlice []iproto.Feature) boo // // Since 1.10.0 func (conn *Connection) NewWatcher(key string, callback WatchCallback) (Watcher, error) { + if conn.c == nil { + return nil, ClientError{ErrConnectionNotReady, "client connection is not ready"} + } // We need to check the feature because the IPROTO_WATCH request is // asynchronous. We do not expect any response from a Tarantool instance // That's why we can't just check the Tarantool response for an unsupported diff --git a/tarantool_test.go b/tarantool_test.go index 3f1e90ef..1bde0871 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -3550,6 +3550,53 @@ func TestConnection_NewWatcher(t *testing.T) { } } +func TestNewWatcherDuringReconnectOrAfterClose(t *testing.T) { + test_helpers.SkipIfWatchersUnsupported(t) + + const server = "127.0.0.1:3015" + testDialer := dialer + testDialer.Address = server + + inst, err := test_helpers.StartTarantool(test_helpers.StartOpts{ + Dialer: testDialer, + InitScript: "config.lua", + Listen: server, + WaitStart: 100 * time.Millisecond, + ConnectRetry: 10, + RetryTimeout: 500 * time.Millisecond, + }) + defer test_helpers.StopTarantoolWithCleanup(inst) + if err != nil { + t.Fatalf("Unable to start Tarantool: %s", err) + } + + ctx, cancel := test_helpers.GetConnectContext() + defer cancel() + + reconnectOpts := opts + reconnectOpts.Reconnect = 100 * time.Millisecond + reconnectOpts.MaxReconnects = 0 + reconnectOpts.Notify = make(chan ConnEvent) + conn, err := Connect(ctx, testDialer, reconnectOpts) + if err != nil { + t.Fatalf("Connection was not established: %v", err) + } + defer conn.Close() + test_helpers.StopTarantool(inst) + + for conn.ConnectedNow() { + time.Sleep(100 * time.Millisecond) + } + _, err = conn.NewWatcher("one", func(event WatchEvent) {}) + assert.NotNil(t, err) + assert.ErrorContains(t, err, "client connection is not ready") + + conn.Close() + _, err = conn.NewWatcher("two", func(event WatchEvent) {}) + assert.NotNil(t, err) + assert.ErrorContains(t, err, "client connection is not ready") +} + func TestConnection_NewWatcher_noWatchersFeature(t *testing.T) { test_helpers.SkipIfWatchersSupported(t)