Skip to content

Commit c76238c

Browse files
committed
feat: support connect_timeout property
Add support for a connect_timeout property that sets the maximum time that the driver will wait when creating a new connection to Spanner. This property only affects the very first creation of a connection to Spanner for a given connector. Once a connection has been established, all further connection creations will not use this value. Fixes #576
1 parent ad05fde commit c76238c

File tree

4 files changed

+62
-1
lines changed

4 files changed

+62
-1
lines changed

connection_properties.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,15 @@ var propertyDisableStatementCache = createConnectionProperty(
461461
connectionstate.ContextStartup,
462462
connectionstate.ConvertBool,
463463
)
464+
var propertyConnectTimeout = createConnectionProperty(
465+
"connect_timeout",
466+
"The amount of time to wait before timing out when creating a new connection.",
467+
0,
468+
false,
469+
nil,
470+
connectionstate.ContextStartup,
471+
connectionstate.ConvertDuration,
472+
)
464473

465474
// Generated read-only properties. These cannot be set by the user anywhere.
466475
var propertyCommitTimestamp = createReadOnlyConnectionProperty(

connectionstate/converters.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func parseTimestamp(re *regexp.Regexp, params string) (time.Time, error) {
113113
func parseDuration(re *regexp.Regexp, value string) (time.Duration, error) {
114114
matches := matchesToMap(re, value)
115115
if matches["duration"] == "" && matches["number"] == "" && matches["null"] == "" {
116-
return 0, spanner.ToSpannerError(status.Error(codes.InvalidArgument, fmt.Sprintf("No duration found: %v", value)))
116+
return 0, spanner.ToSpannerError(status.Error(codes.InvalidArgument, fmt.Sprintf("No or invalid duration found: %v", value)))
117117
}
118118
if matches["duration"] != "" {
119119
d, err := time.ParseDuration(matches["duration"])

driver.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,17 @@ func openDriverConn(ctx context.Context, c *connector) (driver.Conn, error) {
732732
c.connectorConfig.Project,
733733
c.connectorConfig.Instance,
734734
c.connectorConfig.Database)
735+
if value, ok := c.initialPropertyValues[propertyConnectTimeout.Key()]; ok {
736+
if timeout, err := value.GetValue(); err == nil {
737+
if duration, ok := timeout.(time.Duration); ok {
738+
var cancel context.CancelFunc
739+
// This will set the actual timeout of the context to the lower of the
740+
// current context timeout (if any) and the value from the connection property.
741+
ctx, cancel = context.WithTimeout(ctx, duration)
742+
defer cancel()
743+
}
744+
}
745+
}
735746

736747
if err := c.increaseConnCount(ctx, databaseName, opts); err != nil {
737748
return nil, err

driver_with_mockserver_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5555,6 +5555,47 @@ func TestReturnResultSetMetadataAndStats(t *testing.T) {
55555555
}
55565556
}
55575557

5558+
func TestConnectTimeout(t *testing.T) {
5559+
t.Parallel()
5560+
5561+
server, _, serverTeardown := setupMockedTestServerWithDialect(t, databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL)
5562+
defer serverTeardown()
5563+
db, err := sql.Open(
5564+
"spanner",
5565+
fmt.Sprintf("%s/projects/p/instances/i/databases/d?useplaintext=true;connect_timeout=1ms", server.Address))
5566+
if err != nil {
5567+
t.Fatal(err)
5568+
}
5569+
defer silentClose(db)
5570+
5571+
// Make the ExecuteStreamingSql method a bit slow, so the query that is used to detect the dialect responds a bit slowly.
5572+
server.TestSpanner.PutExecutionTime(testutil.MethodExecuteStreamingSql, testutil.SimulatedExecutionTime{MinimumExecutionTime: time.Millisecond * 10})
5573+
5574+
// Try to get/create a connection using a context without a deadline.
5575+
// This will cause the connect_timeout to be used.
5576+
c, err := db.Conn(context.Background())
5577+
if g, w := spanner.ErrCode(err), codes.DeadlineExceeded; g != w {
5578+
t.Fatalf("error code mismatch\n Got: %v\nWant: %v", g, w)
5579+
} else if c != nil {
5580+
_ = c.Close()
5581+
}
5582+
}
5583+
5584+
func TestInvalidConnectTimeout(t *testing.T) {
5585+
t.Parallel()
5586+
5587+
server, _, serverTeardown := setupMockedTestServerWithDialect(t, databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL)
5588+
defer serverTeardown()
5589+
db, err := sql.Open(
5590+
"spanner",
5591+
fmt.Sprintf("%s/projects/p/instances/i/databases/d?useplaintext=true;connect_timeout='very long'", server.Address))
5592+
if g, w := spanner.ErrCode(err), codes.InvalidArgument; g != w {
5593+
t.Fatalf("error code mismatch\n Got: %v\nWant: %v", g, w)
5594+
} else if db != nil {
5595+
defer silentClose(db)
5596+
}
5597+
}
5598+
55585599
func numeric(v string) big.Rat {
55595600
res, _ := big.NewRat(1, 1).SetString(v)
55605601
return *res

0 commit comments

Comments
 (0)