diff Framework/PostgreSQL/PostgreSQLDatabase.cpp @ 134:cc3dc759c989

Added an advisory lock to avoid race conditions during database setup
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 08 May 2019 20:21:13 +0200
parents 5765cc5fd268
children 4cd7e45b671e
line wrap: on
line diff
--- a/Framework/PostgreSQL/PostgreSQLDatabase.cpp	Fri Mar 01 12:56:13 2019 +0100
+++ b/Framework/PostgreSQL/PostgreSQLDatabase.cpp	Wed May 08 20:21:13 2019 +0200
@@ -31,6 +31,7 @@
 #include <Core/OrthancException.h>
 
 #include <boost/lexical_cast.hpp>
+#include <boost/thread.hpp>
 
 
 namespace OrthancDatabases
@@ -109,23 +110,47 @@
   }
 
 
-  void PostgreSQLDatabase::AdvisoryLock(int32_t lock)
+  bool PostgreSQLDatabase::RunAdvisoryLockStatement(const std::string& statement)
   {
     PostgreSQLTransaction transaction(*this);
 
-    Query query("select pg_try_advisory_lock(" + 
-                boost::lexical_cast<std::string>(lock) + ");", false);
+    Query query(statement, false);
     PostgreSQLStatement s(*this, query);
 
     PostgreSQLResult result(s);
-    if (result.IsDone() ||
-        !result.GetBoolean(0))
+
+    bool success = (!result.IsDone() &&
+                    result.GetBoolean(0));
+
+    transaction.Commit();
+
+    return success;
+  }
+  
+
+  bool PostgreSQLDatabase::AcquireAdvisoryLock(int32_t lock)
+  {
+    return RunAdvisoryLockStatement(
+      "select pg_try_advisory_lock(" + 
+      boost::lexical_cast<std::string>(lock) + ")");
+  }
+
+
+  bool PostgreSQLDatabase::ReleaseAdvisoryLock(int32_t lock)
+  {
+    return RunAdvisoryLockStatement(
+      "select pg_advisory_unlock(" + 
+      boost::lexical_cast<std::string>(lock) + ")");
+  }
+
+
+  void PostgreSQLDatabase::AdvisoryLock(int32_t lock)
+  {
+    if (!AcquireAdvisoryLock(lock))
     {
       LOG(ERROR) << "The PostgreSQL database is locked by another instance of Orthanc";
       throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
     }
-
-    transaction.Commit();
   }
 
 
@@ -243,4 +268,39 @@
       return new PostgreSQLTransaction(*this);
     }
   }
+
+
+  PostgreSQLDatabase::TransientAdvisoryLock::TransientAdvisoryLock(
+    PostgreSQLDatabase&  database,
+    int32_t lock) :
+    database_(database),
+    lock_(lock)
+  {
+    bool locked = true;
+    
+    for (unsigned int i = 0; i < 10; i++)
+    {
+      if (database_.AcquireAdvisoryLock(lock_))
+      {
+        locked = false;
+        break;
+      }
+      else
+      {
+        boost::this_thread::sleep(boost::posix_time::milliseconds(500));
+      }
+    }
+
+    if (locked)
+    {
+      LOG(ERROR) << "Cannot acquire a transient advisory lock";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+    }    
+  }
+    
+
+  PostgreSQLDatabase::TransientAdvisoryLock::~TransientAdvisoryLock()
+  {
+    database_.ReleaseAdvisoryLock(lock_);
+  }
 }