From dd8692d4604ccf2e565c3432b792b96e6f1e028f Mon Sep 17 00:00:00 2001
From: Joao Afonso <joao.afonso@cern.ch>
Date: Wed, 25 Jan 2023 17:42:32 +0100
Subject: [PATCH] Resolve "Add a timeout to tape server global lock on the
 object store"

---
 ReleaseNotes.md                                    |  1 +
 catalogue/DriveConfig.cpp                          |  1 +
 cmdline/CtaAdminCmd.cpp                            | 10 ++++++++++
 cmdline/CtaAdminCmd.hpp                            |  2 +-
 cmdline/CtaAdminTextFormatter.cpp                  |  8 +++++---
 cmdline/CtaAdminTextFormatter.hpp                  |  8 ++++++++
 cmdline/cta-cli.conf.example                       |  3 +++
 common/exception/Exception.hpp                     |  8 ++++++++
 objectstore/BackendRados.cpp                       |  4 ++--
 objectstore/BackendVFS.cpp                         |  2 +-
 scheduler/OStoreDB/OStoreDB.cpp                    | 12 ++++++++++--
 scheduler/OStoreDB/OStoreDB.hpp                    |  1 +
 scheduler/PostgresSchedDB/PostgresSchedDB.cpp      |  5 +++++
 scheduler/PostgresSchedDB/PostgresSchedDB.hpp      |  1 +
 scheduler/Scheduler.cpp                            |  2 +-
 scheduler/Scheduler.hpp                            |  4 +++-
 scheduler/SchedulerDatabase.hpp                    |  1 +
 scheduler/SchedulerDatabaseFactory.hpp             |  4 ++++
 .../tape/tapeserver/daemon/DataTransferConfig.hpp  |  5 +++++
 .../tape/tapeserver/daemon/DataTransferSession.cpp | 14 +++++++++++---
 tapeserver/daemon/DriveHandler.cpp                 |  1 +
 tapeserver/daemon/TapedConfiguration.cpp           |  2 ++
 tapeserver/daemon/TapedConfiguration.hpp           |  3 +++
 23 files changed, 88 insertions(+), 14 deletions(-)

diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index afaa317347..69dcf505c2 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -20,6 +20,7 @@
 - cta/CTA#234 - Replace stoi with toUint64 in standalone cli tool
 - cta/CTA#238 - Compilation fails when using cta::common::Configuration::getConfEntInt(...)
 - cta/CTA#273 - Fix tape state change command idempotency when resetting REPACKING/BROKEN/PENDING
+- cta/CTA#280 - Add a timeout to tape server global lock on the object store
 ### Continuous Integration
 - cta/CTA#205 - Updating EOS4/EOS4 in versionlock for v4.8.95/v5.1.5
 - cta/CTA#253 - Allow Failure for cta_valgrind tests
diff --git a/catalogue/DriveConfig.cpp b/catalogue/DriveConfig.cpp
index 3437162fd6..12792242d9 100644
--- a/catalogue/DriveConfig.cpp
+++ b/catalogue/DriveConfig.cpp
@@ -58,6 +58,7 @@ void DriveConfig::setTapedConfiguration(const cta::tape::daemon::TapedConfigurat
   setConfigToDB(&config->useMaintenanceProcess, catalogue, tapeDriveName);
   setConfigToDB(&config->externalFreeDiskSpaceScript, catalogue, tapeDriveName);
   setConfigToDB(&config->tapeLoadTimeout, catalogue, tapeDriveName);
+  setConfigToDB(&config->wdGlobalLockAcqMaxSecs, catalogue, tapeDriveName);
 }
 
 void DriveConfig::checkConfigInDB(catalogue::Catalogue* catalogue, const std::string& tapeDriveName,
diff --git a/cmdline/CtaAdminCmd.cpp b/cmdline/CtaAdminCmd.cpp
index 17bc5dd79b..1a9c4553c3 100644
--- a/cmdline/CtaAdminCmd.cpp
+++ b/cmdline/CtaAdminCmd.cpp
@@ -276,6 +276,16 @@ void CtaAdminCmd::send() const
       throw std::runtime_error("Configuration error: cta.endpoint missing from " + config_file.string());
    }
 
+   // Check drive timeout value
+   auto [driveTimeoutConfigExists, driveTimeoutVal] = config.getOptionValueInt("drive_timeout");
+   if(driveTimeoutConfigExists) {
+     if (driveTimeoutVal > 0) {
+       formattedText.setDriveTimeout(driveTimeoutVal);
+     } else {
+       throw std::runtime_error("Configuration error: cta.drive_timeout not a positive value in " + config_file.string());
+     }
+   }
+
    // If the server is down, we want an immediate failure. Set client retry to a single attempt.
    XrdSsiProviderClient->SetTimeout(XrdSsiProvider::connect_N, 1);
 
diff --git a/cmdline/CtaAdminCmd.hpp b/cmdline/CtaAdminCmd.hpp
index d741448862..da973b59eb 100644
--- a/cmdline/CtaAdminCmd.hpp
+++ b/cmdline/CtaAdminCmd.hpp
@@ -80,7 +80,7 @@ private:
    static std::atomic<bool> is_json;                                  //!< Display results in JSON format
    static std::atomic<bool> is_first_record;                          //!< Delimiter for JSON records
 
-   std::optional<std::string> m_config;                                 //!< User defined config file
+   std::optional<std::string> m_config;                               //!< User defined config file
 
    static constexpr const char* const LOG_SUFFIX  = "CtaAdminCmd";    //!< Identifier for log messages
 };
diff --git a/cmdline/CtaAdminTextFormatter.cpp b/cmdline/CtaAdminTextFormatter.cpp
index 49db6fae18..7be33fc205 100644
--- a/cmdline/CtaAdminTextFormatter.cpp
+++ b/cmdline/CtaAdminTextFormatter.cpp
@@ -31,6 +31,10 @@ namespace admin {
  ** Generic utility methods
  **/
 
+void TextFormatter::setDriveTimeout(unsigned int driveTimeoutSec)  {
+  m_driveTimeoutSec = driveTimeoutSec;
+}
+
 std::string TextFormatter::doubleToStr(double value, char unit) {
   std::stringstream ss;
   ss << std::fixed << std::setprecision(1) << value;
@@ -271,8 +275,6 @@ void TextFormatter::print(const DriveLsItem &drls_item)
 {
   //using namespace cta::common::dataStructures;
 
-  const int DRIVE_TIMEOUT = 14400; // Time after which a drive will be marked as STALE (4 hours)
-
   std::string driveStatusSince;
   std::string filesTransferredInSession;
   std::string bytesTransferredInSession;
@@ -301,7 +303,7 @@ void TextFormatter::print(const DriveLsItem &drls_item)
   }
 
   timeSinceLastUpdate = std::to_string(drls_item.time_since_last_update()) +
-    (drls_item.time_since_last_update() > DRIVE_TIMEOUT ? " [STALE]" : "");
+    (drls_item.time_since_last_update() > m_driveTimeoutSec ? " [STALE]" : "");
 
   std::string reportedLogicalLibrary =  drls_item.logical_library();
   if (drls_item.logical_library_disabled()) {
diff --git a/cmdline/CtaAdminTextFormatter.hpp b/cmdline/CtaAdminTextFormatter.hpp
index 7dd571fdb7..a8340816c9 100644
--- a/cmdline/CtaAdminTextFormatter.hpp
+++ b/cmdline/CtaAdminTextFormatter.hpp
@@ -22,6 +22,10 @@
 namespace cta {
 namespace admin {
 
+// Time after which a drive will be marked as STALE
+// By default it's the default global lock timeout (15 mins) + 5 extra mins
+const int DRIVE_TIMEOUT_S_DEFAULT = (15 + 5) * 60;
+
 class TextFormatter
 {
 public:
@@ -100,6 +104,9 @@ public:
   void print(const MediaTypeLsItem &mtls_item);
   void print(const RecycleTapeFileLsItem & rtfls_item);
 
+  // Modify drive timeout
+  void setDriveTimeout(unsigned int driveTimeoutSec);
+
 private:
   //! Add a line to the buffer
   template<typename... Args>
@@ -166,6 +173,7 @@ private:
   static constexpr const char* const TEXT_RED    = "\x1b[31;1m";    //!< Terminal formatting code for red text
   static constexpr const char* const TEXT_NORMAL = "\x1b[0m";       //!< Terminal formatting code for normal text
   static constexpr const int NB_CHAR_REASON = 50;                   //!< Reason max length to display in tabular output (DriveLs and TapeLs)
+  unsigned int m_driveTimeoutSec = DRIVE_TIMEOUT_S_DEFAULT;         //!< Time after which a drive will be marked as STALE
 };
 
 }}
diff --git a/cmdline/cta-cli.conf.example b/cmdline/cta-cli.conf.example
index c290392975..c38e933c32 100644
--- a/cmdline/cta-cli.conf.example
+++ b/cmdline/cta-cli.conf.example
@@ -23,3 +23,6 @@ eos.requester.group it
 
 # Specify whether log timestamps should have second (false) or microsecond (true) resolution.
 # cta.log.hiRes false
+
+# STALE drive timeout (seconds)
+# cta.drive_timeout 1200
diff --git a/common/exception/Exception.hpp b/common/exception/Exception.hpp
index d7f2e56b0f..a2468d3d70 100644
--- a/common/exception/Exception.hpp
+++ b/common/exception/Exception.hpp
@@ -117,6 +117,14 @@ protected:
 
 } ;
 
+/**
+ * class TimeoutException
+ * A simple exception used for timeout handling in cts
+ */
+class TimeoutException : public Exception {
+  using Exception::Exception;
+};
+
 }} // namespace cta::exception
 
 #define CTA_GENERATE_EXCEPTION_CLASS(A) class A: public cta::exception::Exception { using Exception::Exception; }
diff --git a/objectstore/BackendRados.cpp b/objectstore/BackendRados.cpp
index 1a37b24e73..1644bba13b 100644
--- a/objectstore/BackendRados.cpp
+++ b/objectstore/BackendRados.cpp
@@ -562,7 +562,7 @@ void BackendRados::lockNotify(const std::string& name, uint64_t timeout_us, Lock
     watcher.wait(watchTimeout);
     TIMESTAMPEDPRINT(lockType==LockType::Shared?"Post-wait (shared)":"Post-wait (exclusive)");
     if (timeout_us && (timeoutTimer.usecs() > (int64_t)timeout_us)) {
-      throw exception::Exception("In BackendRados::lockNotify(): timeout.");
+      throw exception::TimeoutException("In BackendRados::lockNotify(): timeout.");
     }
   }
   cta::exception::Errnum::throwOnReturnedErrno(-rc,
@@ -750,7 +750,7 @@ void BackendRados::lockBackoff(const std::string& name, uint64_t timeout_us, Loc
     }
     if (-EBUSY != rc) break;
     if (timeout_us && (timeoutTimer.usecs() > (int64_t)timeout_us)) {
-      throw exception::Exception(
+      throw exception::TimeoutException(
         "In BackendRados::lockBackoff(): timeout : timeout set = " + std::to_string(timeout_us) + " usec, time to lock the object : " +
         std::to_string(timeoutTimer.usecs()) + " usec, number of tries to lock = " + std::to_string(nbTriesToLock) + " object: " + name);
     }
diff --git a/objectstore/BackendVFS.cpp b/objectstore/BackendVFS.cpp
index 3f7fc7110b..4a6810bd51 100644
--- a/objectstore/BackendVFS.cpp
+++ b/objectstore/BackendVFS.cpp
@@ -296,7 +296,7 @@ BackendVFS::ScopedLock * BackendVFS::lockHelper(const std::string& name, int typ
         throw ex;
       }
       if (t.usecs() > (int64_t)timeout_us) {
-        throw exception::Exception("In BackendVFS::lockHelper(): timeout while locking");
+        throw exception::TimeoutException("In BackendVFS::lockHelper(): timeout while locking");
       }
     }
   } else {
diff --git a/scheduler/OStoreDB/OStoreDB.cpp b/scheduler/OStoreDB/OStoreDB.cpp
index 3c09822f29..5bc560f3db 100644
--- a/scheduler/OStoreDB/OStoreDB.cpp
+++ b/scheduler/OStoreDB/OStoreDB.cpp
@@ -696,7 +696,15 @@ std::string OStoreDB::getLowestRequestAgeRetrieveMountPolicyName(const std::list
 // OStoreDB::getMountInfo()
 //------------------------------------------------------------------------------
 std::unique_ptr<SchedulerDatabase::TapeMountDecisionInfo>
-  OStoreDB::getMountInfo(log::LogContext& logContext) {
+OStoreDB::getMountInfo(log::LogContext& logContext) {
+  return OStoreDB::getMountInfo(logContext, 0);
+}
+
+//------------------------------------------------------------------------------
+// OStoreDB::getMountInfo()
+//------------------------------------------------------------------------------
+std::unique_ptr<SchedulerDatabase::TapeMountDecisionInfo>
+  OStoreDB::getMountInfo(log::LogContext& logContext, uint64_t globalLockTimeout_us) {
   utils::Timer t;
   //Allocate the getMountInfostructure to return.
   assertAgentAddressSet();
@@ -709,7 +717,7 @@ std::unique_ptr<SchedulerDatabase::TapeMountDecisionInfo>
   // Take an exclusive lock on the scheduling and fetch it.
   tmdi.m_schedulerGlobalLock.reset(
     new SchedulerGlobalLock(re.getSchedulerGlobalLock(), m_objectStore));
-  tmdi.m_lockOnSchedulerGlobalLock.lock(*tmdi.m_schedulerGlobalLock);
+  tmdi.m_lockOnSchedulerGlobalLock.lock(*tmdi.m_schedulerGlobalLock, globalLockTimeout_us);
   auto lockSchedGlobalTime = t.secs(utils::Timer::resetCounter);
   tmdi.m_lockTaken = true;
   tmdi.m_schedulerGlobalLock->fetch();
diff --git a/scheduler/OStoreDB/OStoreDB.hpp b/scheduler/OStoreDB/OStoreDB.hpp
index 9045f01ba4..823c3f845f 100644
--- a/scheduler/OStoreDB/OStoreDB.hpp
+++ b/scheduler/OStoreDB/OStoreDB.hpp
@@ -232,6 +232,7 @@ class OStoreDB: public SchedulerDatabase {
 
  public:
   std::unique_ptr<SchedulerDatabase::TapeMountDecisionInfo> getMountInfo(log::LogContext& logContext) override;
+  std::unique_ptr<SchedulerDatabase::TapeMountDecisionInfo> getMountInfo(log::LogContext& logContext, uint64_t globalLockTimeout_us) override;
 
   std::unique_ptr<SchedulerDatabase::TapeMountDecisionInfo> getMountInfoNoLock(PurposeGetMountInfo purpose,
     log::LogContext& logContext) override;
diff --git a/scheduler/PostgresSchedDB/PostgresSchedDB.cpp b/scheduler/PostgresSchedDB/PostgresSchedDB.cpp
index d6a01f9cff..79d6994568 100644
--- a/scheduler/PostgresSchedDB/PostgresSchedDB.cpp
+++ b/scheduler/PostgresSchedDB/PostgresSchedDB.cpp
@@ -261,6 +261,11 @@ SchedulerDatabase::JobsFailedSummary PostgresSchedDB::getRetrieveJobsFailedSumma
 }
 
 std::unique_ptr<SchedulerDatabase::TapeMountDecisionInfo> PostgresSchedDB::getMountInfo(log::LogContext& logContext)
+{
+  throw cta::exception::Exception("Not implemented");
+}
+
+std::unique_ptr<SchedulerDatabase::TapeMountDecisionInfo> PostgresSchedDB::getMountInfo(log::LogContext& logContext, uint64_t globalLockTimeout_us)
 {
    throw cta::exception::Exception("Not implemented");
 }
diff --git a/scheduler/PostgresSchedDB/PostgresSchedDB.hpp b/scheduler/PostgresSchedDB/PostgresSchedDB.hpp
index a19e916735..e04ca64ab5 100644
--- a/scheduler/PostgresSchedDB/PostgresSchedDB.hpp
+++ b/scheduler/PostgresSchedDB/PostgresSchedDB.hpp
@@ -166,6 +166,7 @@ class PostgresSchedDB: public SchedulerDatabase {
   JobsFailedSummary getRetrieveJobsFailedSummary(log::LogContext &logContext) override;
 
   std::unique_ptr<SchedulerDatabase::TapeMountDecisionInfo> getMountInfo(log::LogContext& logContext) override;
+  std::unique_ptr<SchedulerDatabase::TapeMountDecisionInfo> getMountInfo(log::LogContext& logContext, uint64_t globalLockTimeout_us) override;
 
   void trimEmptyQueues(log::LogContext& lc) override;
 
diff --git a/scheduler/Scheduler.cpp b/scheduler/Scheduler.cpp
index fc02eccbc7..6c61a4ddcc 100644
--- a/scheduler/Scheduler.cpp
+++ b/scheduler/Scheduler.cpp
@@ -1435,7 +1435,7 @@ bool Scheduler::getNextMountDryRun(const std::string& logicalLibraryName, const
 //------------------------------------------------------------------------------
 // getNextMount
 //------------------------------------------------------------------------------
-std::unique_ptr<TapeMount> Scheduler::getNextMount(const std::string &logicalLibraryName, const std::string &driveName, log::LogContext & lc) {
+std::unique_ptr<TapeMount> Scheduler::getNextMount(const std::string &logicalLibraryName, const std::string &driveName, log::LogContext & lc, uint64_t globalLockTimeout_us) {
   // In order to decide the next mount to do, we have to take a global lock on
   // the scheduling, retrieve a list of all running mounts, queues sizes for
   // tapes and tape pools, order the candidates by priority
diff --git a/scheduler/Scheduler.hpp b/scheduler/Scheduler.hpp
index 78765e622f..8b94937254 100644
--- a/scheduler/Scheduler.hpp
+++ b/scheduler/Scheduler.hpp
@@ -326,12 +326,14 @@ public:
   bool getNextMountDryRun(const std::string &logicalLibraryName, const std::string &driveName, log::LogContext & lc);
   /**
    * Actually decide which mount to do next for a given drive.
+   * Throws a TimeoutException in case the timeout goes out
    * @param logicalLibraryName library for the drive we are scheduling
    * @param driveName name of the drive we are scheduling
    * @param lc log context
+   * @param globalLockTimeout_us global lock timeout
    * @return unique pointer to the tape mount structure. Next step for the user will be find which type of mount this is.
    */
-  std::unique_ptr<TapeMount> getNextMount(const std::string &logicalLibraryName, const std::string &driveName, log::LogContext & lc);
+  std::unique_ptr<TapeMount> getNextMount(const std::string &logicalLibraryName, const std::string &driveName, log::LogContext & lc, uint64_t globalLockTimeout_us = 0);
 
   /**
    * A function returning
diff --git a/scheduler/SchedulerDatabase.hpp b/scheduler/SchedulerDatabase.hpp
index 6801ae0b45..3de7958964 100644
--- a/scheduler/SchedulerDatabase.hpp
+++ b/scheduler/SchedulerDatabase.hpp
@@ -831,6 +831,7 @@ class SchedulerDatabase {
    * a global lock on for scheduling).
    */
   virtual std::unique_ptr<TapeMountDecisionInfo> getMountInfo(log::LogContext& logContext) = 0;
+  virtual std::unique_ptr<TapeMountDecisionInfo> getMountInfo(log::LogContext& logContext, uint64_t globalLockTimeout_us) = 0;
 
   /**
    * A function running a queue trim. This should be called if the corresponding
diff --git a/scheduler/SchedulerDatabaseFactory.hpp b/scheduler/SchedulerDatabaseFactory.hpp
index 2556c04aaa..dedfb67b53 100644
--- a/scheduler/SchedulerDatabaseFactory.hpp
+++ b/scheduler/SchedulerDatabaseFactory.hpp
@@ -197,6 +197,10 @@ public:
     return m_SchedDB->getMountInfo(logContext);
   }
 
+  std::unique_ptr<TapeMountDecisionInfo> getMountInfo(log::LogContext& logContext, uint64_t globalLockTimeout_us) override {
+    return m_SchedDB->getMountInfo(logContext, globalLockTimeout_us);
+  }
+
   void trimEmptyQueues(log::LogContext& lc) override {
     m_SchedDB->trimEmptyQueues(lc);
   }
diff --git a/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.hpp b/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.hpp
index 3f77de93ac..0e57e8f7f0 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.hpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.hpp
@@ -152,6 +152,11 @@ struct DataTransferConfig {
    */
   time_t wdIdleSessionTimer;
 
+  /**
+  * The timeout after which the tape server stops trying to get the next mount
+  */
+  time_t wdGlobalLockAcqMaxSecs;
+
   /**
    * Constructor that sets all integer member-variables to 0 and all string
    * member-variables to the empty string.
diff --git a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSession.cpp b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSession.cpp
index db76894548..888873bcf3 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSession.cpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSession.cpp
@@ -167,10 +167,16 @@ castor::tape::tapeserver::daemon::DataTransferSession::execute() {
     tapeServerReporter.reportState(cta::tape::session::SessionState::Scheduling,
                                    cta::tape::session::SessionType::Undetermined);
 
+    bool globalLockTimeout = false;
     try {
       if (m_scheduler.getNextMountDryRun(m_driveConfig.logicalLibrary, m_driveConfig.unitName, lc)) {
-        tapeMount = m_scheduler.getNextMount(m_driveConfig.logicalLibrary, m_driveConfig.unitName, lc);
+        tapeMount = m_scheduler.getNextMount(m_driveConfig.logicalLibrary, m_driveConfig.unitName, lc,
+                                             m_dataTransferConfig.wdGlobalLockAcqMaxSecs * 1000000);
       }
+    } catch (cta::exception::TimeoutException &e) {
+      // Print warning and try again, after refreshing the tape drive states
+      lc.log(cta::log::WARNING, "Timeout while scheduling new mount.");
+      globalLockTimeout = true;
     } catch (cta::exception::Exception &e) {
       lc.log(cta::log::ERR, "Error while scheduling new mount. Putting the drive down. Stack trace follows.");
       lc.logBacktrace(cta::log::INFO, e.backtrace());
@@ -180,11 +186,13 @@ castor::tape::tapeserver::daemon::DataTransferSession::execute() {
 
     // No mount to be done found, that was fast...
     if (!tapeMount) {
-      lc.log(cta::log::DEBUG, "No new mount found. (sleeping 10 seconds)");
       // Refresh the status to trigger the timeout update
       m_scheduler.reportDriveStatus(m_driveInfo, cta::common::dataStructures::MountType::NoMount,
                                     cta::common::dataStructures::DriveStatus::Up, lc);
-      sleep(m_dataTransferConfig.wdIdleSessionTimer);
+      if (!globalLockTimeout) {
+        lc.log(cta::log::DEBUG, "No new mount found. (sleeping " + std::to_string(m_dataTransferConfig.wdIdleSessionTimer) + " seconds)");
+        sleep(m_dataTransferConfig.wdIdleSessionTimer);
+      }
       continue;
     }
     break;
diff --git a/tapeserver/daemon/DriveHandler.cpp b/tapeserver/daemon/DriveHandler.cpp
index da65077b65..cc515be349 100644
--- a/tapeserver/daemon/DriveHandler.cpp
+++ b/tapeserver/daemon/DriveHandler.cpp
@@ -1010,6 +1010,7 @@ int DriveHandler::runChild() {
     dataTransferConfig.useEncryption = m_tapedConfig.useEncryption.value() == "yes" ? true : false;
     dataTransferConfig.externalEncryptionKeyScript = m_tapedConfig.externalEncryptionKeyScript.value();
     dataTransferConfig.wdIdleSessionTimer = m_tapedConfig.wdIdleSessionTimer.value();
+    dataTransferConfig.wdGlobalLockAcqMaxSecs = m_tapedConfig.wdGlobalLockAcqMaxSecs.value();
     m_stateChangeTimeouts[session::SessionState::Checking] = std::chrono::duration_cast<Timeout>(
       std::chrono::minutes(m_tapedConfig.wdCheckMaxSecs.value()));
     m_stateChangeTimeouts[session::SessionState::Scheduling] = std::chrono::duration_cast<Timeout>(
diff --git a/tapeserver/daemon/TapedConfiguration.cpp b/tapeserver/daemon/TapedConfiguration.cpp
index e224e01f1e..2ccf0ca403 100644
--- a/tapeserver/daemon/TapedConfiguration.cpp
+++ b/tapeserver/daemon/TapedConfiguration.cpp
@@ -117,6 +117,7 @@ TapedConfiguration TapedConfiguration::createFromCtaConf(
   ret.wdMountMaxSecs.setFromConfigurationFile(cf, generalConfigPath);
   ret.wdNoBlockMoveMaxSecs.setFromConfigurationFile(cf, generalConfigPath);
   ret.wdScheduleMaxSecs.setFromConfigurationFile(cf, generalConfigPath);
+  ret.wdGlobalLockAcqMaxSecs.setFromConfigurationFile(cf, generalConfigPath);
   // The central storage access configuration
   ret.backendPath.setFromConfigurationFile(cf, generalConfigPath);
   ret.fileCatalogConfigFile.setFromConfigurationFile(cf, generalConfigPath);
@@ -158,6 +159,7 @@ TapedConfiguration TapedConfiguration::createFromCtaConf(
   ret.wdMountMaxSecs.log(log);
   ret.wdNoBlockMoveMaxSecs.log(log);
   ret.wdScheduleMaxSecs.log(log);
+  ret.wdGlobalLockAcqMaxSecs.log(log);
   
   ret.backendPath.log(log);
   ret.fileCatalogConfigFile.log(log);
diff --git a/tapeserver/daemon/TapedConfiguration.hpp b/tapeserver/daemon/TapedConfiguration.hpp
index 1b14dbd0fd..3539ae4111 100644
--- a/tapeserver/daemon/TapedConfiguration.hpp
+++ b/tapeserver/daemon/TapedConfiguration.hpp
@@ -134,6 +134,9 @@ struct TapedConfiguration {
   /// Time to wait after scheduling came up idle
   cta::SourcedParameter<time_t> wdIdleSessionTimer{
     "taped", "WatchdogIdleSessionTimer", 10, "Compile time default"};
+  /// Time to wait after which the tape server stops trying to get the next mount
+  cta::SourcedParameter<time_t> wdGlobalLockAcqMaxSecs{
+    "taped", "WatchdogGlobalLockAcqMaxSecs", 15 * 60, "Compile time default"};
   //----------------------------------------------------------------------------
   // The central storage access configuration
   //---------------------------------------------------------------------------- 
-- 
GitLab