changeset 7110:5200f9e88bcd

added class SequentialExecutorService
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 14 Aug 2026 10:02:41 +0200
parents 823a4e5e9fc4
children 8c497a722202
files OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake OrthancFramework/Sources/DataSource/DataSourceSequentialReader.cpp OrthancFramework/Sources/DataSource/DataSourceSequentialReader.h OrthancFramework/Sources/DataSource/DicomSequentialReader.h OrthancFramework/Sources/MultiThreading/CallableGroup.cpp OrthancFramework/Sources/MultiThreading/CallableGroup.h OrthancFramework/Sources/MultiThreading/Future.cpp OrthancFramework/Sources/MultiThreading/Future.h OrthancFramework/Sources/MultiThreading/FutureState.cpp OrthancFramework/Sources/MultiThreading/FutureState.h OrthancFramework/Sources/MultiThreading/IExecutorService.h OrthancFramework/Sources/MultiThreading/Internals/FutureState.cpp OrthancFramework/Sources/MultiThreading/Internals/FutureState.h OrthancFramework/Sources/MultiThreading/Internals/ThreadPoolFuture.cpp OrthancFramework/Sources/MultiThreading/Internals/ThreadPoolFuture.h OrthancFramework/Sources/MultiThreading/SequentialExecutorService.cpp OrthancFramework/Sources/MultiThreading/SequentialExecutorService.h OrthancFramework/Sources/MultiThreading/ThreadPool.cpp OrthancFramework/Sources/MultiThreading/ThreadPool.h OrthancFramework/UnitTestsSources/DataSourceTests.cpp OrthancFramework/UnitTestsSources/JobsTests.cpp OrthancServer/Sources/ServerContext.h
diffstat 22 files changed, 545 insertions(+), 373 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Fri Aug 14 09:16:55 2026 +0200
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Fri Aug 14 10:02:41 2026 +0200
@@ -181,6 +181,7 @@
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/HttpServer/StringMatcher.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/Logging.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MallocMemoryBuffer.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MultiThreading/SequentialExecutorService.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/OrthancException.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/OrthancFramework.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/RestApi/RestApiHierarchy.cpp
@@ -667,8 +668,8 @@
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MetricsRegistry.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MultiThreading/BlockingSharedMessageQueue.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MultiThreading/CallableGroup.cpp
-    ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MultiThreading/Future.cpp
-    ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MultiThreading/FutureState.cpp
+    ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MultiThreading/Internals/FutureState.cpp
+    ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MultiThreading/Internals/ThreadPoolFuture.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MultiThreading/RunnableWorkersPool.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MultiThreading/Semaphore.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MultiThreading/SharedMessageQueue.cpp
--- a/OrthancFramework/Sources/DataSource/DataSourceSequentialReader.cpp	Fri Aug 14 09:16:55 2026 +0200
+++ b/OrthancFramework/Sources/DataSource/DataSourceSequentialReader.cpp	Fri Aug 14 10:02:41 2026 +0200
@@ -322,7 +322,7 @@
     }
     else
     {
-      std::unique_ptr<Future> future(runningRequests_.front());
+      std::unique_ptr<IFuture> future(runningRequests_.front());
       runningRequests_.pop_front();
 
       std::unique_ptr<Item> item(dynamic_cast<Item*>(future->ReleaseResult()));
--- a/OrthancFramework/Sources/DataSource/DataSourceSequentialReader.h	Fri Aug 14 09:16:55 2026 +0200
+++ b/OrthancFramework/Sources/DataSource/DataSourceSequentialReader.h	Fri Aug 14 10:02:41 2026 +0200
@@ -110,7 +110,7 @@
     class Callable;
 
     typedef std::list<IDataIdentifier*>  PendingRequests;
-    typedef std::list<Future*>           RunningRequests;
+    typedef std::list<IFuture*>          RunningRequests;
 
     boost::shared_ptr<IExecutorService>    executor_;
     boost::shared_ptr<DataSourceReader>    reader_;
--- a/OrthancFramework/Sources/DataSource/DicomSequentialReader.h	Fri Aug 14 09:16:55 2026 +0200
+++ b/OrthancFramework/Sources/DataSource/DicomSequentialReader.h	Fri Aug 14 10:02:41 2026 +0200
@@ -29,6 +29,8 @@
 #include "../IMemoryBuffer.h"
 #include "../MultiThreading/IExecutorService.h"
 
+#include <boost/shared_ptr.hpp>
+
 
 namespace Orthanc
 {
--- a/OrthancFramework/Sources/MultiThreading/CallableGroup.cpp	Fri Aug 14 09:16:55 2026 +0200
+++ b/OrthancFramework/Sources/MultiThreading/CallableGroup.cpp	Fri Aug 14 10:02:41 2026 +0200
@@ -65,7 +65,7 @@
       delete *it;
     }
 
-    for (std::list<Future*>::iterator it = futures_.begin(); it != futures_.end(); ++it)
+    for (std::list<IFuture*>::iterator it = futures_.begin(); it != futures_.end(); ++it)
     {
       assert(*it != NULL);
       delete *it;
@@ -119,7 +119,7 @@
       std::unique_ptr<IDynamicObject> result;
 
       {
-        std::unique_ptr<Future> future(that_.futures_.front());
+        std::unique_ptr<IFuture> future(that_.futures_.front());
         assert(future.get() != NULL);
 
         result.reset(future->ReleaseResult());
--- a/OrthancFramework/Sources/MultiThreading/CallableGroup.h	Fri Aug 14 09:16:55 2026 +0200
+++ b/OrthancFramework/Sources/MultiThreading/CallableGroup.h	Fri Aug 14 10:02:41 2026 +0200
@@ -26,6 +26,7 @@
 
 #include "IExecutorService.h"
 
+#include <boost/shared_ptr.hpp>
 #include <list>
 
 
@@ -38,7 +39,7 @@
     boost::shared_ptr<IExecutorService>  executor_;
     unsigned int                         windowSize_;
     std::list<ICallable*>                pending_;
-    std::list<Future*>                   futures_;
+    std::list<IFuture*>                  futures_;
     bool                                 hasIterator_;
 
     void FillWindow();
--- a/OrthancFramework/Sources/MultiThreading/Future.cpp	Fri Aug 14 09:16:55 2026 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "Future.h"
-
-#include "FutureState.h"
-#include "../OrthancException.h"
-
-namespace Orthanc
-{
-  Future::Future(boost::shared_ptr<Internals::FutureState>& state) :
-    state_(state)
-  {
-    if (state.get() == NULL)
-    {
-      throw OrthancException(ErrorCode_NullPointer);
-    }
-  }
-
-  Future::~Future()
-  {
-    state_->Cancel();
-  }
-
-  IDynamicObject* Future::ReleaseResult()
-  {
-    return state_->ReleaseResult();
-  }
-}
--- a/OrthancFramework/Sources/MultiThreading/Future.h	Fri Aug 14 09:16:55 2026 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../IDynamicObject.h"
-
-#include <boost/shared_ptr.hpp>
-
-namespace Orthanc
-{
-  namespace Internals
-  {
-    class FutureState;
-  }
-
-  class ORTHANC_PUBLIC Future : public boost::noncopyable
-  {
-    friend class ThreadPool;
-
-  private:
-    boost::shared_ptr<Internals::FutureState>  state_;
-
-    explicit Future(boost::shared_ptr<Internals::FutureState>& state);
-
-  public:
-    ~Future();
-
-    IDynamicObject* ReleaseResult();
-  };
-}
--- a/OrthancFramework/Sources/MultiThreading/FutureState.cpp	Fri Aug 14 09:16:55 2026 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,170 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "FutureState.h"
-
-#include <cassert>
-
-
-namespace Orthanc
-{
-  namespace Internals
-  {
-    IDynamicObject* FutureState::ReleaseInternal(bool hasTimeout,
-                                                 unsigned int millisecondsTimeout)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      for (;;)
-      {
-        switch (state_)
-        {
-          case State_Pending:
-            if (hasTimeout)
-            {
-              bool success = completed_.timed_wait(lock, boost::posix_time::milliseconds(millisecondsTimeout));
-              if (success)
-              {
-                if (state_ != State_Success &&
-                    state_ != State_Failed)
-                {
-                  THROW_WITH_FILE_AND_LINE_INFO(ErrorCode_InternalError);  // Should never happen
-                }
-              }
-            }
-            else
-            {
-              completed_.wait(lock);
-            }
-            break;
-
-          case State_Canceled:
-            throw OrthancException(ErrorCode_CanceledJob);
-
-          case State_Failed:
-            if (error_.get() == NULL)
-            {
-              throw OrthancException(ErrorCode_BadSequenceOfCalls, "The result of the future has already been released");
-            }
-            else
-            {
-              std::unique_ptr<OrthancException> released(error_.release());
-              throw OrthancException(*released);
-            }
-
-          case State_Success:
-            if (result_.get() == NULL)
-            {
-              throw OrthancException(ErrorCode_BadSequenceOfCalls, "The result of the future has already been released");
-            }
-            else
-            {
-              return result_.release();
-            }
-
-          default:
-            THROW_WITH_FILE_AND_LINE_INFO(ErrorCode_InternalError);  // Should never happen
-        }
-      }
-    }
-
-
-    void FutureState::Cancel() ORTHANC_NOEXCEPT
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (state_ == State_Pending)
-      {
-        state_ = State_Canceled;
-        completed_.notify_all();
-      }
-    }
-
-
-    void FutureState::AcquireResult(IDynamicObject* result)
-    {
-      std::unique_ptr<IDynamicObject> protection(result);
-
-      if (result == NULL)
-      {
-        throw OrthancException(ErrorCode_NullPointer);
-      }
-      else
-      {
-        boost::mutex::scoped_lock lock(mutex_);
-
-        switch (state_)
-        {
-          case State_Pending:
-            assert(result_.get() == NULL);
-            assert(error_.get() == NULL);
-
-            result_.reset(protection.release());
-            state_ = State_Success;
-            completed_.notify_all();
-            break;
-
-          case State_Canceled:
-            break;  // Ignore
-
-          case State_Failed:
-          case State_Success:
-            throw OrthancException(ErrorCode_BadSequenceOfCalls);
-
-          default:
-            THROW_WITH_FILE_AND_LINE_INFO(ErrorCode_InternalError);  // Should never happen
-        }
-      }
-    }
-
-
-    void FutureState::SetError(const OrthancException& error)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      switch (state_)
-      {
-        case State_Pending:
-          assert(result_.get() == NULL);
-          assert(error_.get() == NULL);
-
-          error_.reset(new OrthancException(error));
-          state_ = State_Failed;
-          completed_.notify_all();
-          break;
-
-        case State_Canceled:
-          break;  // Ignore
-
-        case State_Failed:
-        case State_Success:
-          throw OrthancException(ErrorCode_BadSequenceOfCalls);
-
-        default:
-          THROW_WITH_FILE_AND_LINE_INFO(ErrorCode_InternalError);  // Should never happen
-      }
-    }
-  }
-}
--- a/OrthancFramework/Sources/MultiThreading/FutureState.h	Fri Aug 14 09:16:55 2026 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Compatibility.h"
-#include "../IDynamicObject.h"
-#include "../OrthancException.h"
-
-#include <boost/thread/mutex.hpp>
-#include <boost/thread/condition_variable.hpp>
-
-namespace Orthanc
-{
-  namespace Internals
-  {
-    class FutureState : public boost::noncopyable
-    {
-    public:
-      enum State
-      {
-        State_Pending,
-        State_Success,
-        State_Failed,
-        State_Canceled
-      };
-
-    private:
-      boost::mutex                       mutex_;
-      boost::condition_variable          completed_;
-      State                              state_;
-      std::unique_ptr<IDynamicObject>    result_;
-      std::unique_ptr<OrthancException>  error_;
-
-      IDynamicObject* ReleaseInternal(bool hasTimeout,
-                                      unsigned int millisecondsTimeout);
-
-    public:
-      FutureState() :
-        state_(State_Pending)
-      {
-      }
-
-      IDynamicObject* ReleaseResult()
-      {
-        return ReleaseInternal(false, 0);
-      }
-
-      IDynamicObject* ReleaseResult(unsigned int millisecondsTimeout)
-      {
-        return ReleaseInternal(true, millisecondsTimeout);
-      }
-
-      void Cancel() ORTHANC_NOEXCEPT;
-
-      void AcquireResult(IDynamicObject* result /* takes ownership */);
-
-      void SetError(const OrthancException& error);
-    };
-  }
-}
--- a/OrthancFramework/Sources/MultiThreading/IExecutorService.h	Fri Aug 14 09:16:55 2026 +0200
+++ b/OrthancFramework/Sources/MultiThreading/IExecutorService.h	Fri Aug 14 10:02:41 2026 +0200
@@ -24,7 +24,7 @@
 
 #pragma once
 
-#include "Future.h"
+#include "IFuture.h"
 #include "ICallable.h"
 #include "IRunnable.h"
 
@@ -38,7 +38,7 @@
     {
     }
 
-    virtual Future* Submit(ICallable* callable /* takes ownership */) = 0;
+    virtual IFuture* Submit(ICallable* callable /* takes ownership */) = 0;
 
     virtual void Submit(IRunnable* runnable /* takes ownership */) = 0;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/MultiThreading/Internals/FutureState.cpp	Fri Aug 14 10:02:41 2026 +0200
@@ -0,0 +1,170 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../../PrecompiledHeaders.h"
+#include "FutureState.h"
+
+#include <cassert>
+
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    IDynamicObject* FutureState::ReleaseInternal(bool hasTimeout,
+                                                 unsigned int millisecondsTimeout)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      for (;;)
+      {
+        switch (state_)
+        {
+          case State_Pending:
+            if (hasTimeout)
+            {
+              bool success = completed_.timed_wait(lock, boost::posix_time::milliseconds(millisecondsTimeout));
+              if (success)
+              {
+                if (state_ != State_Success &&
+                    state_ != State_Failed)
+                {
+                  THROW_WITH_FILE_AND_LINE_INFO(ErrorCode_InternalError);  // Should never happen
+                }
+              }
+            }
+            else
+            {
+              completed_.wait(lock);
+            }
+            break;
+
+          case State_Canceled:
+            throw OrthancException(ErrorCode_CanceledJob);
+
+          case State_Failed:
+            if (error_.get() == NULL)
+            {
+              throw OrthancException(ErrorCode_BadSequenceOfCalls, "The result of the future has already been released");
+            }
+            else
+            {
+              std::unique_ptr<OrthancException> released(error_.release());
+              throw OrthancException(*released);
+            }
+
+          case State_Success:
+            if (result_.get() == NULL)
+            {
+              throw OrthancException(ErrorCode_BadSequenceOfCalls, "The result of the future has already been released");
+            }
+            else
+            {
+              return result_.release();
+            }
+
+          default:
+            THROW_WITH_FILE_AND_LINE_INFO(ErrorCode_InternalError);  // Should never happen
+        }
+      }
+    }
+
+
+    void FutureState::Cancel() ORTHANC_NOEXCEPT
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (state_ == State_Pending)
+      {
+        state_ = State_Canceled;
+        completed_.notify_all();
+      }
+    }
+
+
+    void FutureState::AcquireResult(IDynamicObject* result)
+    {
+      std::unique_ptr<IDynamicObject> protection(result);
+
+      if (result == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+      else
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+
+        switch (state_)
+        {
+          case State_Pending:
+            assert(result_.get() == NULL);
+            assert(error_.get() == NULL);
+
+            result_.reset(protection.release());
+            state_ = State_Success;
+            completed_.notify_all();
+            break;
+
+          case State_Canceled:
+            break;  // Ignore
+
+          case State_Failed:
+          case State_Success:
+            throw OrthancException(ErrorCode_BadSequenceOfCalls);
+
+          default:
+            THROW_WITH_FILE_AND_LINE_INFO(ErrorCode_InternalError);  // Should never happen
+        }
+      }
+    }
+
+
+    void FutureState::SetError(const OrthancException& error)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      switch (state_)
+      {
+        case State_Pending:
+          assert(result_.get() == NULL);
+          assert(error_.get() == NULL);
+
+          error_.reset(new OrthancException(error));
+          state_ = State_Failed;
+          completed_.notify_all();
+          break;
+
+        case State_Canceled:
+          break;  // Ignore
+
+        case State_Failed:
+        case State_Success:
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+
+        default:
+          THROW_WITH_FILE_AND_LINE_INFO(ErrorCode_InternalError);  // Should never happen
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/MultiThreading/Internals/FutureState.h	Fri Aug 14 10:02:41 2026 +0200
@@ -0,0 +1,82 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Compatibility.h"
+#include "../../IDynamicObject.h"
+#include "../../OrthancException.h"
+
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/condition_variable.hpp>
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    class FutureState : public boost::noncopyable
+    {
+    public:
+      enum State
+      {
+        State_Pending,
+        State_Success,
+        State_Failed,
+        State_Canceled
+      };
+
+    private:
+      boost::mutex                       mutex_;
+      boost::condition_variable          completed_;
+      State                              state_;
+      std::unique_ptr<IDynamicObject>    result_;
+      std::unique_ptr<OrthancException>  error_;
+
+      IDynamicObject* ReleaseInternal(bool hasTimeout,
+                                      unsigned int millisecondsTimeout);
+
+    public:
+      FutureState() :
+        state_(State_Pending)
+      {
+      }
+
+      IDynamicObject* ReleaseResult()
+      {
+        return ReleaseInternal(false, 0);
+      }
+
+      IDynamicObject* ReleaseResult(unsigned int millisecondsTimeout)
+      {
+        return ReleaseInternal(true, millisecondsTimeout);
+      }
+
+      void Cancel() ORTHANC_NOEXCEPT;
+
+      void AcquireResult(IDynamicObject* result /* takes ownership */);
+
+      void SetError(const OrthancException& error);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/MultiThreading/Internals/ThreadPoolFuture.cpp	Fri Aug 14 10:02:41 2026 +0200
@@ -0,0 +1,54 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../../PrecompiledHeaders.h"
+#include "ThreadPoolFuture.h"
+
+#include "FutureState.h"
+#include "../../OrthancException.h"
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    ThreadPoolFuture::ThreadPoolFuture(boost::shared_ptr<FutureState>& state) :
+      state_(state)
+    {
+      if (state.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+    }
+
+    ThreadPoolFuture::~ThreadPoolFuture()
+    {
+      state_->Cancel();
+    }
+
+    IDynamicObject* ThreadPoolFuture::ReleaseResult()
+    {
+      return state_->ReleaseResult();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/MultiThreading/Internals/ThreadPoolFuture.h	Fri Aug 14 10:02:41 2026 +0200
@@ -0,0 +1,49 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../IFuture.h"
+#include "FutureState.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    class ORTHANC_PUBLIC ThreadPoolFuture : public IFuture
+    {
+    private:
+      boost::shared_ptr<FutureState>  state_;
+
+    public:
+      explicit ThreadPoolFuture(boost::shared_ptr<FutureState>& state);
+
+      virtual ~ThreadPoolFuture();
+
+      IDynamicObject* ReleaseResult() ORTHANC_OVERRIDE;
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/MultiThreading/SequentialExecutorService.cpp	Fri Aug 14 10:02:41 2026 +0200
@@ -0,0 +1,112 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "SequentialExecutorService.h"
+
+#include "../OrthancException.h"
+
+
+namespace Orthanc
+{
+  class SequentialExecutorService::Future : public IFuture
+  {
+  private:
+    std::unique_ptr<IDynamicObject>  value_;
+
+  public:
+    Future(IDynamicObject* value) :
+      value_(value)
+    {
+      if (value_ == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+    }
+
+    virtual IDynamicObject* ReleaseResult() ORTHANC_OVERRIDE
+    {
+      return value_.release();
+    }
+  };
+
+
+  SequentialExecutorService::SequentialExecutorService() :
+    working_(true)
+  {
+  }
+
+
+  bool SequentialExecutorService::IsWorking()
+  {
+    Mutex::ScopedLock lock(mutex_);
+    return working_;
+  }
+
+
+  IFuture* SequentialExecutorService::Submit(ICallable* callable /* takes ownership */)
+  {
+    Mutex::ScopedLock lock(mutex_);
+
+    if (working_)
+    {
+      return new Future(callable->Call());
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  void SequentialExecutorService::Submit(IRunnable* runnable /* takes ownership */)
+  {
+    Mutex::ScopedLock lock(mutex_);
+
+    if (runnable == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+    else
+    {
+      std::unique_ptr<IRunnable> protection(runnable);
+      protection->Run();
+    }
+  }
+
+
+  void SequentialExecutorService::Stop()
+  {
+    Mutex::ScopedLock lock(mutex_);
+
+    if (working_)
+    {
+      working_ = false;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/MultiThreading/SequentialExecutorService.h	Fri Aug 14 10:02:41 2026 +0200
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Compatibility.h"
+#include "IExecutorService.h"
+#include "Mutex.h"
+
+
+namespace Orthanc
+{
+  class SequentialExecutorService : public IExecutorService
+  {
+  private:
+    class Future;
+
+    Mutex  mutex_;
+    bool   working_;
+
+  public:
+    SequentialExecutorService();
+
+    bool IsWorking();
+
+    virtual IFuture* Submit(ICallable* callable /* takes ownership */) ORTHANC_OVERRIDE;
+
+    virtual void Submit(IRunnable* runnable /* takes ownership */) ORTHANC_OVERRIDE;
+
+    virtual void Stop() ORTHANC_OVERRIDE;
+  };
+}
--- a/OrthancFramework/Sources/MultiThreading/ThreadPool.cpp	Fri Aug 14 09:16:55 2026 +0200
+++ b/OrthancFramework/Sources/MultiThreading/ThreadPool.cpp	Fri Aug 14 10:02:41 2026 +0200
@@ -26,10 +26,11 @@
 #include "ThreadPool.h"
 
 #include "../Logging.h"
+#include "../MetricsRegistry.h"
 #include "../OrthancException.h"
-#include "../MetricsRegistry.h"
 #include "../Toolbox.h"
-#include "FutureState.h"
+#include "Internals/FutureState.h"
+#include "Internals/ThreadPoolFuture.h"
 
 #include <boost/lexical_cast.hpp>
 #include <boost/weak_ptr.hpp>
@@ -389,7 +390,7 @@
   }
 
 
-  Future* ThreadPool::Submit(ICallable* callable)
+  IFuture* ThreadPool::Submit(ICallable* callable)
   {
     std::unique_ptr<ICallable> protection(callable);
 
@@ -411,7 +412,7 @@
 
     queue_.Enqueue(new CallableTask(protection.release(), state));
 
-    return new Future(state);
+    return new Internals::ThreadPoolFuture(state);
   }
 
 
--- a/OrthancFramework/Sources/MultiThreading/ThreadPool.h	Fri Aug 14 09:16:55 2026 +0200
+++ b/OrthancFramework/Sources/MultiThreading/ThreadPool.h	Fri Aug 14 10:02:41 2026 +0200
@@ -82,7 +82,7 @@
 
     void Start();
 
-    virtual Future* Submit(ICallable* callable /* takes ownership */) ORTHANC_OVERRIDE;
+    virtual IFuture* Submit(ICallable* callable /* takes ownership */) ORTHANC_OVERRIDE;
 
     virtual void Submit(IRunnable* runnable /* takes ownership */) ORTHANC_OVERRIDE;
 
--- a/OrthancFramework/UnitTestsSources/DataSourceTests.cpp	Fri Aug 14 09:16:55 2026 +0200
+++ b/OrthancFramework/UnitTestsSources/DataSourceTests.cpp	Fri Aug 14 10:02:41 2026 +0200
@@ -30,10 +30,13 @@
 #include <gtest/gtest.h>
 
 #include "../Sources/DataSource/DataSourceReader.h"
+#include "../Sources/MultiThreading/SequentialExecutorService.h"
+#include "../Sources/OrthancException.h"
+
 
 using namespace Orthanc;
 
 
-TEST(DataSources, Basic)
+TEST(DataSource, Basic)
 {
 }
--- a/OrthancFramework/UnitTestsSources/JobsTests.cpp	Fri Aug 14 09:16:55 2026 +0200
+++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp	Fri Aug 14 10:02:41 2026 +0200
@@ -1860,8 +1860,8 @@
   pool.Start();
 
   {
-    std::unique_ptr<Future> future1(pool.Submit(new CountCallable));
-    std::unique_ptr<Future> future2(pool.Submit(new ExceptionCallable));
+    std::unique_ptr<IFuture> future1(pool.Submit(new CountCallable));
+    std::unique_ptr<IFuture> future2(pool.Submit(new ExceptionCallable));
 
     {
       std::unique_ptr<IDynamicObject> result(future1->ReleaseResult());
--- a/OrthancServer/Sources/ServerContext.h	Fri Aug 14 09:16:55 2026 +0200
+++ b/OrthancServer/Sources/ServerContext.h	Fri Aug 14 10:02:41 2026 +0200
@@ -36,7 +36,6 @@
 #include "../../OrthancFramework/Sources/DicomParsing/DicomModification.h"
 #include "../../OrthancFramework/Sources/JobsEngine/JobsEngine.h"
 #include "../../OrthancFramework/Sources/MetricsRegistry.h"
-#include "../../OrthancFramework/Sources/MultiThreading/Future.h"
 #include "../../OrthancFramework/Sources/MultiThreading/Semaphore.h"