From af7d6735b1266d34fde2b8d8c6ae678c531200e3 Mon Sep 17 00:00:00 2001
From: Eric Cano <Eric.Cano@cern.ch>
Date: Tue, 9 May 2017 10:13:53 +0200
Subject: [PATCH] Homogenized the behaviors of object store backends.

BackendVFS now throws the same exceptions as BackendRados.
The exceptions of the user provided update callback are now passed through.
A unit test was added.
---
 objectstore/BackendRados.cpp | 10 ++--------
 objectstore/BackendTest.cpp  | 33 +++++++++++++++++++++++++++++++++
 objectstore/BackendVFS.cpp   | 33 +++++++++++++++++++++++++++++----
 3 files changed, 64 insertions(+), 12 deletions(-)

diff --git a/objectstore/BackendRados.cpp b/objectstore/BackendRados.cpp
index afaf15e10e..a786a69e52 100644
--- a/objectstore/BackendRados.cpp
+++ b/objectstore/BackendRados.cpp
@@ -288,14 +288,8 @@ void BackendRados::AsyncUpdater::fetchCallback(librados::completion_t completion
                   std::string("In In BackendRados::AsyncUpdater::fetchCallback::update_lambda(): failed to read buffer: ")
                   + ex.what());
             }
-            try {
-              // Execute the user's callback.
-              value=au.m_update(value);
-            } catch (std::exception & ex) {
-              throw CouldNotUpdateValue(
-                  std::string("In In BackendRados::AsyncUpdater::fetchCallback::update_lambda(): failed to call update(): ")
-                  + ex.what());
-            }
+            // Execute the user's callback. Let exceptions fly through. User knows his own exceptions.
+            value=au.m_update(value);
             try {
               // Prepare result in buffer list.
               au.m_radosBufferList.clear();
diff --git a/objectstore/BackendTest.cpp b/objectstore/BackendTest.cpp
index d62d6b2417..55f72fd6b6 100644
--- a/objectstore/BackendTest.cpp
+++ b/objectstore/BackendTest.cpp
@@ -100,6 +100,39 @@ TEST_P(BackendAbstractTest, AsyncIOInterface) {
   m_os->remove(testObjectName);
 }
 
+TEST_P(BackendAbstractTest, AsyncIOInterfaceMultithread) {
+  // Create object to update.
+  const std::string testValue = "1234";
+  const std::string testSecondValue = "12345";
+  const std::string testObjectNameRadix = "testObject";
+  std::function<std::string(size_t)> testObjectName=[&](size_t i){
+    std::stringstream tom;
+    tom << testObjectNameRadix << i;
+    return tom.str();
+  };
+  std::function<std::string(size_t)> value=[&](size_t i){
+    std::stringstream val;
+    val << testSecondValue << i;
+    return val.str();
+  };
+  for (size_t i=0; i<10; i++) { try {m_os->remove(testObjectName(i));}catch(...){} }
+  std::list<std::unique_ptr<cta::objectstore::Backend::AsyncUpdater>> updaters;
+  std::list<std::function<std::string(const std::string &)>> lambdas;
+  for (size_t i=0; i<10; i++) {
+    m_os->create(testObjectName(i), testValue);
+    // Launch update of object via asynchronous IO
+    lambdas.emplace_back([i,&value](const std::string &s)->std::string{return value(i);});
+    updaters.emplace_back(m_os->asyncUpdate(testObjectName(i),lambdas.back()));
+  }
+  size_t i=0;
+  for (auto & u: updaters) {
+    u->wait();
+    ASSERT_EQ(value(i), m_os->read(testObjectName(i)));
+    m_os->remove(testObjectName(i));
+    i++;
+  }
+}
+
 TEST_P(BackendAbstractTest, ParametersInterface) {
   //std::cout << "Type=" << m_os->typeName() << std::endl;
   std::unique_ptr<cta::objectstore::Backend::Parameters> params(
diff --git a/objectstore/BackendVFS.cpp b/objectstore/BackendVFS.cpp
index a6651adbf4..6fe9df874d 100644
--- a/objectstore/BackendVFS.cpp
+++ b/objectstore/BackendVFS.cpp
@@ -203,8 +203,6 @@ std::list<std::string> BackendVFS::list() {
   return ret;
 }
 
-
-
 BackendVFS::Parameters* BackendVFS::getParams() {
   std::unique_ptr<Parameters> ret(new Parameters);
   ret->m_path = m_root;
@@ -236,10 +234,13 @@ BackendVFS::ScopedLock * BackendVFS::lockHelper(std::string name, int type) {
     int openErrno = errno;
     struct ::stat sBuff;
     int statResult = ::stat((m_root + name).c_str(), &sBuff);
+    int statErrno = errno;
     if (ENOENT == openErrno && !statResult) {
       ret->set(::open(path.c_str(), O_RDONLY | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH), path);
       exception::Errnum::throwOnMinusOne(ret->m_fd, "In BackendVFS::lockHelper(): Failed to recreate missing lock file");
     } else {
+      if (statErrno == ENOENT)
+        throw Backend::NoSuchObject("In BackendVFS::lockHelper(): no such file");
       const std::string errnoStr = utils::errnoToString(errno);
       exception::Exception ex;
       ex.getMessage() << "In BackendVFS::lockHelper(): Failed to open file " << path <<
@@ -289,8 +290,32 @@ BackendVFS::AsyncUpdater::AsyncUpdater(BackendVFS & be, const std::string& name,
   m_backend(be), m_name(name), m_update(update),
   m_job(std::async(std::launch::async, 
     [&](){
-      std::unique_ptr<ScopedLock> sl(m_backend.lockExclusive(m_name));
-      m_backend.atomicOverwrite(m_name, m_update(m_backend.read(m_name)));
+      std::unique_ptr<ScopedLock> sl;
+      try { // locking already throws proper exceptions for no such file.
+        sl.reset(m_backend.lockExclusive(m_name));
+      } catch (Backend::NoSuchObject &) {
+        throw;
+      } catch (cta::exception::Exception & ex) {
+        throw Backend::CouldNotLock(ex.getMessageValue());
+      }
+      std::string preUpdateData;
+      try {
+        preUpdateData=m_backend.read(m_name);
+      } catch (cta::exception::Exception & ex) {
+        throw Backend::CouldNotFetch(ex.getMessageValue());
+      }
+      // Let user's exceptions go through.
+      std::string postUpdateData=m_update(preUpdateData);
+      try {
+        m_backend.atomicOverwrite(m_name, postUpdateData);
+      } catch (cta::exception::Exception & ex) {
+        throw Backend::CouldNotCommit(ex.getMessageValue());
+      }
+      try {
+        sl->release();
+      } catch (cta::exception::Exception & ex) {
+        throw Backend::CouldNotUnlock(ex.getMessageValue());
+      }
     })) 
 {}
 
-- 
GitLab