Resource Ownership

From cxwiki

The cxsource framework does not follow a strict C++ RAII ownership model. RAII is broadly encouraged but is not mandatory. Various classes are available for tracking resource ownership, and should be used as the use-case demands (rather than attempting a one-size-fits all solution).
CXAutoReference is used where an object is to be used without clear ownership or object lifetime. While this takes care of ensuring that the object is not deleted prematurely, it is typical that one system will still act as an object's "owner" and that any remaining references will be released over time once that system releases its reference.
  • If the object is stateful, care must be taken to understand which system owns the state at any given time. A filestream might be an example of an unsuitable use-case for a CXAutoReference, because the only way to use a filestream is by changing its state. If two separate systems are attempting to read from the same filestream, the result will inevitably be corrupted.
  • A better example would be an object which is loaded at construction, or lazy loaded, and then remains largely immutable. A shared data cache which underlies a given file stream might reasonably be refererred to via CXAutoReference.
  • Similarly, read-write objects which provide some form of access locking could safely be used with CXAutoReference.

Care must be taken not to form an unbreakable reference cycle. If your use-case must permit reference cycles, then you must also provide application logic to detect and/or break these cyles as required. The CXAutoReference system cannot automatically detect or break cycles.

Care should be taken when an object managed by CXAutoReference has exclusive access of some resource (such as a mutex, a file handle, or a network socket). Since releasing the CXAutoReference does not guarantee that all other users have released it, it also does not guarantee that the resource is now free for reuse. For example, consider the following problem:

  • A cache is created (managed via CXAutoReference) which takes exclusive access to a file.
  • Some actions are performed via the cache, potentially passing references to the cache to other systems or other threads.
  • The cache is then released.
  • At this point, it might be expected that the file is ready for reuse (either by opening a new cache instance, or by running a backup function or etc.)
  • In practice, it's feasible that the cache object is still being held open by other systems or threads, meaning that the file is still in exclusive use. Attempts to reuse the file may fail.

There are a few solutions to this kind of problem:

  1. If the only use-case for the file is via the cache, then ensuring that it is always possible to re-attach to the cache is a simple solution. Keeping a CXSafePointer (or simliar) to the cache allows code to re-attach to the existing cache object if it still exists. Care must be taken to avoid race conditions if threading is involved, but this approach can certainly be made to work.
  2. If you absolutely must close the cache off and use the file in some other manner, then having a method on the cache object which causes it to detach from the file can work. This doesn't delete the cache object, and other systems/threads may continue to make calls to the cache, but they will no longer be able to access through to the file itself. This might cause methods on the cache to return errors to their callers, for example.
  3. Delay the necessary file-manipulation until the cache is deleted. For example, the cache object may offer some method to queue a command which is executed immediately after the file is closed by the cache.
  4. Structure your application logic such that all users of the cache release their references before you attempt to manipulate the file. This guarantees that the cache is deleted. This requires that you have a very clear chain of ownership so that you know which systems need to finish up.
CXSafePointer is used where a system wishes to safely refer to an object, but does not make any ownership claims. This acts similarly to a C pointer, but automatically becomes nullptr if the target object is deleted. This is also known as a "weak reference".