From 3669894446ad9a1a44290f06cb0ec73253e116f1 Mon Sep 17 00:00:00 2001
From: Hadrien Grasland <grasland@lal.in2p3.fr>
Date: Thu, 24 Jan 2019 18:53:06 +0100
Subject: [PATCH] Basic multi-slot backend

---
 .../src/Conditions/ConditionBackendSvc.cpp    | 46 +++++++++++++++++--
 .../src/Conditions/ConditionBackendSvc.h      | 32 +++++++++++--
 .../src/Conditions/ConditionContext.cpp       | 16 ++++++-
 .../src/Conditions/ConditionContext.h         | 23 ++++++----
 4 files changed, 97 insertions(+), 20 deletions(-)

diff --git a/GaudiExamples/src/Conditions/ConditionBackendSvc.cpp b/GaudiExamples/src/Conditions/ConditionBackendSvc.cpp
index 316270189..c9e7d32f1 100644
--- a/GaudiExamples/src/Conditions/ConditionBackendSvc.cpp
+++ b/GaudiExamples/src/Conditions/ConditionBackendSvc.cpp
@@ -1,3 +1,4 @@
+#include <algorithm>
 #include <stdexcept>
 #include <unordered_map>
 
@@ -7,16 +8,33 @@ namespace DemoBackend
 {
   namespace detail
   {
+    StatusCode ConditionBackendSvc::initialize()
+    {
+      auto status = Service::start();
+      if ( status.isFailure() ) return status;
+
+      m_slotUsage.reserve( m_numSlots );
+      for ( size_t i = 0; i < m_numSlots; ++i ) {
+        m_slotUsage.push_back( false );
+      }
+
+      return status;
+    }
+
     void ConditionBackendSvc::registerCondition( Gaudi::ConditionAccessorBase& accessor )
     {
       if ( m_keysFrozen ) {
         throw std::runtime_error( "Attempted to register a condition input after start()" );
       }
+
       if ( accessor.type() != typeid( float ) ) {
         throw std::runtime_error( "This dummy condition back-end only supports float conditions" );
       }
-      auto res = m_data.emplace( accessor.key(), std::numeric_limits<float>::infinity() );
-      const float* dataPtr = &( res.first->second );
+
+      std::vector<float> slots( m_numSlots, std::numeric_limits<float>::infinity() );
+      auto res = m_data.emplace( accessor.key(), std::move( slots ) );
+      const std::vector<float>* dataPtr = &( res.first->second );
+
       auto id = reinterpret_cast<Gaudi::ConditionID>( dataPtr );
       accessor.setID( id );
     }
@@ -34,11 +52,17 @@ namespace DemoBackend
       if ( !m_keysFrozen ) {
         throw std::runtime_error( "Attempted to access a condition before start()" );
       }
-      std::unique_lock<std::mutex> dataLock( m_dataMutex );
+      size_t slotIdx = selectSlot( eventTime );
       for ( auto& keyVal : m_data ) {
-        keyVal.second = 4.2 * eventTime * keyVal.first.size();
+        keyVal.second[slotIdx] = 4.2 * eventTime * keyVal.first.size();
       }
-      return ConditionContext{ std::move( dataLock ) };
+      return ConditionContext{ *this, slotIdx };
+    }
+
+    void ConditionBackendSvc::releaseSlot( size_t slotIdx )
+    {
+      m_slotUsage[slotIdx] = false;
+      m_slotReady.notify_one();
     }
 
     const DataObjID& ConditionBackendSvc::contextPath() const
@@ -46,6 +70,18 @@ namespace DemoBackend
       return m_contextPath;
     }
 
+    size_t ConditionBackendSvc::selectSlot( const EventTimestamp& /* eventTime */ )
+    {
+      std::unique_lock<std::mutex> slotLock{ m_slotMutex };
+      auto slotIt = m_slotUsage.end();
+      m_slotReady.wait( slotLock, [&]{
+        slotIt = std::find( m_slotUsage.begin(), m_slotUsage.end(), false );
+        return slotIt != m_slotUsage.end();
+      } );
+      *slotIt = true;
+      return std::distance( m_slotUsage.begin(), slotIt );
+    }
+
     DECLARE_COMPONENT( ConditionBackendSvc )
   }
 }
diff --git a/GaudiExamples/src/Conditions/ConditionBackendSvc.h b/GaudiExamples/src/Conditions/ConditionBackendSvc.h
index 9aca0bb10..24c3e44b8 100644
--- a/GaudiExamples/src/Conditions/ConditionBackendSvc.h
+++ b/GaudiExamples/src/Conditions/ConditionBackendSvc.h
@@ -1,7 +1,10 @@
 #pragma once
 
+#include <condition_variable>
+#include <mutex>
 #include <typeinfo>
-#include <unordered_set>
+#include <unordered_map>
+#include <vector>
 
 #include "GaudiKernel/ConditionAccessorBase.h"
 #include "GaudiKernel/ConditionKey.h"
@@ -36,6 +39,9 @@ namespace DemoBackend
       // Inherited Service constructor
       using Service::Service;
 
+      // Need to do some bookkeeping once amount of condition slots is known
+      StatusCode initialize() override;
+
       // Notify the storage back-end that we will be accessing a condition
       // Type info is provided to be able to type-check the condition dataflow
       void registerCondition( Gaudi::ConditionAccessorBase& );
@@ -48,6 +54,9 @@ namespace DemoBackend
       // derivation or gives access to previously prepared conditions.
       ConditionContext setupEvent( const EventTimestamp& );
 
+      // ConditionContext implementation detail for releasing a storage slot
+      void releaseSlot( size_t slotIdx );
+
       // TES location at which the condition context should be inserted
       const DataObjID& contextPath() const;
 
@@ -56,12 +65,27 @@ namespace DemoBackend
       Gaudi::Property<DataObjID> m_contextPath{
         this, "ContextPath", "/Event/DemoBackendCondCtx", "Path to condition context in TES" };
 
+      // Number of condition storage slots to be used
+      Gaudi::Property<size_t> m_numSlots{
+        this, "StorageSlots", 4, "Number of condition storage slots to use" };
+
       // Flag indicating when the above set can be considered complete
       bool m_keysFrozen = false;
 
-      // Condition storage
-      std::mutex m_dataMutex;
-      std::unordered_map<Gaudi::ConditionKey, float> m_data;
+      // Slots are shared mutable state, so access to them must be synchronized
+      std::mutex m_slotMutex;
+      std::condition_variable m_slotReady;
+
+      // One entry per storage slot, indicates if the slot is used
+      // FIXME: Use LRU eviction policy instead of first-fit
+      std::vector<bool> m_slotUsage;
+
+      // Condition data sorted by condition, one entry per storage slot
+      // FIXME: Have a single array of conditions to reduce allocation & memory spread
+      std::unordered_map<Gaudi::ConditionKey, std::vector<float>> m_data;
+
+      // Select a storage slot for an incoming event
+      size_t selectSlot( const EventTimestamp& );
     };
   }
 }
\ No newline at end of file
diff --git a/GaudiExamples/src/Conditions/ConditionContext.cpp b/GaudiExamples/src/Conditions/ConditionContext.cpp
index 0c55574ad..64af4aedf 100644
--- a/GaudiExamples/src/Conditions/ConditionContext.cpp
+++ b/GaudiExamples/src/Conditions/ConditionContext.cpp
@@ -1,8 +1,20 @@
+#include "ConditionBackendSvc.h"
 #include "ConditionContext.h"
 
 namespace DemoBackend
 {
-  ConditionContext::ConditionContext( std::unique_lock<std::mutex>&& dataLock )
-    : m_dataLock{ std::move( dataLock ) }
+  ConditionContext::~ConditionContext()
+  {
+    if ( m_backendSvc ) m_backendSvc->releaseSlot( m_slotIdx );
+  }
+
+  ConditionContext::ConditionContext( ConditionContext&& other )
+    : m_backendSvc{ std::exchange( other.m_backendSvc, nullptr ) }
+    , m_slotIdx{ other.m_slotIdx }
+  {}
+
+  ConditionContext::ConditionContext( detail::ConditionBackendSvc& backendSvc, size_t slotIdx )
+    : m_backendSvc{ &backendSvc }
+    , m_slotIdx{ slotIdx }
   {}
 }
\ No newline at end of file
diff --git a/GaudiExamples/src/Conditions/ConditionContext.h b/GaudiExamples/src/Conditions/ConditionContext.h
index 4a3249df1..a174276fe 100644
--- a/GaudiExamples/src/Conditions/ConditionContext.h
+++ b/GaudiExamples/src/Conditions/ConditionContext.h
@@ -3,6 +3,7 @@
 #include <mutex>
 #include <stdexcept>
 #include <typeinfo>
+#include <vector>
 
 #include "GaudiKernel/DataObject.h"
 #include "GaudiKernel/ConditionID.h"
@@ -24,12 +25,13 @@ namespace DemoBackend
   {
   public:
     // Upon going out of scope the ConditionContext will mark the conditions as unused
-    ~ConditionContext() = default;
+    ~ConditionContext();
 
-    // The ConditionContext is movable but not copyable
-    ConditionContext( ConditionContext&& ) = default;
+    // The ConditionContext should be neither moved nor copied, but we can't
+    // prevent you from moving it until C++17 is the norm
+    ConditionContext( ConditionContext&& );
     ConditionContext( const ConditionContext& ) = delete;
-    ConditionContext& operator=( ConditionContext&& ) = default;
+    ConditionContext& operator=( ConditionContext&& ) = delete;
     ConditionContext& operator=( const ConditionContext& ) = delete;
 
   private:
@@ -44,8 +46,8 @@ namespace DemoBackend
       if ( typeid( ConditionType ) != typeid( float ) ) {
         throw std::runtime_error( "This silly backend only supports float conditions" );
       }
-      auto dataPtr = reinterpret_cast<const float*>( id );
-      return *dataPtr;
+      auto dataPtr = reinterpret_cast<const std::vector<float>*>( id );
+      return (*dataPtr)[m_slotIdx];
     }
 
   private:
@@ -53,10 +55,13 @@ namespace DemoBackend
     friend class detail::ConditionBackendSvc;
 
     // Construct a ConditionContext from backend-internal data
-    ConditionContext( std::unique_lock<std::mutex>&& dataLock );
+    ConditionContext( detail::ConditionBackendSvc& backend, size_t slotIdx );
 
   private:
-    // This silly implementation is actually locking
-    std::unique_lock<std::mutex> m_dataLock;
+    // Must tell the ConditionBackendSvc to release our slot when destroyed
+    detail::ConditionBackendSvc* m_backendSvc;
+
+    // This is the condition slot index that we shall be targeting
+    size_t m_slotIdx;
   };
 }
\ No newline at end of file
-- 
GitLab