Skip to content

fix: resolved Windows file permission error#3269

Open
agsaru wants to merge 2 commits into
Netflix:masterfrom
agsaru:filecache
Open

fix: resolved Windows file permission error#3269
agsaru wants to merge 2 commits into
Netflix:masterfrom
agsaru:filecache

Conversation

@agsaru

@agsaru agsaru commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

PR Type

  • Bug fix
  • New feature
  • Core Runtime change (higher bar -- see CONTRIBUTING.md)
  • Docs / tooling
  • Refactoring

Summary

  • Replaced os.rename with os.replace, and ensured temporary files are explicitly closed and synced to disk before moving. This prevents PermissionErrors caused by file locks on Windows.

  • Added a None check in get_log_legacy to prevent the datastore from attempting to write empty log files to the cache.

  • Swapped an O(N) list .insert() with an O(1) .append() in _index_objects() to speed up lazy indexing of cache objects. Because at the end that was sorted so the indexing does not matters.

  • Added python 2 compatible import and _replace function.

Copilot AI review requested due to automatic review settings June 13, 2026 07:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a Windows PermissionError in FileCache.create_file by closing the NamedTemporaryFile before renaming it — on Windows, open file handles block rename operations. It also switches to os.replace (atomic overwrite) and adds os.fsync for durability.

  • Core fix is correct: closing tmpfile before the rename resolves the Windows permission error; using os.replace instead of os.rename adds atomic-overwrite semantics.
  • None-guard on log: the new if log is not None check before create_file prevents a downstream error when load_log_legacy returns None.
  • Dead code added: both the urlparse try/except fallback and the Python 2 _replace shim target Python 2, which Metaflow no longer supports (requires 3.6+); these branches can never execute and should be removed.

Confidence Score: 4/5

The change is safe to merge; the core tempfile-close-before-rename fix is correct and the other improvements are low-risk.

The functional changes (close before rename, os.replace, fsync, None guard) are all correct. The only concerns are two Python 2 compatibility shims that are unreachable dead code and a redundant close() call in the except block — none of these affect runtime behaviour on any supported platform.

metaflow/client/filecache.py — the Python 2 shims (urlparse fallback and _replace wrapper) should be removed before merging to keep the file clean.

Important Files Changed

Filename Overview
metaflow/client/filecache.py Core fix (close tempfile before rename) correctly addresses the Windows PermissionError. Also adds os.fsync, guards against None log, and switches to os.replace. Two unnecessary Python 2 compatibility shims are added as dead code.

Reviews (1): Last reviewed commit: "made replace python 2 compatible" | Re-trigger Greptile

Comment on lines +10 to +15
try:
# Python 3
from urllib.parse import urlparse
except ImportError:
# Python 2
from urlparse import urlparse

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Python 2 dead code — urlparse fallback unreachable

Metaflow requires Python 3.6+, so the except ImportError branch importing from urlparse import urlparse can never execute. The original single-line import was correct. This try/except adds noise without benefit.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The duplicate tmpfile.close() is required. If an i/p exception occurs during write(), flush(), or fsync(), the file is never closed in the try block.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your comment seems to be addressing a different change than what my comment was about — I flagged the urlparse try/except as dead code (since Metaflow requires Python 3.6+), while you're discussing the tmpfile.close() in the except block of create_file.

On the tmpfile.close() point: you're correct. If write(), flush(), or fsync() raises, the tmpfile.close() inside the try block is never reached, so the handle would leak without the one in except. That's a valid and necessary pattern.

My original comment stands separately — the urlparse try/except wrapping from urllib.parse import urlparse is still unreachable dead code given Metaflow's Python 3.6+ requirement, and the original single-line import was fine.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

Comment on lines +32 to +45
if sys.version_info[0] >= 3:
_replace = os.replace
else:
import errno

def _replace(src, dst):
try:
os.rename(src, dst)
except OSError as e:
if e.errno == errno.EEXIST or (os.name == "nt" and e.errno in (13, 17)):
os.remove(dst)
os.rename(src, dst)
else:
raise

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Python 2 _replace fallback is dead code

The else branch (Python < 3) and the custom _replace function are unreachable given Metaflow's Python 3.6+ requirement. os.replace (added in Python 3.3) is always available, handles atomic overwrite on all platforms including Windows, and can be called directly. The sys.version_info guard and the fallback implementation just add complexity. Consider replacing all uses of _replace with os.replace directly.

Comment on lines +300 to +303
except Exception: # noqa E722
tmpfile.close()
if os.path.exists(tmpfile.name):
os.unlink(tmpfile.name)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Redundant close() call in the exception handler

tmpfile.close() is already called in the try block (line 296) before _replace. If _replace raises, the except block calls tmpfile.close() again on an already-closed file. While this is safe (Python's file close() is idempotent), it may be clearer to restructure with finally or remove the duplicate call.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@agsaru

agsaru commented Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

@talsperre, could you confirm whether Metaflow is still compatible with Python 2?

@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 23.07692% with 20 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (master@38797e3). Learn more about missing BASE report.

Files with missing lines Patch % Lines
metaflow/client/filecache.py 23.07% 19 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##             master    #3269   +/-   ##
=========================================
  Coverage          ?   29.08%           
=========================================
  Files             ?      381           
  Lines             ?    52532           
  Branches          ?     9270           
=========================================
  Hits              ?    15278           
  Misses            ?    36217           
  Partials          ?     1037           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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