Skip to content

a full disk is treated as an ordinary per-file error: the run continues and burns bandwidth it cannot write #116

Description

@LeyckerS

When the destination fills up, every remaining file is still extracted, downloaded in full, and only then fails on the write. The run does not stop and the user is not told.

Observed

58 links, destination drive with no free space left:

  Total links    : 58
  Completed OK   : 12
  Failed         : 46
  Total data     : 4.36 GB
  Median DL time : 52.9s

The GUI meanwhile reported 16.58 GB transferred over 4m 50s. Every one of the 46 failures carries the same note:

  1    Halo_Campaign_Evolved_...    0   0    0.0    0.4   236.1    —  fail
       → error att 1: [Errno 28] No space left on device
  2    Halo_Campaign_Evolved_...    1   0    0.0    0.5   236.5    —  fail
       → error att 1: [Errno 28] No space left on device
  3    Halo_Campaign_Evolved_...    2   0    0.0    0.3   236.2    —  fail

Each of those spent 236 seconds downloading before failing on a write that could never succeed. Roughly 12 GB pulled off the network and discarded.

Cause

ENOSPC is not distinguished anywhere — grep -n "ENOSPC\|No space" moon_download.py returns nothing. It lands in the catch-all:

except Exception as e:
    err = str(e)
    rec.notes.append(f"error att {att+1}: {err}")
    if att < DL_INNER_RETRIES - 1 and ("ContentLengthError" in err or "not enough data" in err.lower()):
        await asyncio.sleep(0.5 * (att + 1))
        continue
    return False, err, downloaded

It correctly avoids the inner retry, then returns a per-file failure and the worker moves to the next link — which will fail identically, because the disk does not empty itself between files.

The note goes to rec.notes, which only reaches the report after the run (#99), so nothing on screen ever says "disk full". The user sees files failing for no stated reason.

What is needed

ENOSPC is not a per-file problem. It is a run-level, non-recoverable condition and the only correct response is to stop:

  • Detect it specifically — isinstance(e, OSError) and e.errno == errno.ENOSPC rather than matching on the message, which is localised (the report above is from an Italian Windows and reads "No space left on device" only because Python normalises it; other paths do not)
  • Abort the run rather than continuing to the next link
  • Say so unmistakably, in the live log and not only in the report: which folder, how much was needed
  • Leave the .tmp files alone — they are resumable once space is freed, and the run had 46 of them

Worth deciding and stating in the pull request: whether in-flight transfers are cancelled immediately or allowed to finish. Cancelling recovers the bandwidth sooner; finishing means fewer half-files. Either is defensible.

Acceptance criteria

  • A run that hits ENOSPC stops instead of attempting every remaining link
  • The reason appears on screen while it happens, naming the destination folder
  • Existing .tmp files are preserved for resume
  • Detection is by errno, not by string matching
  • A regression test with a write that raises OSError(errno.ENOSPC, ...) — no network needed, tests/test_resume_200.py shows the pattern for faking the response surface

Environment

Windows 10, Python 3.14.6, destination on a drive with 0 bytes free of 931 GB. Found by running the program.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinghelp wantedExtra attention is needed

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions