Sunday, February 6, 2011

Notes on safe use of StorageDevice in XNA

I recently found a bug in StorageTasks.fs, an exception that I did not expect can be thrown in some situations. I am therefore going over the code and checking if I'm handling all exceptions correctly.

The bug I just fixed was an uncaught InvalidOperationException when attempting to open a StorageContainer after pulling the memory unit (which was picked as the storage device). The method that threw that exception was StorageDevice.EndOpenContainer. Sadly, the msdn documentation does not mention that.

I thought I would use EasyStorage as a reference. Unfortunately, even EasyStorage isn't 100% correct. For instance, see SaveDevice.OpenContainer: It does not catch InvalidOperationException. Although unlikely, this can happen if the user disconnects the storage device after BeginOpenContainer and before EndOpenContainer.

Anyway, I'll just go on and rely on testing to get it right... In the mean time, here are my findings regarding exceptions thrown by the StorageDevice API in XNA.

StorageDevice.BeginShowSelector
  • GuideAlreadyVisibleException
StorageDevice.EndShowSelector
  • None known
StorageDevice.BeginOpenContainer
  • InvalidOperationException (from msdn)
  • ArgumentNullException (from msdn)
  • StorageDeviceNotConnectedException
StorageDevice.EndOpenContainer
  • InvalidOperationException
  • StorageDeviceNotConnectedException
StorageContainer.OpenFile
  • StorageDeviceNotConnectedException
  • FileNotFoundException
StorageContainer.CreateFile
  • StorageDeviceNotConnectedException
  • Possibly other exceptions, e.g. if no space is left on the device?
I have listed StorageDeviceNotConnectedException under most methods. Although it's never mentioned explicitly on msdn in the method documentation, it seems reasonable to expect it in any situation where the device might be accessed.

There are other failure scenarios associated to Stream and serialization which I'm not listing here. In particular, XML de-serialization of F# types (discriminated unions, records, tuples, lists...) will fail at run-time due to these types being immutable and lacking a default constructor.