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

Fixed recvfd leaks #1173

Merged
merged 12 commits into from
Dec 7, 2021
54 changes: 52 additions & 2 deletions pkg/networkservice/chains/nsmgr/unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//+build !windows
// +build !windows
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// +build !windows
// +build linux


package nsmgr_test

Expand All @@ -38,6 +38,10 @@ import (
"github.com/networkservicemesh/sdk/pkg/tools/sandbox"
)

const (
linuxOsName = "linux"
)

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const (
linuxOsName = "linux"
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change was required by golangci-lint

Copy link
Member

Choose a reason for hiding this comment

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

Just don't check on OS name. See at first comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right. Thanks, Denis!

func Test_Local_NoURLUsecase(t *testing.T) {
t.Cleanup(func() { goleak.VerifyNone(t) })

Expand Down Expand Up @@ -84,7 +88,7 @@ func Test_Local_NoURLUsecase(t *testing.T) {
}

func Test_MultiForwarderSendfd(t *testing.T) {
if runtime.GOOS != "linux" {
if runtime.GOOS != linuxOsName {
t.Skip("sendfd works only on linux")
}
t.Cleanup(func() { goleak.VerifyNone(t) })
Expand Down Expand Up @@ -160,3 +164,49 @@ func Test_MultiForwarderSendfd(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 1, counter.Closes())
}

func Test_TimeoutRecvfd(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

Could you explain how this test checks the problem?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Client makes a request with an expired token thus triggering timeout (timeoutserver).

Copy link
Member

@denis-tingaikin denis-tingaikin Nov 24, 2021

Choose a reason for hiding this comment

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

The test is incorrect.

  1. Refresh was not disabled. So token can no be expired.
  2. We need to verify that all files are removed.

Please add test for recvfd package.

Also, I think we do not need to use sandbox here. Because we simply need to check cleaning and sandbox is overkilled thing for it.

if runtime.GOOS != linuxOsName {
t.Skip("recvfd works only on linux")
}
t.Cleanup(func() { goleak.VerifyNone(t) })

ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

domain := sandbox.NewBuilder(ctx, t).
UseUnixSockets().
SetNodeSetup(func(ctx context.Context, node *sandbox.Node, _ int) {
node.NewNSMgr(ctx, "nsmgr", nil, sandbox.GenerateTestToken, nsmgr.NewServer)
node.NewForwarder(ctx, &registry.NetworkServiceEndpoint{
Name: "forwarder-1",
NetworkServiceNames: []string{"forwarder"},
NetworkServiceLabels: map[string]*registry.NetworkServiceLabels{
"forwarder": {
Labels: map[string]string{
"p2p": "true",
},
},
},
}, sandbox.GenerateTestToken, recvfd.NewServer())
}).
Build()

nsRegistryClient := domain.NewNSRegistryClient(ctx, sandbox.GenerateTestToken)

nsReg, err := nsRegistryClient.Register(ctx, defaultRegistryService())
require.NoError(t, err)

nseReg := defaultRegistryEndpoint(nsReg.Name)

domain.Nodes[0].NewEndpoint(ctx, nseReg, sandbox.GenerateTestToken)

nsc := domain.Nodes[0].NewClient(ctx, sandbox.GenerateExpiringToken(0), kernel.NewClient(), sendfd.NewClient())

request := defaultRequest(nsReg.Name)

conn, err := nsc.Request(ctx, request.Clone())
require.Nil(t, err)
require.NotNil(t, conn)
require.Equal(t, 4, len(conn.Path.PathSegments))
}
19 changes: 17 additions & 2 deletions pkg/networkservice/common/mechanisms/recvfd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,21 @@ func (r *recvFDServer) Request(ctx context.Context, request *networkservice.Netw
}

func (r *recvFDServer) Close(ctx context.Context, conn *networkservice.Connection) (*empty.Empty, error) {
// Clean up the fileMap no matter what happens
defer r.fileMaps.Delete(conn.GetId())
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
defer r.fileMaps.Delete(conn.GetId())
defer r.closeFiles(conn)


// Get the grpcfd.FDRecver
recv, ok := grpcfd.FromContext(ctx)
if !ok {
r.closeFiles(conn)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
r.closeFiles(conn)

return next.Server(ctx).Close(ctx, conn)
}

// Get the fileMap
fileMap, _ := r.fileMaps.LoadOrStore(conn.GetId(), &perConnectionFileMap{
filesByInodeURL: make(map[string]*os.File),
inodeURLbyFilename: make(map[string]*url.URL),
})
// Clean up the fileMap no matter what happens
defer r.fileMaps.Delete(conn.GetId())

// Recv the FD and Swap the Inode for a file in InodeURL in Parameters
err := recvFDAndSwapInodeToFile(ctx, fileMap, conn.GetMechanism().GetParameters(), recv)
Expand All @@ -107,3 +110,15 @@ func (r *recvFDServer) Close(ctx context.Context, conn *networkservice.Connectio
err = swapFileToInode(fileMap, conn.GetMechanism().GetParameters(), true)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
err = swapFileToInode(fileMap, conn.GetMechanism().GetParameters(), true)
err = swapFileToInode(fileMap, conn.GetMechanism().GetParameters())

return &empty.Empty{}, err
}

func (r *recvFDServer) closeFiles(conn *networkservice.Connection) {
denis-tingaikin marked this conversation as resolved.
Show resolved Hide resolved
fileMap, _ := r.fileMaps.LoadOrStore(conn.GetId(), &perConnectionFileMap{
filesByInodeURL: make(map[string]*os.File),
inodeURLbyFilename: make(map[string]*url.URL),
})

for inodeURLStr, file := range fileMap.filesByInodeURL {
delete(fileMap.filesByInodeURL, inodeURLStr)
_ = file.Close()
}
}
19 changes: 17 additions & 2 deletions pkg/registry/common/recvfd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,21 @@ func (r *recvfdNseServer) Unregister(ctx context.Context, endpoint *registry.Net
if endpoint.GetName() == "" {
return nil, errors.New("invalid endpoint specified")
}

// Clean up the fileMap no matter what happens
defer r.fileMaps.Delete(endpoint.GetName())

// Get the grpcfd.FDRecver
recv, ok := grpcfd.FromContext(ctx)
if !ok {
r.closeFiles(endpoint)
return next.NetworkServiceEndpointRegistryServer(ctx).Unregister(ctx, endpoint)
}
// Get the fileMap
fileMap, _ := r.fileMaps.LoadOrStore(endpoint.GetName(), &perEndpointFileMap{
filesByInodeURL: make(map[string]*os.File),
inodeURLbyFilename: make(map[string]*url.URL),
})
// Clean up the fileMap no matter what happens
defer r.fileMaps.Delete(endpoint.GetName())

// Recv the FD and Swap the Inode for a file in InodeURL in Parameters
endpoint = endpoint.Clone()
Expand Down Expand Up @@ -210,3 +213,15 @@ func swapFileToInode(fileMap *perEndpointFileMap, endpoint *registry.NetworkServ
})
return nil
}

func (r *recvfdNseServer) closeFiles(endpoint *registry.NetworkServiceEndpoint) {
fileMap, _ := r.fileMaps.LoadOrStore(endpoint.GetName(), &perEndpointFileMap{
filesByInodeURL: make(map[string]*os.File),
inodeURLbyFilename: make(map[string]*url.URL),
})

for inodeURLStr, file := range fileMap.filesByInodeURL {
delete(fileMap.filesByInodeURL, inodeURLStr)
_ = file.Close()
}
}