Skip to content

Commit 1120bee

Browse files
authored
Support setting file descriptor limits for the proxy. (GoogleCloudPlatform#179)
Check for file descriptor limit on startup.
1 parent b9c6045 commit 1120bee

File tree

4 files changed

+254
-0
lines changed

4 files changed

+254
-0
lines changed

cmd/cloud_sql_proxy/cloud_sql_proxy.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/GoogleCloudPlatform/cloudsql-proxy/logging"
3939
"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/certs"
4040
"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/fuse"
41+
"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/limits"
4142
"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy"
4243

4344
"cloud.google.com/go/compute/metadata"
@@ -76,6 +77,7 @@ can be removed automatically by this program.`)
7677

7778
// Settings for limits
7879
maxConnections = flag.Uint64("max_connections", 0, `If provided, the maximum number of connections to establish before refusing new connections. Defaults to 0 (no limit)`)
80+
fdRlimit = flag.Uint64("fd_rlimit", limits.ExpectedFDs, `Sets the rlimit on the number of open file descriptors for the proxy to the provided value. If set to zero, disables attempts to set the rlimit. Defaults to a value which can support 4K connections to one instance`)
7981

8082
// Settings for authentication.
8183
token = flag.String("token", "", "When set, the proxy uses this Bearer token for authorization.")
@@ -410,6 +412,12 @@ func main() {
410412
log.SetOutput(ioutil.Discard)
411413
}
412414

415+
if *fdRlimit != 0 {
416+
if err := limits.SetupFDLimits(*fdRlimit); err != nil {
417+
logging.Infof("failed to setup file descriptor limits: %v", err)
418+
}
419+
}
420+
413421
// TODO: needs a better place for consolidation
414422
// if instances is blank and env var INSTANCES is supplied use it
415423
if envInstances := os.Getenv("INSTANCES"); *instances == "" && envInstances != "" {

proxy/limits/limits.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2015 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// +build !windows
16+
17+
// Package limits provides routines to check and enforce certain resource
18+
// limits on the Cloud SQL client proxy process.
19+
package limits
20+
21+
import (
22+
"fmt"
23+
"syscall"
24+
25+
"github.com/GoogleCloudPlatform/cloudsql-proxy/logging"
26+
)
27+
28+
var (
29+
// For overriding in unittests.
30+
syscallGetrlimit = syscall.Getrlimit
31+
syscallSetrlimit = syscall.Setrlimit
32+
)
33+
34+
// Each connection handled by the proxy requires two file descriptors, one
35+
// for the local end of the connection and one for the remote. So, the proxy
36+
// process should be able to open at least 8K file descriptors if it is to
37+
// handle 4K connections to one instance.
38+
const ExpectedFDs = 8500
39+
40+
// SetupFDLimits ensures that the process running the Cloud SQL proxy can have
41+
// at least wantFDs number of open file descriptors. It returns an error if it
42+
// cannot ensure the same.
43+
func SetupFDLimits(wantFDs uint64) error {
44+
rlim := &syscall.Rlimit{}
45+
if err := syscallGetrlimit(syscall.RLIMIT_NOFILE, rlim); err != nil {
46+
return fmt.Errorf("failed to read rlimit for max file descriptors: %v", err)
47+
}
48+
49+
if rlim.Cur >= wantFDs {
50+
logging.Verbosef("current FDs rlimit set to %d, wanted limit is %d. Nothing to do here.", rlim.Cur, wantFDs)
51+
return nil
52+
}
53+
54+
// Linux man page:
55+
// The soft limit is the value that the kernel enforces for the corre‐
56+
// sponding resource. The hard limit acts as a ceiling for the soft limit:
57+
// an unprivileged process may set only its soft limit to a value in the
58+
// range from 0 up to the hard limit, and (irreversibly) lower its hard
59+
// limit. A privileged process (under Linux: one with the CAP_SYS_RESOURCE
60+
// capability in the initial user namespace) may make arbitrary changes to
61+
// either limit value.
62+
if rlim.Max < wantFDs {
63+
// When the hard limit is less than what is requested, let's just give it a
64+
// shot, and if we fail, we fallback and try just setting the softlimit.
65+
rlim2 := &syscall.Rlimit{}
66+
rlim2.Max = wantFDs
67+
rlim2.Cur = wantFDs
68+
if err := syscallSetrlimit(syscall.RLIMIT_NOFILE, rlim2); err == nil {
69+
logging.Verbosef("Rlimits for file descriptors set to {%v}", rlim2)
70+
return nil
71+
}
72+
}
73+
74+
rlim.Cur = wantFDs
75+
if err := syscallSetrlimit(syscall.RLIMIT_NOFILE, rlim); err != nil {
76+
return fmt.Errorf("failed to set rlimit {%v} for max file descriptors: %v", rlim, err)
77+
}
78+
79+
logging.Verbosef("Rlimits for file descriptors set to {%v}", rlim)
80+
return nil
81+
}

proxy/limits/limits_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright 2015 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// +build !windows
16+
17+
package limits
18+
19+
import (
20+
"errors"
21+
"math"
22+
"syscall"
23+
"testing"
24+
)
25+
26+
type rlimitFunc func(int, *syscall.Rlimit) error
27+
28+
func TestSetupFDLimits(t *testing.T) {
29+
tests := []struct {
30+
desc string
31+
getFunc rlimitFunc
32+
setFunc rlimitFunc
33+
wantFDs uint64
34+
wantErr bool
35+
}{
36+
{
37+
desc: "Getrlimit fails",
38+
getFunc: func(_ int, _ *syscall.Rlimit) error {
39+
return errors.New("failed to read rlimit for max file descriptors")
40+
},
41+
setFunc: func(_ int, _ *syscall.Rlimit) error {
42+
panic("shouldn't be called")
43+
},
44+
wantFDs: 0,
45+
wantErr: true,
46+
},
47+
{
48+
desc: "Getrlimit max is less than wantFDs",
49+
getFunc: func(_ int, rlim *syscall.Rlimit) error {
50+
rlim.Cur = 512
51+
rlim.Max = 512
52+
return nil
53+
},
54+
setFunc: func(_ int, rlim *syscall.Rlimit) error {
55+
if rlim.Cur != 1024 || rlim.Max != 1024 {
56+
return errors.New("setrlimit called with unexpected value")
57+
}
58+
return nil
59+
},
60+
wantFDs: 1024,
61+
wantErr: false,
62+
},
63+
{
64+
desc: "Getrlimit returns rlim_infinity",
65+
getFunc: func(_ int, rlim *syscall.Rlimit) error {
66+
rlim.Cur = math.MaxUint64
67+
rlim.Max = math.MaxUint64
68+
return nil
69+
},
70+
setFunc: func(_ int, _ *syscall.Rlimit) error {
71+
panic("shouldn't be called")
72+
},
73+
wantFDs: 1024,
74+
wantErr: false,
75+
},
76+
{
77+
desc: "Getrlimit cur is greater than wantFDs",
78+
getFunc: func(_ int, rlim *syscall.Rlimit) error {
79+
rlim.Cur = 512
80+
rlim.Max = 512
81+
return nil
82+
},
83+
setFunc: func(_ int, _ *syscall.Rlimit) error {
84+
panic("shouldn't be called")
85+
},
86+
wantFDs: 256,
87+
wantErr: false,
88+
},
89+
{
90+
desc: "Setrlimit fails",
91+
getFunc: func(_ int, rlim *syscall.Rlimit) error {
92+
rlim.Cur = 128
93+
rlim.Max = 512
94+
return nil
95+
},
96+
setFunc: func(_ int, _ *syscall.Rlimit) error {
97+
return errors.New("failed to set rlimit for max file descriptors")
98+
},
99+
wantFDs: 256,
100+
wantErr: true,
101+
},
102+
{
103+
desc: "Success",
104+
getFunc: func(_ int, rlim *syscall.Rlimit) error {
105+
rlim.Cur = 128
106+
rlim.Max = 512
107+
return nil
108+
},
109+
setFunc: func(_ int, _ *syscall.Rlimit) error {
110+
return nil
111+
},
112+
wantFDs: 256,
113+
wantErr: false,
114+
},
115+
}
116+
117+
for _, test := range tests {
118+
oldGetFunc := syscallGetrlimit
119+
syscallGetrlimit = test.getFunc
120+
defer func() {
121+
syscallGetrlimit = oldGetFunc
122+
}()
123+
124+
oldSetFunc := syscallSetrlimit
125+
syscallSetrlimit = test.setFunc
126+
defer func() {
127+
syscallSetrlimit = oldSetFunc
128+
}()
129+
130+
gotErr := SetupFDLimits(test.wantFDs)
131+
if (gotErr != nil) != test.wantErr {
132+
t.Errorf("%s: limits.SetupFDLimits(%d) returned error %v, wantErr %v", test.desc, test.wantFDs, gotErr, test.wantErr)
133+
}
134+
}
135+
}

proxy/limits/limits_windows.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2015 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package limits is a package stub for windows, and we currently don't support
16+
// setting limits in windows.
17+
package limits
18+
19+
import "errors"
20+
21+
// We don't support limit on the number of file handles in windows.
22+
const ExpectedFDs = 0
23+
24+
func SetupFDLimits(wantFDs uint64) error {
25+
if wantFDs != 0 {
26+
return errors.New("setting limits on the number of file handles is not supported")
27+
}
28+
29+
return nil
30+
}

0 commit comments

Comments
 (0)