-
Notifications
You must be signed in to change notification settings - Fork 36
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
Fixed recvfd leaks #1173
Fixed recvfd leaks #1173
Changes from all commits
c7b4986
5d58f01
1895c74
9f4f9ab
6f613aa
a21604d
51703c4
0c19391
55d55c8
a178bfe
007843e
b2faeee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,7 +86,7 @@ func recvFDAndSwapInodeToFile(ctx context.Context, fileMap *perConnectionFileMap | |
return err | ||
} | ||
|
||
func swapFileToInode(fileMap *perConnectionFileMap, parameters map[string]string, closeAllFiles bool) error { | ||
func swapFileToInode(fileMap *perConnectionFileMap, parameters map[string]string) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you tested this in integration? I ask... because I would expect this to cause issues. The purpose of the recvfd was to receive the file and hold it open for the life of the connection. This is because other things like for example memifproxy may need to be using it during the life of the connection. Now please note, the and hold it open part may in fact not be necessary... but you will not be able to tell that from the unit testing in SDK. Testing in integration or with cmd-forwarder-vpp would give you a sense of whether you can get away with closing these files after the processing of a Request message or whether you need to hold them open for the life of the connection. I'm hoping your are correct here... because I'd like to see this change work as submitted :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @edwarnicke , thanks a lot for your comments! As far as I understand, if there's a timeout, all files are also closed as I'm definitely not going to commit this unless it's tested in integration (locally as well as on CI - will discuss the details with Denis). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sol-0 Yeah... and please note: your change may be right. I hope its right. I was just wracking my brains as to why the closeAll had been there to begin with, and thought of possible (not certain) issues. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @edwarnicke You're correct. Currently, @sol-0 is verifying this change on integration tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @denis-tingaikin , @edwarnicke integration testing has been completed: https://github.com/networkservicemesh/integration-k8s-kind/actions/runs/1517061295 Local integration testing has also completed successfully. |
||
// Get the inodeURL from parameters | ||
fileURLStr, ok := parameters[common.InodeURL] | ||
if !ok { | ||
|
@@ -112,11 +112,10 @@ func swapFileToInode(fileMap *perConnectionFileMap, parameters map[string]string | |
// Swap the fileURL for the inodeURL in parameters | ||
parameters[common.InodeURL] = inodeURL.String() | ||
|
||
// If closeAllFiles == true, close any files we may have open for any other inodes | ||
// This is used to clean up files sent by MechanismPreferences that were *not* selected to be the | ||
// connection mechanism | ||
for inodeURLStr, file := range fileMap.filesByInodeURL { | ||
if closeAllFiles || inodeURLStr != inodeURL.String() { | ||
if inodeURLStr != inodeURL.String() { | ||
delete(fileMap.filesByInodeURL, inodeURLStr) | ||
_ = file.Close() | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
// Copyright (c) 2021 Doc.ai and/or its affiliates. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at: | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// +build linux | ||
|
||
package recvfd_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/url" | ||
"os" | ||
"path" | ||
"runtime" | ||
"testing" | ||
"time" | ||
|
||
"github.com/edwarnicke/grpcfd" | ||
"github.com/networkservicemesh/api/pkg/api/networkservice" | ||
"github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/cls" | ||
"github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/common" | ||
"github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/kernel" | ||
"github.com/stretchr/testify/suite" | ||
"go.uber.org/goleak" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/credentials/insecure" | ||
|
||
"github.com/networkservicemesh/sdk/pkg/networkservice/chains/client" | ||
"github.com/networkservicemesh/sdk/pkg/networkservice/common/mechanisms/recvfd" | ||
"github.com/networkservicemesh/sdk/pkg/networkservice/common/mechanisms/sendfd" | ||
"github.com/networkservicemesh/sdk/pkg/networkservice/core/chain" | ||
"github.com/networkservicemesh/sdk/pkg/networkservice/utils/checks/checkcontext" | ||
"github.com/networkservicemesh/sdk/pkg/tools/grpcfdutils" | ||
"github.com/networkservicemesh/sdk/pkg/tools/grpcutils" | ||
"github.com/networkservicemesh/sdk/pkg/tools/sandbox" | ||
) | ||
|
||
type checkRecvfdTestSuite struct { | ||
suite.Suite | ||
|
||
tempDir string | ||
onFileClosedContexts []context.Context | ||
onFileClosedCallbacks map[string]func() | ||
|
||
testClient networkservice.NetworkServiceClient | ||
} | ||
|
||
func (s *checkRecvfdTestSuite) SetupTest() { | ||
t := s.T() | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
|
||
t.Cleanup(func() { | ||
cancel() | ||
goleak.VerifyNone(t) | ||
}) | ||
|
||
s.tempDir = t.TempDir() | ||
|
||
sock, err := os.Create(path.Join(s.tempDir, "test.sock")) | ||
s.Require().NoError(err) | ||
|
||
serveURL := &url.URL{Scheme: "unix", Path: sock.Name()} | ||
|
||
testChain := chain.NewNetworkServiceServer( | ||
checkcontext.NewServer(t, func(t *testing.T, c context.Context) { | ||
injectErr := grpcfdutils.InjectOnFileReceivedCallback(c, func(fileName string, file *os.File) { | ||
runtime.SetFinalizer(file, func(file *os.File) { | ||
onFileClosedCallback, ok := s.onFileClosedCallbacks[fileName] | ||
if ok { | ||
onFileClosedCallback() | ||
} | ||
}) | ||
}) | ||
|
||
s.Require().NoError(injectErr) | ||
}), | ||
recvfd.NewServer()) | ||
|
||
startServer(ctx, s, &testChain, serveURL) | ||
s.testClient = createClient(ctx, serveURL) | ||
} | ||
|
||
func TestRecvfd(t *testing.T) { | ||
suite.Run(t, new(checkRecvfdTestSuite)) | ||
} | ||
|
||
func startServer(ctx context.Context, s *checkRecvfdTestSuite, testServerChain *networkservice.NetworkServiceServer, serveURL *url.URL) { | ||
grpcServer := grpc.NewServer(grpc.Creds(grpcfd.TransportCredentials(insecure.NewCredentials()))) | ||
networkservice.RegisterNetworkServiceServer(grpcServer, *testServerChain) | ||
|
||
errCh := grpcutils.ListenAndServe(ctx, serveURL, grpcServer) | ||
|
||
s.Require().Len(errCh, 0) | ||
} | ||
|
||
func createClient(ctx context.Context, u *url.URL) networkservice.NetworkServiceClient { | ||
return client.NewClient( | ||
ctx, | ||
client.WithClientURL(sandbox.CloneURL(u)), | ||
client.WithDialOptions(grpc.WithTransportCredentials( | ||
grpcfd.TransportCredentials(insecure.NewCredentials())), | ||
), | ||
client.WithDialTimeout(time.Second), | ||
client.WithoutRefresh(), | ||
client.WithAdditionalFunctionality(sendfd.NewClient())) | ||
} | ||
|
||
func createFile(s *checkRecvfdTestSuite, fileName string) (inodeURLStr string, fileClosedContext context.Context, cancelFunc func()) { | ||
f, err := os.Create(fileName) | ||
s.Require().NoErrorf(err, "Failed to create and open a file: %v", err) | ||
|
||
err = f.Close() | ||
s.Require().NoErrorf(err, "Failed to close file: %v", err) | ||
|
||
fileClosedContext, cancelFunc = context.WithCancel(context.Background()) | ||
|
||
inodeURL, err := grpcfd.FilenameToURL(fileName) | ||
s.Require().NoError(err) | ||
|
||
return inodeURL.String(), fileClosedContext, cancelFunc | ||
} | ||
|
||
func (s *checkRecvfdTestSuite) TestRecvfdClosesSingleFile() { | ||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) | ||
defer cancel() | ||
|
||
testFileName := path.Join(s.tempDir, "TestRecvfdClosesSingleFile.test") | ||
|
||
inodeURLStr, fileClosedContext, cancelFunc := createFile(s, testFileName) | ||
|
||
s.onFileClosedCallbacks = map[string]func(){ | ||
inodeURLStr: cancelFunc, | ||
} | ||
|
||
request := &networkservice.NetworkServiceRequest{ | ||
MechanismPreferences: []*networkservice.Mechanism{ | ||
{ | ||
Cls: cls.LOCAL, | ||
Type: kernel.MECHANISM, | ||
Parameters: map[string]string{ | ||
common.InodeURL: "file:" + testFileName, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
conn, err := s.testClient.Request(ctx, request) | ||
s.Require().NoError(err) | ||
|
||
_, err = s.testClient.Close(ctx, conn) | ||
s.Require().NoError(err) | ||
|
||
s.Require().Eventually(func() bool { | ||
runtime.GC() | ||
return fileClosedContext.Err() != nil | ||
}, time.Second, time.Millisecond*100) | ||
} | ||
|
||
func (s *checkRecvfdTestSuite) TestRecvfdClosesMultipleFiles() { | ||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) | ||
defer cancel() | ||
|
||
const numFiles = 3 | ||
s.onFileClosedContexts = make([]context.Context, numFiles) | ||
s.onFileClosedCallbacks = make(map[string]func(), numFiles) | ||
|
||
request := &networkservice.NetworkServiceRequest{ | ||
MechanismPreferences: make([]*networkservice.Mechanism, numFiles), | ||
} | ||
|
||
var filePath string | ||
for i := 0; i < numFiles; i++ { | ||
filePath = path.Join(s.tempDir, fmt.Sprintf("TestRecvfdClosesMultipleFiles.test%d", i)) | ||
|
||
inodeURLStr, fileClosedContext, cancelFunc := createFile(s, filePath) | ||
s.onFileClosedCallbacks[inodeURLStr] = cancelFunc | ||
s.onFileClosedContexts[i] = fileClosedContext | ||
|
||
request.MechanismPreferences = append(request.MechanismPreferences, | ||
&networkservice.Mechanism{ | ||
Cls: cls.LOCAL, | ||
Type: kernel.MECHANISM, | ||
Parameters: map[string]string{ | ||
common.InodeURL: "file:" + filePath, | ||
}, | ||
}) | ||
} | ||
|
||
conn, err := s.testClient.Request(ctx, request) | ||
s.Require().NoError(err) | ||
|
||
_, err = s.testClient.Close(ctx, conn) | ||
s.Require().NoError(err) | ||
|
||
for i := range s.onFileClosedContexts { | ||
onClosedFileCtx := s.onFileClosedContexts[i] | ||
s.Require().Eventually(func() bool { | ||
runtime.GC() | ||
return onClosedFileCtx.Err() != nil | ||
}, time.Second, time.Millisecond*100) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the switch to only linux? This precludes testing on the Mac OS... what was the driver?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, Ed!
This was a code change suggested during review and I somehow missed that it would remove tests for MacOS.
I'm going to revert this back to
!windows
.@denis-tingaikin , please, share your thoughts in case you have any objections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@edwarnicke This tag was incorrect. All tests actually check OS name on
linux
. So I suggested just changing the build tag instead of using skip on non-linux os names in tests...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@edwarnicke @denis-tingaikin , I'll check the tests with
!windows
on MacOS (with removed OS name check)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sol-0 Please do not use
!windows
with removed OS name check. Note: these tests are testing grpcfd library. The library required linux...FYI: Tests will work, but for MacOS they will not make sense because we are using null server/clients instead of server/clients based on grpcfd for non-linux systems...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@denis-tingaikin , ok, thanks, Denis! I'll revert the change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See at an example of implementation grpcfd based chain element for non-linux systems
sdk/pkg/registry/common/recvfd/client_nolinux.go
Line 31 in c57ed95