Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@smallketchup82
Copy link
Contributor

Closes #5997

Introduction

This PR adds a static DiskUsage class to osu.Game/IO with a method to natively determine available disk space given a path to a file or directory, providing a safeguard against running out of space during gameplay.

Implementation & Notes

  • I based the architectural approach on stable's implementation, focusing on using DriveInfo for system-level disk usage checking.
  • The disk space check is performed on startup during realm initialization only, rather than during realm file additions or write transactions. This decision prioritizes simplicity and avoids compromising real-time performance.
  • An asynchronous version, EnsureSufficientSpaceAsync, has been implemented to allow for disk space checks in future IO-bound scenarios without blocking the calling thread.
  • The warning threshold has been adjusted from stable's 128 MiB to 512 MiB. This is intended to function as an earlier warning system, providing users with more time to free up space before they encounter critical issues. This value is open for discussion.
  • In RealmAccess, I had to add using FileInfo = System.IO.FileInfo; due to a FileInfo class existing in both System.IO and osu.Game.IO. Read the commit for details on that.
  • I can move these changes to osu-framework if preferred
  • Tests are included to ensure basic I/O functionality and path validation are working as expected. These tests complement manual verification performed on MacOS, where the warning notification was successfully triggered on low disk space.

Overall, this is a simple solution to the linked issue, adding roughly 62 LOC excluding tests. I plan to submit smaller PRs (excl. haptics) going forwards to reduce burden on the core team with my contributions.

This is due to a `FileInfo` class existing in both `System.IO` and
`osu.Game.IO`.

I decided to go with this, but I can remove this and reference the
namespace during the only `FileInfo` usage in the file, or remove the
`osu.Game.IO` import and specify `osu.Game.IO` when calling `DiskUsage`

Could also just move the `DiskUsage` class outside of `osu.Game.IO` to
wherever it fits better. Let me know if an alternative solution to this
commit is preferred.
Comment on lines 45 to 48
public static async Task EnsureSufficientSpaceAsync(string checkDirectory, long requiredSpace = required_space_default)
{
await Task.Run(() => EnsureSufficientSpace(checkDirectory, requiredSpace)).ConfigureAwait(false);
}
Copy link
Member

Choose a reason for hiding this comment

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

I'd be worried about usages of this in something like an import operation firing far too many checks.

Also I think you can make this return the task directly rather than adding an extra async layer:

Suggested change
public static async Task EnsureSufficientSpaceAsync(string checkDirectory, long requiredSpace = required_space_default)
{
await Task.Run(() => EnsureSufficientSpace(checkDirectory, requiredSpace)).ConfigureAwait(false);
}
public static Task EnsureSufficientSpaceAsync(string checkDirectory, long requiredSpace = required_space_default)
{
return Task.Run(() => EnsureSufficientSpace(checkDirectory, requiredSpace));
}

Copy link
Contributor Author

@smallketchup82 smallketchup82 Dec 11, 2025

Choose a reason for hiding this comment

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

I've applied your suggestion in c641f0a

I did originally consider adding debouncing logic here. My original idea was to add a DateTime as a property of DiskUsage, storing DateTime.Now in it whenever EnsureSufficientSpace is called, and silently return in said method if it hasn't yet been 5 minutes since the last call.

I decided to leave that out of this PR as the async wrapper is mostly just future-proofing, and this logic could be added in the future if the async wrapper does ever end up being used in real-time contexts. But I can see the confusion in supplying this method, but not going the full mile and specializing it for its intended purpose 😅

I can probably just remove the async wrapper altogether if we decide to stick with the "only on startup" approach. The actual overhead of checking disk space isn't really high, but it can get bad if done in a loop.

Co-authored-by: Dean Herbert <[email protected]>
if (!Directory.Exists(checkPath))
throw new DirectoryNotFoundException($"The directory '{checkPath}' does not exist or could not be found.");

string? validPathRoot = Path.GetPathRoot(checkPath);
Copy link
Member

@Susko3 Susko3 Dec 11, 2025

Choose a reason for hiding this comment

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

If I'm reading the documentation correctly, this will basically always return "/" on non-Windows, making this code invalid if osu! is on an external disk. Have you checked that this correctly reports the free size when querying directories on external disks on macOS?

Copy link
Contributor Author

@smallketchup82 smallketchup82 Dec 11, 2025

Choose a reason for hiding this comment

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

I have not, thanks for catching that. Should be fixed in fb73dec

Using GetPathRoot() does always return "/". Fortunately, my main use for that method was simply to check if the provided path is valid, so changing it to GetFullPath() properly provides the correct disk space while still checking path validity.

Here's a test on my Linux PC, having switched to GetFullPath(), testing for "/" and my HDD:
image

Which is roughly consistent with what my OS reports for my hard drive:
image

Note: The reason for the large difference here is that the logger reports the available space in MiB, while duf reports in GB. Convert the 385,103 MiB to GB and you get 403GB, which is exactly what my OS reports.
The reason for that second discrepancy (376GB vs 403GB), is due to the fact that duf reports btrfs's actual free space including metadata usage, while DiskInfo just checks whatever my OS returns. The available space for my HDD is reported as 403GB in nautilus, so everything here is accurate and working as intended, just confusing due to linux shenanigans.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider hard disk drive space availability and early-warn user on low disk space

3 participants