IOException: Success
Some time ago, I encountered and resolved a truly bizarre issue: certain players were unable to save their game due to an IOException with the error message "Success". To this day, I still receive inquiries from fellow devs about how to address this problem. This article documents the issue and provides a reference for the workaround.
Root cause of the IOException: Success
The exception was triggered by a File.Delete
call.
After exhausting the "this shouldn't happen" phase of debugging (without any way to reproduce the issue locally),
I discovered that the exception occurred when the file was actually deleted successfully.
Hence, the error message "Success."
This anomaly only affected players running our game through
CrossOver,
a compatibility layer that allows Mac users to run Windows programs.
It appears that CrossOver incorrectly interpreted a success exit code from a system call as an error, which was then surfaced as an exception by the .NET runtime.
The workaround
Unfortunately, the solution was more of a workaround, implemented in three stages:
First, I've wrapped all File.Delete
calls in a try-catch block and ignored exceptions where the message was "Success."
This resolved the issue for most players.
Second, while the initial fix worked for many, our logs still showed anomalies.
Turns out that .NET exception messages are localized.
Players running the game in different languages encountered "Success" exceptions translated into their system's language (Why are system exceptions localized, Microsoft‽).
To address this, I've added another check for the HResult
property.
If HResult
is 0 (indicating a successful operation), the exception is ignored.
This ensured that the fix was robust across all languages.
Third, to future-proof the workaround, I've added another layer of protection:
if File.Exists
fails but the file no longer exists, the operation is treated as a success.
Warnings are logged for these cases, allowing us to monitor any further issues.
The Code listing 1 below shows the final code snippet implementing all three protections. Similar safety checks were also added for other file operations to prevent similar issues.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void DeleteFile(string filePath) {
try {
File.Delete(filePath);
} catch (IOException ex) {
if (ex.HResult == 0 || ex.Message.StartsWith("Success", StringComparison.Ordinal)) {
// Ignore exceptions starting with "Success" string(work - around for issues with CrossOver.
Log.Warning($"File.Delete failed with result {ex.HResult} and {ex.Message}, ignoring.");
} if (File.Exists(filePath) == false) {
// Ignore exceptions when file was actually deleted.
Log.Warning($"File.Delete failed with result {ex.HResult} and {ex.Message}, "
+ "but the file was actually deleted, ignoring");
} else {
throw;
}
}
}
Final thoughts
I've reported this problem to the CrossOver team, and they were able to reproduce it, however, I haven't received any updates from them since. I am still receiving questions about this issue so it's still out there in the wild, possibly encountered by people who use older versions of CrossOver.
I hope this documentation helps anyone who encountered this strange issue. If nothing else, I hope you found it a bit amusing to learn about. Debugging this kind of issue truly keeps me on my toes!