Skip to content

Avoid FD (file descriptor) leak #560

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Avoid FD (file descriptor) leak #560

wants to merge 2 commits into from

Conversation

Saga4
Copy link
Contributor

@Saga4 Saga4 commented Jul 18, 2025

Testing it with https://github.com/Saga4/core/pulls where kept getting error:

with Popen(*popenargs, kwargs) as process:
         ~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/miniforge3/envs/core/lib/python3.13/subprocess.py", line 1005, in init**
    errread, errwrite) = self._get_handles(stdin, stdout, stderr)
                         ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/miniforge3/envs/core/lib/python3.13/subprocess.py", line 1731, in gethandles
    c2pread, c2pwrite = os.pipe()
                        ~~~~~~~^^
OSError: [Errno 24] Too many open files
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/home/ubuntu/core/venv/bin/codeflash", line 8, in <module>
    sys.exit(main())
             ~~~~^^
  File "/home/ubuntu/core/venv/lib/python3.13/site-packages/codeflash/main.py", line 46, in main
    optimizer.run_with_args(args)
    ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/home/ubuntu/core/venv/lib/python3.13/site-packages/codeflash/optimization/optimizer.py", line 390, in run_with_args
    optimizer.run()
    ~~~~~~~~~~~~~^^
  File "/home/ubuntu/core/venv/lib/python3.13/site-packages/codeflash/optimization/optimizer.py", line 349, in run
    self.cleanup_temporary_paths()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/home/ubuntu/core/venv/lib/python3.13/site-packages/codeflash/optimization/optimizer.py", line 381, in cleanup_temporary_paths
    get_run_tmp_file.tmpdir.cleanup()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/home/ubuntu/miniforge3/envs/core/lib/python3.13/tempfile.py", line 954, in cleanup
    self._rmtree(self.name, ignore_errors=self._ignore_cleanup_errors)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/miniforge3/envs/core/lib/python3.13/tempfile.py", line 934, in _rmtree
    _shutil.rmtree(name, onexc=onexc)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/miniforge3/envs/core/lib/python3.13/shutil.py", line 763, in rmtree
    rmtreesafe_fd(stack, onexc)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  File "/home/ubuntu/miniforge3/envs/core/lib/python3.13/shutil.py", line 707, in rmtreesafe_fd
    onexc(func, path, err)
    ~~~~~^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/miniforge3/envs/core/lib/python3.13/shutil.py", line 682, in rmtreesafe_fd
    with os.scandir(topfd) as scandir_it:
         ~~~~~~~~~~^^^^^^^
OSError: [Errno 24] Too many open files: '/tmp/codeflash__tgzk05c'

User description

  • Add precautions to FD leaks
  • Add precautions to FD leaks2

PR Type

Bug fix


Description

  • Close SQLite connections after usage

  • Use context manager for devnull file

  • Ensure subprocess pipes are closed

  • Add Makefile with leak test command


File Walkthrough

Relevant files
Error handling
replay_test.py
Add SQLite connection cleanup                                                       

codeflash/benchmarking/replay_test.py

  • Wrap SQLite operations in try/finally
  • Close DB connection in get_next_arg_and_return
  • Ensure conn.close in generate_replay_test
+79/-68 
beta.py
Manage devnull file context                                                           

codeflash/lsp/beta.py

  • Use context manager for os.devnull writer
  • Redirect stdout within 'with' block
  • Remove manual open(os.devnull) call
+1/-2     
codeflash_capture.py
Close capture database connection                                               

codeflash/verification/codeflash_capture.py

  • Move cursor creation inside try block
  • Close SQLite connection in finally
  • Balance gc.disable/enable calls properly
+47/-44 
test_runner.py
Ensure subprocess pipes closed                                                     

codeflash/verification/test_runner.py

  • Replace subprocess.run with Popen
  • Explicitly close stdin/stdout/stderr pipes
  • Handle timeouts with kill and terminate
+25/-1   
Configuration changes
Makefile
Add development Makefile                                                                 

Makefile

  • Add comprehensive development Makefile
  • Include test-file-handles leak test
+221/-0 

Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Resource Leak

The DB connection in get_next_arg_and_return is closed in a finally block that only runs when the generator is fully consumed or garbage-collected. If iteration is stopped early, the connection remains open.

try:
    cur = db.cursor()
    limit = num_to_get

    if class_name is not None:
        cursor = cur.execute(
            "SELECT * FROM benchmark_function_timings WHERE benchmark_function_name = ? AND function_name = ? AND file_path = ? AND class_name = ? LIMIT ?",
            (benchmark_function_name, function_name, file_path, class_name, limit),
        )
    else:
        cursor = cur.execute(
            "SELECT * FROM benchmark_function_timings WHERE benchmark_function_name = ? AND function_name = ? AND file_path = ? AND class_name = '' LIMIT ?",
            (benchmark_function_name, function_name, file_path, limit),
        )

    while (val := cursor.fetchone()) is not None:
        yield val[9], val[10]  # pickled_args, pickled_kwargs
finally:
    db.close()
Exception Pickling

Using pickle.dumps(exception) for arbitrary exception objects may fail if the exception isn't serializable. Consider serializing exception details instead of the raw object.

exception = None
gc.disable()
try:
    counter = time.perf_counter_ns()
    wrapped(*args, **kwargs)
    codeflash_duration = time.perf_counter_ns() - counter
except Exception as e:
    codeflash_duration = time.perf_counter_ns() - counter
    exception = e
Install Command

The target uses uv pip install ., which may be incorrect. It probably should be a direct pip install . or use uv run pip install ..

uv pip install .

@Saga4 Saga4 changed the title saga4/FD leak Avoid FD (file descriptor) leak Jul 18, 2025
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Check args before accessing

Add a guard to ensure there is at least one positional argument before accessing
args[0], so you avoid an IndexError when the wrapper is invoked without arguments.

codeflash/verification/codeflash_capture.py [137-142]

-if hasattr(args[0], "__dict__"):
-    instance_state = args[
-        0
-    ].__dict__  # self is always the first argument, this is ensured during instrumentation
-else:
+if not args or not hasattr(args[0], "__dict__"):
     raise ValueError("Instance state could not be captured.")
+instance_state = args[0].__dict__
Suggestion importance[1-10]: 5

__

Why: Adding a guard for an empty args tuple prevents a potential IndexError and safely raises a ValueError, improving the robustness of the wrapper.

Low

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant