samedi 21 octobre 2017

Best design pattern for managing asymmetrical resource use

I wanted to canvas some opinions on the best design pattern for working with managed resources, where two distinct resources are involved but you need to release them in the opposite order to that which they were acquired.

First, let me set the scene. We are working with two types of objects Documents, and Collections of Documents. A Collection of Documents literally contains references to the Documents and some metadata per-document.

Originally we had a symmetrical pattern which flowed like:

  1. Lock Collection
  2. Do useful stuff with Collection
  3. Lock Document
  4. Do useful stuff with Collection and Document
  5. Unlock Document
  6. Unlock Collection

and in code was represented like:

Collection col = null;
try {
    col = getCollection("col1 name", LockMode.WRITE_LOCK);

    // Here we do any operations that only require the Collection

    Document doc = null;
    try {
        doc = col.getDocument("doc1 name", LockMode.WRITE_LOCK);

        // Here we do some operations on the document (of the Collection)

    } finally {
        if (doc != null) {
            doc.close();
        }
    }

} finally {
    if (col != null) {
        col.close();
    }
}

Now that we have try-with-resources since Java 7, we have improved this like so:

try (final Collection col = getCollection("col1 name", LockMode.WRITE_LOCK)) {

    // Here we do any operations that only require the Collection

    try (final Document doc = col.getDocument("doc1 name", LockMode.WRITE_LOCK)) {

        // Here we do some operations on the document (of the Collection)

    }

}

The problem we have is that keeping the Collection locked whilst we perform operations on the document is inefficient, as other threads have to wait, and often the operations on the document don't require modifying the Collection.

So we would like to move to an asymmetrical pattern which allows us to release the Collection as soon as possible. The flow should be like:

  1. Lock Collection
  2. Do useful stuff with Collection
  3. Lock Document
  4. Do anything that requires both Collection and Document (rare)
  5. Unlock Collection
  6. Do useful stuff with Document
  7. Unlock Document

I am wondering about the best pattern for implementing this asymmetrical approach in code. This could obviously be done with try/finally etc like so:

Collection col = null;
Document doc = null;
try {
    col = getCollection("col1 name", LockMode.WRITE_LOCK);

    // Here we do any operations that only require the Collection
    try {
        doc = col.getDocument("doc1 name", LockMode.WRITE_LOCK);

        // Here we do any operations that require both the Collection and Document (rare).

    } finally {
        if (col != null) {
        col.close();
    }

    // Here we do some operations on the document (of the Collection)

} finally {
    if (doc != null) {
            doc.close();
        }
    }
}

I can also think of a try-with-resources scheme where we exchange the resource release order but I am wondering if that makes reading the code less understandable. For example:

try (final ManagedRelease<Collection> mcol =
        new ManagedRelease<>(getCollection("col1 name", LockMode.WRITE_LOCK))) {

    // Here we do any operations that only require the Collection

    try (final ManagedRelease<Document> mdoc =
            mcol.withAsymetrical(mcol.resource.getDocument("doc1 name", LockMode.WRITE_LOCK))) {

        // Here we do any operations that require both the Collection and Document (rare).

    }  // NOTE: Collection is released here

    // Here we do some operations on the document (of the Collection)

}  // NOTE: Document is released here

I would welcome opinions on whether the try-with-resources approach to asymmetrical resource management is a sensible one or not, and also any pointers to other patterns that might be more appropriate.

Aucun commentaire:

Enregistrer un commentaire