aiida-core icon indicating copy to clipboard operation
aiida-core copied to clipboard

Fix FileNotFoundError in monitors and importers using improper NamedTemporaryFile pattern

Open Copilot opened this issue 3 months ago • 2 comments

The always_kill monitor and ArithmeticAddCalculationImporter were causing FileNotFoundError when transport.getfile() attempted to write to temporary files created with NamedTemporaryFile('w+'). The open file handle conflicted with external file access, particularly on Windows and certain filesystems.

Changes

  • Replace NamedTemporaryFile(delete=True) with tempfile.mkstemp() in monitor and importer code
    • Matches existing pattern in cmd_computer.py for transport operations
    • Explicitly close file descriptor before external access
    • Manual cleanup in finally block with OSError handling
  • Add minimal tests to verify temporary file cleanup
    • test_always_kill_tempfile_cleanup: Verifies the monitor properly cleans up temporary files
    • test_parse_remote_data_tempfile_cleanup: Verifies the importer properly cleans up temporary files

Before:

with tempfile.NamedTemporaryFile('w+') as handle:
    cwd = node.get_remote_workdir()
    transport.getfile(str(Path(cwd).joinpath('_aiidasubmit.sh')), handle.name)
    handle.seek(0)
    output = handle.read()

After:

handle, temp_path = tempfile.mkstemp(suffix='.sh')
os.close(handle)

try:
    transport.getfile(str(Path(cwd).joinpath('_aiidasubmit.sh')), temp_path)
    with open(temp_path, 'r', encoding='utf-8') as f:
        output = f.read()
finally:
    try:
        os.remove(temp_path)
    except OSError:
        pass

Files Modified

  • src/aiida/calculations/monitors/base.py - always_kill() function
  • src/aiida/calculations/importers/arithmetic/add.py - ArithmeticAddCalculationImporter.parse_remote_data() method
  • tests/engine/processes/calcjobs/test_monitors.py - Added cleanup test for always_kill()
  • tests/calculations/importers/arithmetic/test_add.py - Added cleanup test for parse_remote_data()
Original prompt

This section details on the original issue you should resolve

<issue_title>🐛 FileNotFoundError related to monitors</issue_title> <issue_description>While "running" a workflow, I am reliably running into the following error:

FileNotFoundError: [Errno 2] No such file

From the full traceback (see below), the issue seems to be related to the monitors. In the end, the workflow does complete successfully, but the issue produces a lot of noise that will worry users.

I tested both core.ssh and core.ssh_async, and am running into the issue for both.

Full Traceback
11/05/2025 11:52:01 AM <1716> aiida.engine.transports: [ERROR] Exception whilst using transport:
Traceback (most recent call last):
  File "/Users/mbercx/project/defect/git/aiida-core/src/aiida/engine/transports.py", line 106, in request_transport
    yield transport_request.future
  File "/Users/mbercx/project/defect/git/aiida-core/src/aiida/engine/processes/calcjobs/tasks.py", line 257, in do_monitor
    return monitors.process(node, transport)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mbercx/project/defect/git/aiida-core/src/aiida/engine/processes/calcjobs/monitors.py", line 204, in process
    monitor_result = monitor_function(node, transport, **monitor.kwargs)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mbercx/project/defect/git/aiida-vasp/src/aiida_vasp/calcs/monitors.py", line 66, in monitor_stdout
    file_stat = transport.get_attribute(stdout_path)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mbercx/project/defect/git/aiida-core/src/aiida/transports/plugins/ssh.py", line 1201, in get_attribute
    paramiko_attr = self.lstat(path)
                    ^^^^^^^^^^^^^^^^
  File "/Users/mbercx/project/defect/git/aiida-core/src/aiida/transports/plugins/ssh.py", line 662, in lstat
    return self.sftp.lstat(path)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mbercx/.aiida_venvs/defect/lib/python3.12/site-packages/paramiko/sftp_client.py", line 511, in lstat
    t, msg = self._request(CMD_LSTAT, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mbercx/.aiida_venvs/defect/lib/python3.12/site-packages/paramiko/sftp_client.py", line 857, in _request
    return self._read_response(num)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mbercx/.aiida_venvs/defect/lib/python3.12/site-packages/paramiko/sftp_client.py", line 909, in _read_response
    self._convert_status(msg)
  File "/Users/mbercx/.aiida_venvs/defect/lib/python3.12/site-packages/paramiko/sftp_client.py", line 938, in _convert_status
    raise IOError(errno.ENOENT, text)
FileNotFoundError: [Errno 2] No such file

11/05/2025 11:52:01 AM <1716> aiida.orm.nodes.process.calculation.calcjob.CalcJobNode: [ERROR] iteration 1 of do_monitor excepted, retrying after 20 seconds
Traceback (most recent call last):
  File "/Users/mbercx/project/defect/git/aiida-core/src/aiida/engine/utils.py", line 205, in exponential_backoff_retry
    result = await coro()
             ^^^^^^^^^^^^
  File "/Users/mbercx/project/defect/git/aiida-core/src/aiida/engine/processes/calcjobs/tasks.py", line 257, in do_monitor
    return monitors.process(node, transport)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mbercx/project/defect/git/aiida-core/src/aiida/engine/processes/calcjobs/monitors.py", line 204, in process
    monitor_result = monitor_function(node, transport, **monitor.kwargs)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mbercx/project/defect/git/aiida-vasp/src/aiida_vasp/calcs/monitors.py", line 66, in monitor_stdout
    file_stat = transport.get_attribute(stdout_path)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mbercx/project/defect/git/aiida-core/src/aiida/transports/plugins/ssh.py", line 1201, in get_attribute
    paramiko_attr = self.lstat(path)
                    ^^^^^^^^^^^^^^^^
  File "/Users/mbercx/project/defect/git/aiida-core/src/aiida/transports/plugins/ssh.py", line 662, in lstat
    return self.sftp.lstat(path)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mbercx/.aiida_venvs/defect/lib/python3.12/site-packages/paramiko/sftp_client.py", line 511, in lstat
    t, msg = self._request(CMD_LSTAT, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mbercx/.aiida_venvs/defect/lib/python3.12/site-packages/paramiko/sftp_client.py", line 857, in _request
    return self._read_response(num)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mbercx/.aiida_venvs/defect/lib/python3.12/site-packages/paramiko/sftp_client.py", line 909, in _read_response
    self._convert_status(msg)
  File "/Users/mbercx/.aiida_venvs/defect/lib/python3.12/site-packages/paramiko/sftp_client.py", line 938, in _convert_status
    raise IOError(errno.ENOENT, text)
FileNotFoundError: [Errno 2] No such file

Comments on the Issue (you ar...

  • Fixes aiidateam/aiida-core#7086

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot avatar Dec 10 '25 11:12 Copilot

Codecov Report

:x: Patch coverage is 42.30769% with 15 lines in your changes missing coverage. Please review. :white_check_mark: Project coverage is 79.61%. Comparing base (396792b) to head (402d057). :warning: Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/aiida/calculations/monitors/base.py 7.15% 13 Missing :warning:
src/aiida/calculations/importers/arithmetic/add.py 83.34% 2 Missing :warning:
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #7148       +/-   ##
===========================================
+ Coverage   29.18%   79.61%   +50.44%     
===========================================
  Files         566      566               
  Lines       43530    43590       +60     
===========================================
+ Hits        12702    34702    +22000     
+ Misses      30828     8888    -21940     

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

codecov[bot] avatar Dec 10 '25 11:12 codecov[bot]

@copilot changes looks good. Please try writing minimal tests

Added minimal tests in commit cac5f4d:

  • test_always_kill_tempfile_cleanup: Verifies the monitor properly cleans up temporary files after execution
  • test_parse_remote_data_tempfile_cleanup: Verifies the importer properly cleans up temporary files after parsing

Both tests use monkeypatch to track mkstemp() calls and verify no temporary files remain after execution.

Copilot avatar Dec 10 '25 11:12 Copilot