Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Added multisubnetfailover option, set to false to prevent issue #158 #159

Merged
merged 6 commits into from
Oct 19, 2023
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))))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveServerPort(p.Port)

call Itoa(resolveServerPort) just once

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! Will mod! Thanks

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 {
abairmj marked this conversation as resolved.
Show resolved Hide resolved
break
}
}
} else {
//Try Dials in parallel to avoid waiting for timeouts.
connChan := make(chan net.Conn, len(ips))
Expand Down