Skip to content

Commit

Permalink
fix: Added multisubnetfailover option that can be set to false to pre…
Browse files Browse the repository at this point in the history
…vent issue microsoft#158
  • Loading branch information
abairmj committed Oct 13, 2023
1 parent e51fa15 commit 8eaca2b
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 5 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ Other supported formats are listed below.
* `ApplicationIntent` - Can be given the value `ReadOnly` to initiate a read-only connection to an Availability Group listener. The `database` must be specified when connecting with `Application Intent` set to `ReadOnly`.
* `protocol` - forces use of a protocol. Make sure the corresponding package is imported.
* `columnencryption` or `column encryption setting` - a boolean value indicating whether Always Encrypted should be enabled on the connection.
* `multisubnetfailover`
* `true` (Default) Client library will attempt to connect to all IPs simultaneously. For non multi subnet configurations, servers with multiple IPs, may cause SQL Server to log error 17830 due to the abrupt closure of initial connections during the parallel attempts "fire and forget pattern". Consider false in those scenarios to avoid the misleading error "Network error code 0x2746 occurred while establishing a connection; the connection has been closed. This may have been caused by client or server login timeout expiration".
* `false` Client library attempts to connect to IPs in serial.

### Connection parameters for namedpipe package
* `pipe` - If set, no Browser query is made and named pipe used will be `\\<host>\pipe\<pipe>`
Expand Down
21 changes: 21 additions & 0 deletions msdsn/conn_str.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const (
Protocol = "protocol"
DialTimeout = "dial timeout"
Pipe = "pipe"
MultiSubnetFailover = "multisubnetfailover"
)

type Config struct {
Expand Down Expand Up @@ -128,6 +129,8 @@ type Config struct {
ChangePassword string
//ColumnEncryption is true if the application needs to decrypt or encrypt Always Encrypted values
ColumnEncryption bool
// Attempt to connect to all IPs in parallel when MultiSubnetFailover is true
MultiSubnetFailover bool
}

func readDERFile(filename string) ([]byte, error) {
Expand Down Expand Up @@ -483,6 +486,24 @@ func Parse(dsn string) (Config, error) {
}
p.ColumnEncryption = columnEncryption
}

msf, ok := params[MultiSubnetFailover]
if ok {
multiSubnetFailover, err := strconv.ParseBool(msf)
if err != nil {
if strings.EqualFold(msf, "Enabled") {
multiSubnetFailover = true
} else if strings.EqualFold(msf, "Disabled") {
multiSubnetFailover = false
} else {
return p, fmt.Errorf("invalid multiSubnetFailover value '%v': %v", multiSubnetFailover, err.Error())
}
}
p.MultiSubnetFailover = multiSubnetFailover
} else {
// Defaulting to true to prevent breaking change although other client libraries default to false
p.MultiSubnetFailover = true
}
return p, nil
}

Expand Down
3 changes: 3 additions & 0 deletions msdsn/conn_str_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestInvalidConnectionString(t *testing.T) {
"failoverport=invalid",
"applicationintent=ReadOnly",
"disableretry=invalid",
"multisubnetfailover=invalid",

// ODBC mode
"odbc:password={",
Expand Down Expand Up @@ -104,6 +105,8 @@ func TestValidConnectionString(t *testing.T) {
{"disableretry=1", func(p Config) bool { return p.DisableRetry }},
{"disableretry=0", func(p Config) bool { return !p.DisableRetry }},
{"", func(p Config) bool { return p.DisableRetry == disableRetryDefault }},
{"MultiSubnetFailover=true", func(p Config) bool { return p.MultiSubnetFailover }},
{"MultiSubnetFailover=false", func(p Config) bool { return !p.MultiSubnetFailover }},

// those are supported currently, but maybe should not be
{"someparam", func(p Config) bool { return true }},
Expand Down
18 changes: 13 additions & 5 deletions protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,19 @@ func (t tcpDialer) DialSqlConnection(ctx context.Context, c *Connector, p *msdsn
} else {
ips = []net.IP{ip}
}
if len(ips) == 1 {
d := c.getDialer(p)
addr := net.JoinHostPort(ips[0].String(), strconv.Itoa(int(resolveServerPort(p.Port))))
conn, err = d.DialContext(ctx, "tcp", addr)

if len(ips) == 1 || !p.MultiSubnetFailover {
// Attempt IPs sequentially when MultiSubnetFailover is false (or we only have one)
// to avoid triggering a 17830 error on SQL Server from a "fire and forget" pattern
// caused by connecting and immediately closing without doing any work
for _, ipaddress := range ips {
d := c.getDialer(p)
addr := net.JoinHostPort(ipaddress.String(), strconv.Itoa(int(resolveServerPort(p.Port))))
fmt.Println(ipaddress.String())
conn, err = d.DialContext(ctx, "tcp", addr)
if err == nil {
break
}
}
} else {
//Try Dials in parallel to avoid waiting for timeouts.
connChan := make(chan net.Conn, len(ips))
Expand Down

0 comments on commit 8eaca2b

Please sign in to comment.