Search Unity

Catch "device out of space" exception

Discussion in 'Scripting' started by Mr_Jigs, Mar 25, 2016.

  1. Mr_Jigs

    Mr_Jigs

    Joined:
    Apr 18, 2015
    Posts:
    69
    Morning everyone (at least it is morning over here ;)),

    I would like to find out how to catch an "out of free space on device" exception explicitly. Using testing and careful programming I should be able to avoid most IO errors with regards to reading and writing files. The one thing I will never know however is how much space is left on the users device.

    My app allows users to create and store drawings on their device. So potentially the device can run out of storage space. I would like to be able to handle this situation.

    I believe I need to use try & catch to catch an exception and I that can even use IOException instead of the general Exception to focus on IO errors. But how would I go about separating out an "out of space" situation?

    Here's the code I plan to use:

    Code (CSharp):
    1.  
    2.  
    3.     public string WriteToFile(string pathAndFilename)
    4.     {
    5.         string message;
    6.         message = null;
    7.  
    8.         try
    9.         {
    10.             StreamWriter writer;
    11.  
    12.             Debug.Log ("Saving file to: " + pathAndFilename);
    13.  
    14.             FileStream fcreate = File.Open(pathAndFilename, FileMode.Create);
    15.             writer = new StreamWriter(fcreate);
    16.  
    17.             images = palettePanel.GetComponentsInChildren <Image>();
    18.  
    19.             index = 0;
    20.             foreach( Image image in images) {
    21.                 writer.WriteLine(image.color.r, image.color.g, image.color.b);
    22.                 index++;
    23.             }
    24.  
    25.             writer.Flush ();
    26.             writer.Close ();
    27.             return message;
    28.         }
    29.         catch (IOException ex)
    30.         {
    31.             // catch out of space somehow
    32.             return message = "Device out of space";
    33.         }
    34.     }
    Any thoughts?
     
  2. Mr_Jigs

    Mr_Jigs

    Joined:
    Apr 18, 2015
    Posts:
    69
    Some more search work and putting 1 and 2 together and I came up with the following solution:

    Code (CSharp):
    1.  
    2.         catch (Exception ex)
    3.         {
    4.             const int ERROR_HANDLE_DISK_FULL = 0x27;
    5.             const int ERROR_DISK_FULL = 0x70;
    6.  
    7.             int win32ErrorCode = Marshal.GetHRForException(ex) & 0xFFFF;
    8.             if (win32ErrorCode == ERROR_HANDLE_DISK_FULL || win32ErrorCode == ERROR_DISK_FULL)
    9.             {
    10.                 return message = "Device out of space";
    11.             } else
    12.             {
    13.                 return message = "Unknown Error: " + win32ErrorCode.ToString();
    14.             }
    15.  
    16.         }
    Since it is a bit hard to test I would appreciate your thought on the "correctness" of the solution.

    Thanks
     
  3. Dave-Carlile

    Dave-Carlile

    Joined:
    Sep 16, 2012
    Posts:
    967
    There is yours, and a couple of other solutions in this StackOverflow answer. Apparently there is some side effect in using the Marshal call which may or may not cause you issues. Exception has a protected HResult property that was made public in a later version. You could see if it happens to be public in the version of Mono being used by Unity, or use reflection to get at the protected property as suggested in the StackOverflow answer.

    You might also want to catch IOException instead of Exception. You can have multiple catch clauses going from more to less specific. In general you'd save Exception for last to handle anything you don't handle specifically before.
     
  4. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    I was actually a little surprised at first that there is no "OutOfSpaceException", since this seems like a reasonable kind of exception you would want to differentiate from other IOExceptions.

    When you write exception handling, you should only catch the exceptions you are going to deal with and allow everything else to propagate up. You shouldn't catch the very generic Exception type because catch statements imply you are going to "handle" this type of exception, and any type lower in it's class hierarchy. For Exception, that means you are claiming to handle A LOT of problems.

    This is why I thought an OutOfSpaceException would be nice, so you can treat this case differently than other IOExceptions. Except.. Then began to wonder what the code would look like to handle something like error codes: ERROR_WRITE_FAULT or ERROR_GEN_FAILURE or any of the other myriad disk related codes. I can't think of what I would do for those situations other than say something like "Sorry, we couldn't save your picture because the operating system had this error: " ... No matter how clever I am, when there are IO problems, I pretty much need the user to take over.

    So actually, my .net code would end up being exactly the same for pretty much all of the disk based IO error codes.

    Before I show how I would handle this, let's take a quick look at your code:

    Code (CSharp):
    1.  
    2.         catch (Exception ex) // Exception is too generic, I don't want to claim to handle all kinds of bad news such as StackOverflowException. Let's stick to catching IOExceptions
    3.         {
    4.             const int ERROR_HANDLE_DISK_FULL = 0x27; // I think we should handle all IOExceptions the same since we ultimately need the user to get involved, so this isn't necessary.
    5.             const int ERROR_DISK_FULL = 0x70; // Neither is this
    6.  
    7.             int win32ErrorCode = Marshal.GetHRForException(ex) & 0xFFFF; // Don't need this either
    8.             if (win32ErrorCode == ERROR_HANDLE_DISK_FULL || win32ErrorCode == ERROR_DISK_FULL) // nor this
    9.             {
    10.                 return message = "Device out of space"; // This seems a bit odd to me. An Exception was raised, so we should either recover or continue throwing the exception until it can be shown to the user.
    11.             } else
    12.             {
    13.                 return message = "Unknown Error: " + win32ErrorCode.ToString(); // This seems VERY odd to me. I've just eaten an Exception object and returned a pretty unhelpful string. At least the Exception object would have had a stack trace and debugging information. It wouldn't have been useful to a user but maybe they have an IT friend who could have helped them. Now? Pretty much no one can help. Better to just "throw;" to let the Exception continue bubbling up. Remember to use throw; instead of throw e; so the stack trace is preserved.
    14.             }
    15.  
    16.         }
    Okay, with that out of the way, I think I would just do this:

    Code (csharp):
    1. catch (IOException e)
    2. {
    3.   throw new IOException(string.Format("Sorry, we couldn't save your picture because the operating system had this error when using the disk: {0}", e.Message), e);
    4. }
     
  5. Mr_Jigs

    Mr_Jigs

    Joined:
    Apr 18, 2015
    Posts:
    69
    OK, eisenpony, I can see where you are going and agree on handling all IOExceptions. My "return message" is a way of passing the string containing some sort of "user friendly" information to the user in the application interface. Informing the user that their attempt at saving the picture has failed. I wanted to separate out the disk full part as that would seem to be the most likely cause. Anything else and the user probably has serious hardware problems.

    My logic goes: ok there's an IO problem. Is it to do with insufficient space? if yes show the user a message to that extend, if no show the user another message (which is probably going to be very unhelpful I agree). Aboard the operation and wait for user to confirm reading the message. resume normal operation.

    The program doesn't crash. the current drawing and program state have already been saved so the user can exit gracefully and solve any space and or other problems and later resume the app from the point where the problem occurred.

    Your use of throw (not very familiar with that yet) seems to me to want to output your new IOException message to the console where it would not be very useful to the user. Looks like you still would need additional code to inform the user. Or am I badly interpreting the way throw works here? :D
     
  6. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    No. You're absolutely right -- you'll need some more code to display this message to the user.

    The reason I chose to throw in my sample is because I usually build my system in layers such that the code which knows how to save a picture to disk knows nothing about displaying messages to the user. It looks like you follow a similar pattern since your error code is returning a string rather than displaying the message directly.

    I think the disconnect between our thinking is more conceptual in nature, and more related to error handling in general. Let me try to explain.

    Before the time of Exceptions, it was a common practice for methods to return a "code" representing how it completed, either a success-code or one of many error-codes. This was a pretty good way to report to a client if problems had occurred, but it led to some pretty ugly looking code. It also meant the "real" return value of the method, that is the result the method calculated, was typically squirreled away somewhere and the client would need to go and retrieve it. Since the intention of a method is to run successfully, and we assume a method runs successfully at least 9 times out of 10, it's not very helpful to have extra steps involved for retrieving results.

    Exceptions represent a different way of thinking about error-codes which allows us to write code that better represents the intended flow. From this point of view, even a void method in C# has a "return" type; it could "return" that something went wrong by throwing an exception. Using a try/catch block lets us show we are going to handle those kinds of returns by specifying explicitly what "types" of exceptions we are prepared to handle.

    With this tool, you can see why it's a bit odd to have a method which just saves some bytes to disk return a string message representing its success or failure. If there was a problem, you should use the built-in tools for reporting that: an Exception. So, what I propose is to allow Exceptions to "bubble up" your call stack back to a level where it makes sense to simply display a dialog to the user. This is typically at the GUI level, probably the same place the button was set up.

    One other bit of information you might find useful. An Exception has a property called Message which is a spot for a user friendly message. So, really, there is no reason to throw away an Exception and just return a string. There is already a user friendly message available to the caller of this method in Exception.Message.

    Something I did in my code was add additional information about what has gone wrong. The method you are calling to write the picture's bytes (File.WriteAllBytes for instance) doesn't know anything about the context of the application -- that it is trying to save the currently viewed picture to disk -- so it can't do anything more than say "the disk is full". By creating and throwing a new Exception with the old one added as the "innerException", we are able to add additional information about what has happened.

    In this case I choose to use an IOException but it might make more sense to create a custom exception type, such as CommandException, so that your GUI code can detect this specific kind of error execute further recovery code.
     
    Last edited: Mar 28, 2016
  7. Mr_Jigs

    Mr_Jigs

    Joined:
    Apr 18, 2015
    Posts:
    69
    So, let me see if I got the "exceptions" concept right. I would add a try/catch block at the point where the user presses the button to save the picture. I would not try to catch the exception at the level in my example where the the bytes are actually written to disk. If there is a problem at this level it would throw an exception but I would not worry about it at this level. I would let the exception "bubble up" as you say and catch it at the point where the user pressed the button. Which would be the right place to display a message to the user as we are in the UI somewhere anyway.

    Rather then me doing the "bubbling" by passing return values I leave it to the system and just catch them at the appropriate point. Rather neat actually.
     
  8. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    Yep, I think you've got it.

    Just one other consideration...

    You may still want to use a try/catch at the lower levels to add additional information to the exception as it bubble higher in your system. For instance, the code that captures the picture to be saved and issues the command to save it to disk may be considered a "command".

    Perhaps you have several different commands and you want to treat errors that occur during their execution similarly. Then you might decide to add a try/catch at the low levels, within each command and wrap it in a "CommandException", which would be a custom exception specific to your application. By wrapping expected exceptions in this CommandException, your GUI can have a single try/catch looking for CommandExceptions. If this type is found, then you would display the error message to the user and abort the command, allowing the user to continue using your application in a different way. This is a great way to specify the specific types of errors you are willing to handle at low levels and inform the higher levels that something reasonably expected has occurred and can probably be continued from.

    Compare this to the case where you write a catch block for all IOExceptions at the GUI level and something occurs at a level other than writing bytes to disk, say with the display device driver. Now your GUI code might incorrectly assume the IOException means a problem with writing the disk when actually some code in between was the real culprit and your program should probably just exit.

    These are the typical ways I use exceptions:
    1. Only catch when you are going to try to recover or add additional information
    2. Throw anything you can't resolve completely so that information is not lost
    3. At the highest level of your application, log all unhandled exceptions and exit

    Note that #3 is actually handled by Unity for you, though not necessarily in an ideal way. Unity will log any unhandled exception for you automatically but will not shut down. Instead, it continues to run. I'm a little torn about this behavior because, on the one hand, it's nice to prevent stupid mistakes or error handling oversights from completely shutting down my game, but on the other hand, it leaves my application in as unknown state. Did that exception happen because my game is deeply diseased and needs to be restarted? Or was it just a stupid situation I didn't think to handle when I wrote the code and it can be safely ignored. The problem is that we simply don't know.