RAII Pattern
Spoiler: To avoid resource management problems (acquisition and release), the RAII pattern is a must known of object-oriented programming. To avoid problems (bugs and maintenance), and if your language allows it, initialize in constructors, release in destructors, and handle errors with exceptions.
If there is one problem that any developer encounters, it is resource management. The memory, the files, the network connections, the databases, the libraries to be initialized (openssl, opengl, …), the mutexes,… The list is long because indeed, any structure is a resource.
Anywhere in the code where we need a resource (its scope, which can be seen as a small universe bubble), the management of a resource comes down to the following three phases:
- Acquisition: Did you obtain a valid resource?
- Handling: does the resource remain valid?
- The release: what happens if you do not release the resource?
First (bad) Example
Let’s take a simple example of incrementing the value saved in a
file. The following code, in language C
, will perform the
necessary operations:
- open the file,
- get a mutex,
- read the previous value,
- write the new value,
- release the mutex and close the descriptor.
For simplicity, I consider that the functions that read and write values are provided elsewhere and we will not dwell on them.
// Read / Write functions
void read(int fd, int * value) ;
void write(int fd, int value) ;
// Ressource handling
void increment(const char * filename)
{
int fd = open(filename, O_RDWR | O_CREAT, 0666);
(fd, LOCK_EX) ;
flockint value ;
(fd, &value) ;
read(fd, value + 1) ;
write(fd) ;
close}
The problem in this version of the code is that no check is made on the validity of the resource (the file descriptor). What will happen if the file does not exist? If the mutex is not acquired? In the event of an error, the execution can exit the function without the file being closed…
You can believe me, it is always when you forget one of these checks that the case occurs. With luck, the quality department will tell you (and you can take your time to fix it), otherwise, it will be the support team because an unhappy customer has found a bug (and your time will be according to customer’s patience).
Second (better) Example
In this new version, still in language C
, we will
therefore carry out the necessary checks and be careful to free the
resource in the event of a problem. This time, the functions return an
error code (-1) when something goes wrong.
int read(int * fd, int * value) ;
int write(int * fd, int value) ;
int increment(const char * filename)
{
int fd = open(filename, O_RDWR | O_CREAT, 0666);
if (fd == -1) {
// Error while opening the file
return -1 ;
}
if (! flock(fd, LOCK_EX)) {
// Error while trying to acquire a lock
(fd) ;
fclosereturn -1 ;
}
int value ;
if (! read(fd, &value)) {
// Error while reading the file content
(fd) ;
fclosereturn -1 ;
}
if (! write(fd, value + 1)) {
// Error while writing the new value
(fd) ;
flosereturn -1 ;
}
if (! close(fd)) {
// Error while closing the file
return -1 ;
}
return 0 ;
}
What was clear is now complicated. From less than 10 lines, we go to about 30, the cyclomatic complexity is multiplied by 6. And from a resource management problem, we move to one of maintainability because for any change to the code will require checking the return codes, releasing the resource and returning an error code.
You can be sure someone will eventually change your code and forget a check or a release, it will be drama and hours of debugging ahead.
The solution : RAII
Demonstrating once again the superiority of object-oriented programming, the RAII pattern solves these management problems in a very elegant way.
RAII is a programming idiom that comes to us from C ++
and whose goal is to guarantee that an acquired resource is valid and
that its release will be automatically carried out when it is no longer
in range (return, stop, exception, …).
The concepts are indeed very simple and rather classic in OOP:
- encapsulate the resource in an object;
- manage the acquisition in the constructor;
- manage the release in the destructor;
- use exceptions to handle errors.
Example in C++
Improving the previous example, we are now going to encapsulate the
management of the file in a File
class which will handle
the opening in the constructor, the closing in the destructor and will
provide methods for the other operations. All with exceptions to make it
cleaner.
The following code, in C ++, is an example of a minimal implementation of such a class. We could improve this class, or use the
STD
classes orboost
classes instead but the goal is illustrative. To do things really well, we could even use templates to implement the mutex as a policy but that would be quite beyond the point.
class File
{
private:
int fd ;
// Make File not copyable
( const File& other );
File& operator=( const File& );
File
public:
(std::string const & filename)
File{
= open(filename.c_str(), O_RDWR | O_CREAT, 0666);
fd if (fd == -1) {
throw std::exception("Opening file failed") ;
}
}
~File()
{
(fd) ;
close// No check since one can not throw in destructor
}
void lock()
{
if (flock(fd, LOCK_EX) == -1) {
throw std::exception("Locking file failed") ;
}
}
int read() { ... }
void write(int value) { ... }
}
Thanks to this abstraction, the increment function becomes much simpler since it no longer needs to manage the various error cases but only to define a file and use its methods.
void increment(std::string const & filename)
{
(filename) ;
File f.lock() ;
f.write(f.read() + 1) ;
f}
Example in PHP
The implementation in PHP
is similar and differs only by
the syntax, the functions used are almost the same and the principle
therefore does not change.
The subtle difference is that where the
C ++
destroys the objects in a block when execution exits,PHP
uses a garbage collector which destroys the objects when they are no longer referenced (cf. the documentation on Constructors and Destructors). It is the same most of the time but in some subtle cases it can be a problem.
<?php
class File
{private $fd ;
public function __construct($filename)
{$this->fd = fopen($filename, "rw") ;
if ($this->fd === false) {
throw new Exception("Opening file failed") ;
}
}
public function __destruct()
{if (fclose($this->fd) === false) {
throw new Exception("Closing file failed") ;
}
}
public function lock()
{if (! flock($this->fd, LOCK_EX)) {
throw new Exception("Locking file failed") ;
}
}
public function read() { ... }
public function write($value) { ... }
}
function increment($filename)
{$f = new File($filename) ;
$f->lock() ;
$f->write($f->read() + 1) ;
}
Which languages supports RAII?
This technique works with all languages supporting exceptions AND
ensures that your destructors are called when execution leaves the scope
of your objects. This is the case with real object languages
such as C++
and PHP
, of which we have just
seen two examples.
For other languages, it’s a bit more complicated. They often provide
a finalize
method called on when freeing but since they
cannot guarantee that you will release your items as soon as you exit
blocks, it won’t help.
Some languages have chosen to cheat with a syntactic trick to fill this gap. With this particular syntax, you can define a method that will be called when the execution exits the corresponding particular block.
- python: proposes the with statement context manager,
- Java: since version 7 has a try-with-ressource,
- C#: defines the Dispose Pattern
Other languages have outright snubbed the principle and considered it
unnecessary. As in node.js
where you will have to define
your macros yourself and hope not to forget an error case.
Example (dangerous) of false RAII
The problem with these methods, apart from the syntactic inconsistency, is that they are not guaranteed by the class which provides the resource but by the one which uses it.
Applied to website access control, it’s like letting visitors decide for themselves whether or not they can see a page. As long as they are playing the game, everything is working fine.
The following example, in python
illustrates the
problem, the File
class provides the same kind of method as
before. The goodOne
function shows the use of
with
. The problem is that nothing forces you to use this
construct, as the evilOne
function shows.
class File:
def __init__(self, filename):
self.fd = open(filename, "rw")
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.fd.close()
def lock(self):
self.fd, LOCK_EX)
fcntl.flock(
def read(self):
# ...
def write(self, value):
# ...
def goodOne(filename):
with File(filename) as f:
+ 1)
f.write(f.read()
def evilOne(filename):
= File(filename)
f + 1) f.write(f.read()
So yes, you can answer me that we could delegate the opening of the
file descriptor in the __enter__
function and add
exceptions in the other methods if the descriptor is not available and
therefore force the use of with
:
class File:
def __init__(self, filename):
self.filename = filename
self.fd = None
def __enter__(self):
self.fd = open(filename, "rw")
return self
def __exit__(self, type, value, traceback):
self.check()
self.fd.close()
def check(self):
if (self.fd is None):
raise Exception("File have not been correctly created")
def lock(self):
self.check()
self.fd, LOCK_EX)
fcntl.flock(
def read(self):
self.check()
# ...
def write(self, value):
self.check()
# ...
But this solution is far from being as practical as a true RAII:
- it complicates the
File
class by imposing a systematiccheck()
call. Let one be forgotten or deleted and your will gain hours of debugging, - it only detects forgetting the
with
at runtime, so unless you have 100% code coverage by automatic testing, it will happen in production…
In short, you will understand, it remains only DIY to imitate the pros.
And next
If your languages allow the use of this technique, I can only advise you to abuse it because it allows both to simplify the business codes but above all it makes them more secure by avoiding the problems of error management and release.