-
Notifications
You must be signed in to change notification settings - Fork 0
14 RAII
Details of where, how and points of special interest relating to RAII are supplied here.
RAII is a jargon term for a technique that provides "scoped pairing" of operations. Generally you use a RAII object's destructor to ensure that an operation performed at construction time, is balanced by reversing the effect in the destructor. That way declaring a RAII object in block scope will perform an operation (e.g. locking a mutex), and a corresponding operation (unlocking the mutex) is guaranteed to follow.
What's cool is that as direct consequence of this, objects going out of scope either at end of execution block or by exception throwing just clean themselves up, without any need for additional code to manually track and hard code a special defence.
You will find this guidance applied universally in App3Dev. However pay particular attention to the helper class RAII_cd_physical_lock called from ripper.hpp. Somehow (for me at least) restoring a system device mechanical state on all return paths helped me visualize the RAII concept better and put my trust in it... If only that were justified! There is a "gotcha" here.
RAII is limited by needing an active program thread to unwind destructors. BUT when a process is signalled to stop, its threads are abandoned and give up their resources in a rather more abrupt fashion. This means that RAII can't always be relied upon to restore system state. In this case the need to unlock the cdrom tray door, recover the cd, continue full service on this PC, is critical. An RAII approach simplifies unlock, but fails to address all of the program paths. A SIGNAL handler is also needed here, and I've added one accordingly. Let this be a warning about RAII limitations.
The clue is in the name. RAII isn't meant to manage system state - the concept was originally about resource (de-)allocation. That being said, it has since come to be more generally used as an approach to do the "right thing" in all program paths, recognizing that exceptions might be thrown, either now or in a later revision of code. The example RAII_preserve_last_error that you can find in the file utf8_convert.cpp shows how the RAII pattern can be applied as an elegant response to a wider set of problems.
To add a final twist to our story, RAII_cd_exclusive_access_lock is also used to claim exclusicve access to the physical cd rom device underlying the cd filesystem while a rip is in progress, but why is there no corresponding release in the signal handler? The reason is that the system (Windows) deals with this automatically when a process releases the lock, or closes the corresponding device handle (either in a controlled way, or when the process terminates). So unlike the cd rom tray door lock, all bases are covered in the case of exclusive access locks. That may be inconsistent, but it is the documented behaviour.
In this code, the most vexing open issue that cannot be managed with this approach, is program ab-end during debug sessions. Here it is perfectly possible that (if the program breaks with the CD-ROM/DVD tray locked) a reboot could be required to unlock it.
For full disclosure, the signal handler provided in business logic probably addresses a (windows) platform specific behavioural limitation, and in strict interpretation of the io-control used to unlock the drive door, it only works because the signal handler is invoked in the context of the same host process that performed the corresponding lock. This is a happenstance that I have yet to find a guarantee for, and is the kind of detail that could affect (say) porting to Linux, or even break with a future version of Windows.
As a post script, as of Windows 11 we appear to be in that broken scenario. I leave it as an exercise to readers to seek an elegant solution to this. My own ideas would make this code more complex and therefore less effective as a training tool.