In a managed wrapper over a native library, I have to accomplish certain operations which to the user of the high level objects should be considered atomic and consistent. However, the underlying operations in native code are atomic and consistent individually, but not as a whole.
// Simplistic look at the AddRange operation
void AddRange(IEnumerable<ChildType> range)
{
foreach (var value in range)
{
this.NativeAdd(value);
}
}
// Simplistic look at the Delete operation
void Delete(ParentType value)
{
foreach (var child in value.Children)
{
this.NativeDelete(child);
}
this.NativeDelete(value);
}
All of the Native operations fail with a common exception type if the Native code relates there has been an error:
void NativeDelete(ChildType child)
{
StatusCode status = StatusCode.NoError;
NativeMethods.DeleteChild(this.id, child.Id, out status);
if (status != StatusCode.NoError)
{
throw new LibraryException(this, child, status);
}
}
In the high-level AddRange and Delete routines I run into the situation where some of either the native Add or Delete calls have completed, but an error occurs in one of them and the rest do not complete.
UPDATE: The actual file on disk, to a user, will not look any different if they added 7 items and it failed on the 7th item, or if they added 6 items successfully. The only time the error is recorded is during runtime. Therefore, if the user is not made aware of the possibility of an inconsistent state, they may not be able to decide if the underlying file is valid.
Should I:
- Let the LibraryException through and provide documentation to the user that the ParentType or ChildType object may be in an inconsistent state
- Wrap the LibraryException in an InconsistentStateException containing information about which objects may be in an inconsistent state
- Catch the LibraryException and attempt to "hand" rollback the changes made, before rethrowing the exception for the user (not a fan of this)
- Something else?