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

ethtool fd leaks #1081

Merged
merged 4 commits into from
Mar 16, 2023
Merged

ethtool fd leaks #1081

merged 4 commits into from
Mar 16, 2023

Conversation

svinota
Copy link
Owner

@svinota svinota commented Mar 15, 2023

Bug-Url: #1079

def __init__(self):
self._with_ioctl = IoctlEthtool()
self._with_nl = NlEthtool()
self._with_nl.module_err_level = 'debug'
self._is_nl_working = self._with_nl.is_nlethtool_in_kernel()

def __enter__(self):
Copy link

@socketpair socketpair Mar 15, 2023

Choose a reason for hiding this comment

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

Isn't it better to define this in one of the base classes ?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Netlink classes have it defined, but Ethtool is a bit separate thing using composition (netlink | ioctl). We can later create one common context manager utility class.



def get_fds():
return set(os.listdir(f'/proc/{os.getpid()}/fd'))
Copy link

@socketpair socketpair Mar 15, 2023

Choose a reason for hiding this comment

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

may fail because listing a directory requires file descriptor. Unstable test. How to do better ? open() directory and then list the directory by passing file descriptor instead of path. Then omit the descriptor of the directory from the resulting set.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Good point, fixed.

def get_fds():
fd = os.open(f'/proc/{os.getpid()}/fd', os.O_RDONLY)
try:
return set(os.listdir(fd))

Choose a reason for hiding this comment

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

return set(os.listdir(fd)) - {fd}

Copy link
Owner Author

Choose a reason for hiding this comment

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

Indeed.

Explicitly open/use/close the proc/../fd directory fd
@svinota svinota force-pushed the svinota-fd-leaks-1079 branch from 886d983 to b7b4452 Compare March 15, 2023 16:24
@socketpair
Copy link

lgtm

@svinota svinota merged commit 49abe5e into master Mar 16, 2023
@svinota svinota deleted the svinota-fd-leaks-1079 branch March 16, 2023 09:15
def test_pipe_leak():
fds = get_fds()
etht = Ethtool()
etht.close()

Choose a reason for hiding this comment

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

HUH. not calling .close() will leak descriptors anyway ? You have to add __del__ to classes that use os.open() / os.pipe() in class constructors.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Personally I prefer resources to be freed explicitly, putting anyting in __del__ requires a lot of checks and exception handling to guarantee no errors during the GC work.

But I'm to try to look what we can do there.

Copy link

@socketpair socketpair Mar 16, 2023

Choose a reason for hiding this comment

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

@svinota just close the resources opened explicitly.

from contextlib import suppress

class Xxx:
    def __init__(self):
            self.__x, self.__y = os.pipe()
            
     def close():
            # i.e. check every resource if it is closed already, close and clear the variable somehow.
            if self.__x !=1:
                os.close(self.__x)
                self.__x = -1 # not 'None' because of typing.
          
    def __del__(self):
            super().__del__() # is it required?
            self.close()

Copy link

@socketpair socketpair Mar 16, 2023

Choose a reason for hiding this comment

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

@svinota Another way:

import os


class FdWrapper(int):
    def __del__(self):
        self.close()

    def close(self):
        if not hasattr(self, '__closed'):
            try:
                print('here')
                os.close(self)
            except OSError:
                pass  # TODO: log ?
            finally:
                setattr(self, '__closed', True)


def MyPipe() -> tuple[FdWrapper, FdWrapper]:
    x, y = os.pipe()
    return FdWrapper(x), FdWrapper(y)


x, y = MyPipe()
os.write(y, b'qwe')
x.close()

P.S. updated. I don't use a constructor because it's hard to do it when subclassing int. But you may prefer not to subclass, but create parentless class and add .fileno()

@svinota svinota restored the svinota-fd-leaks-1079 branch March 19, 2023 16:53
@svinota svinota deleted the svinota-fd-leaks-1079 branch March 19, 2023 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants