Skip to content

Support SQL_ATTR_LOGIN_TIMEOUT via DSN keyword CONNECTTIMEOUT to allow connection timeout #277

@corentinrx

Description

@corentinrx

Is your feature request related to a problem? Please describe.

When a DB2 server is stopped or unreachable (e.g. firewall dropping packets, host down), sql.Open / sql.DB queries hang for 30-120 seconds waiting for the OS TCP timeout. Go's context.WithTimeout cannot help because:

  • conn.go's Open() calls SQLDriverConnect which is a blocking CGO/C call. Go context cancellation cannot interrupt it.
  • The context checks in PrepareContext(), ExecContext(), and QueryContext() (stmt.go) use a non-blocking select with default, so they only check if the context is already cancelled after the blocking ODBC call returns. they don't actually enforce the timeout during execution.
    This makes it impossible to implement a fast-failing health check when DB2 is down. The only way to control the connection timeout is via the ODBC SQL_ATTR_LOGIN_TIMEOUT attribute, which must be set on the connection handle before calling SQLDriverConnect.

Describe the solution you'd like

Support a CONNECTTIMEOUT= keyword in the DSN connection string. When present, the driver should:

  • Parse and extract CONNECTTIMEOUT from the DSN before passing it to SQLDriverConnect
  • Call SQLSetConnectAttr(handle, SQL_ATTR_LOGIN_TIMEOUT, seconds, SQL_IS_UINTEGER) on the allocated connection handle before SQLDriverConnect

This leverages the existing api.SQLSetConnectUIntPtrAttr function already available in the codebase (used for SQL_ATTR_AUTOCOMMIT in tx.go).

Suggested implementation

In conn.go:

const SQL_ATTR_LOGIN_TIMEOUT api.SQLINTEGER = 103
func (d *Driver) Open(dsn string) (driver.Conn, error) {
	var out api.SQLHANDLE
	ret := api.SQLAllocHandle(api.SQL_HANDLE_DBC, api.SQLHANDLE(d.h), &out)
	if IsError(ret) {
		return nil, NewError("SQLAllocHandle", d.h)
	}
	h := api.SQLHDBC(out)
	drv.Stats.updateHandleCount(api.SQL_HANDLE_DBC, 1)
	// Parse and apply CONNECTTIMEOUT before connecting
	cleanDsn, loginTimeout := extractConnectTimeout(dsn)
	if loginTimeout > 0 {
		ret = api.SQLSetConnectUIntPtrAttr(h, SQL_ATTR_LOGIN_TIMEOUT,
			uintptr(loginTimeout), api.SQL_IS_UINTEGER)
		if IsError(ret) {
			defer releaseHandle(h)
			return nil, NewError("SQLSetConnectUIntPtrAttr(SQL_ATTR_LOGIN_TIMEOUT)", h)
		}
	}
	b := api.StringToUTF16(cleanDsn)
	// ... rest of SQLDriverConnect unchanged ...
}
func extractConnectTimeout(dsn string) (string, int) {
	parts := strings.Split(dsn, ";")
	var cleaned []string
	timeout := 0
	for _, part := range parts {
		trimmed := strings.TrimSpace(part)
		if strings.HasPrefix(strings.ToUpper(trimmed), "CONNECTTIMEOUT") {
			kv := strings.SplitN(trimmed, "=", 2)
			if len(kv) == 2 {
				if v, err := strconv.Atoi(strings.TrimSpace(kv[1])); err == nil && v > 0 {
					timeout = v
				}
			}
		} else {
			cleaned = append(cleaned, part)
		}
	}
	return strings.Join(cleaned, ";"), timeout
}

Usage

dsn := "HOSTNAME=myhost; UID=user; PWD=pass; DATABASE=mydb; PORT=50000; CONNECTTIMEOUT=5"
db, err := sql.Open("go_ibm_db", dsn)

If DB2 is unreachable, Open / first query will fail after 5 seconds instead of hanging for the OS TCP timeout.

Additional context

  • SQL_ATTR_LOGIN_TIMEOUT (value 103) is a standard ODBC attribute supported by IBM CLI — see IBM documentation
  • The same SQLSetConnectUIntPtrAttr pattern is already used in tx.go for SQL_ATTR_AUTOCOMMIT
  • This is fully backward-compatible: DSNs without CONNECTTIMEOUT behave exactly as before

Tested with go_ibm_db v0.5.4 on Linux (clidriver)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions