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 16, 2023
1 parent e51fa15 commit 2ee23a5
Show file tree
Hide file tree
Showing 4 changed files with 37 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 attempt to connect to all IPs simultaneously.
* `false` Client 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
15 changes: 10 additions & 5 deletions protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,16 @@ 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 {
// Try to connect to IPs sequentially until one is successful per MultiSubnetFailover false rules
for _, ipaddress := range ips {
d := c.getDialer(p)
addr := net.JoinHostPort(ipaddress.String(), strconv.Itoa(int(resolveServerPort(p.Port))))
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 2ee23a5

Please sign in to comment.