changeset 2281:2329f970cde7 refactoring tip

native oracle: sleep
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 19 Sep 2026 01:24:20 +0200
parents 7a169e34a033
children
files Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake OrthancStone/Sources/Oracle/IOracle.h OrthancStone/Sources/Oracle/OracleCallback.cpp OrthancStone/Sources/Oracle/OracleCallback.h OrthancStone/Sources/Oracle/ThreadedOracle.cpp OrthancStone/Sources/Oracle/ThreadedOracle.h OrthancStone/Sources/Platforms/Native/NativeEnvironment.h OrthancStone/Sources/Platforms/Native/RunnableThread.h OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h
diffstat 12 files changed, 259 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Thu Sep 17 20:17:50 2026 +0200
+++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Sat Sep 19 01:24:20 2026 +0200
@@ -61,7 +61,7 @@
 #include "../../../../OrthancStone/Sources/Oracle/ThreadedOracle.h"
 
 static OrthancStone::NativeEnvironment  environment_;
-static OrthancStone::New::ThreadedOracle oracle_(environment_, 4 /* threads */);
+static OrthancStone::New::ThreadedOracle oracle_(4 /* threads */);
 
 
 class Toto : public OrthancStone::IOracleClient
@@ -215,6 +215,7 @@
       context.StartOracle();
 
       environment_.Start();
+      oracle_.Start();
 
       {
         {
@@ -298,7 +299,7 @@
                 {
                   case SDLK_b:
                     // TODO Refactoring
-                    oracle_.Submit(toto_, new OrthancStone::SleepOracleCommand(1000));
+                    oracle_.Submit(environment_, toto_, new OrthancStone::SleepOracleCommand(1000));
                     break;
 
                   case SDLK_f:
@@ -560,6 +561,7 @@
         }
         context.StopOracle();
 
+        oracle_.Stop();
         environment_.Stop();
       }
     }
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Thu Sep 17 20:17:50 2026 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Sat Sep 19 01:24:20 2026 +0200
@@ -4878,7 +4878,7 @@
 #include "../../../OrthancStone/Sources/Oracle/SleepOracleCommand.h"
 
 static OrthancStone::WebAssemblyEnvironment  environment_;
-static OrthancStone::New::WebAssemblyOracle  oracle_(environment_);
+static OrthancStone::New::WebAssemblyOracle  oracle_;
 
 class Toto : public OrthancStone::IOracleClient
 {
@@ -4937,7 +4937,7 @@
 
 
     // TODO Refactoring
-    oracle_.Submit(toto_, new OrthancStone::SleepOracleCommand(2000));
+    oracle_.Submit(environment_, toto_, new OrthancStone::SleepOracleCommand(2000));
   }
 
 
--- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Thu Sep 17 20:17:50 2026 +0200
+++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Sat Sep 19 01:24:20 2026 +0200
@@ -266,6 +266,7 @@
   ${ORTHANC_STONE_ROOT}/Oracle/GetOrthancImageCommand.cpp
   ${ORTHANC_STONE_ROOT}/Oracle/GetOrthancWebViewerJpegCommand.cpp
   ${ORTHANC_STONE_ROOT}/Oracle/HttpCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Oracle/OracleCallback.cpp
   ${ORTHANC_STONE_ROOT}/Oracle/OracleCommandBase.cpp
   ${ORTHANC_STONE_ROOT}/Oracle/OrthancRestApiCommand.cpp
   ${ORTHANC_STONE_ROOT}/Oracle/ParseDicomFromFileCommand.cpp
--- a/OrthancStone/Sources/Oracle/IOracle.h	Thu Sep 17 20:17:50 2026 +0200
+++ b/OrthancStone/Sources/Oracle/IOracle.h	Sat Sep 19 01:24:20 2026 +0200
@@ -24,8 +24,9 @@
 #pragma once
 
 #include "../Messages/IObserver.h"
+#include "IEnvironment.h"
+#include "IOracleClient.h"
 #include "IOracleCommand.h"
-#include "IOracleClient.h"
 
 #include <OrthancException.h>
 
@@ -59,7 +60,8 @@
       {
       }
 
-      virtual void Submit(const boost::shared_ptr<IOracleClient>& client,
+      virtual void Submit(IEnvironment& environment,
+                          const boost::shared_ptr<IOracleClient>& client,
                           IOracleCommand* command /* takes ownership */) = 0;
     };
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Oracle/OracleCallback.cpp	Sat Sep 19 01:24:20 2026 +0200
@@ -0,0 +1,70 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., 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 "OracleCallback.h"
+
+
+namespace OrthancStone
+{
+  OracleCallback::OracleCallback(IEnvironment& environment,
+                                 const boost::shared_ptr<IOracleClient>& client,
+                                 IOracleCommand* command /* takes ownership */) :
+    environment_(environment),
+    client_(client),
+    command_(command)
+  {
+    if (!client ||
+        command == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+  }
+
+
+  void OracleCallback::NotifySuccess(Orthanc::IDynamicObject* result)
+  {
+    std::unique_ptr<Orthanc::IDynamicObject> protection(result);
+
+    if (command_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      environment_.NotifyOracleSuccess(client_, command_.release(), protection.release());
+    }
+  }
+
+
+  void OracleCallback::NotifyError(const Orthanc::OrthancException& error)
+  {
+    if (command_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      environment_.NotifyOracleError(client_, command_.release(), error);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Oracle/OracleCallback.h	Sat Sep 19 01:24:20 2026 +0200
@@ -0,0 +1,46 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., 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 "IEnvironment.h"
+
+namespace OrthancStone
+{
+  class OracleCallback : public boost::noncopyable
+  {
+  private:
+    IEnvironment&                    environment_;
+    boost::weak_ptr<IOracleClient>   client_;
+    std::unique_ptr<IOracleCommand>  command_;
+
+  public:
+    OracleCallback(IEnvironment& environment,
+                   const boost::shared_ptr<IOracleClient>& client,
+                   IOracleCommand* command /* takes ownership */);
+
+    void NotifySuccess(Orthanc::IDynamicObject* result);
+
+    void NotifyError(const Orthanc::OrthancException& error);
+  };
+}
--- a/OrthancStone/Sources/Oracle/ThreadedOracle.cpp	Thu Sep 17 20:17:50 2026 +0200
+++ b/OrthancStone/Sources/Oracle/ThreadedOracle.cpp	Sat Sep 19 01:24:20 2026 +0200
@@ -23,6 +23,7 @@
 
 #include "ThreadedOracle.h"
 
+#include "OracleCallback.h"
 #include "SleepOracleCommand.h"
 
 #include <Logging.h>
@@ -132,7 +133,7 @@
       const boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
 
       Content  stillSleeping;
-        
+
       for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
       {
         if (*it != NULL &&
@@ -440,18 +441,91 @@
     class ThreadedOracle::SleepRunnable : public Orthanc::IRunnable
     {
     private:
+      class Item : public boost::noncopyable
+      {
+      private:
+        OracleCallback            callback_;
+        boost::posix_time::ptime  expiration_;
 
+      public:
+        Item(IEnvironment& environment,
+             const boost::shared_ptr<IOracleClient>& client,
+             SleepOracleCommand* command) :
+          callback_(environment, client, command)
+        {
+          expiration_ = (boost::posix_time::microsec_clock::local_time() +
+                         boost::posix_time::milliseconds(command->GetDelay()));
+        }
+
+        const boost::posix_time::ptime& GetExpirationTime() const
+        {
+          return expiration_;
+        }
+
+        OracleCallback& GetCallback()
+        {
+          return callback_;
+        }
+      };
+
+      typedef std::list<Item*>  Content;
+
+      boost::mutex  mutex_;
+      Content       content_;
 
     public:
+      ~SleepRunnable()
+      {
+        for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          if (*it != NULL)
+          {
+            delete *it;
+          }
+        }
+      }
+
+
+      void Add(IEnvironment& environment,
+               const boost::shared_ptr<IOracleClient>& client,
+               SleepOracleCommand* command /* takes ownership */)
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+        content_.push_back(new Item(environment, client, command));
+      }
+
+
+      // Awakes expired sleeps
       virtual void Run() ORTHANC_OVERRIDE
       {
+        boost::mutex::scoped_lock lock(mutex_);
+
+        const boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
+
+        Content  stillSleeping;
+
+        for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          if (*it != NULL &&
+              (*it)->GetExpirationTime() <= now)
+          {
+            (*it)->GetCallback().NotifySuccess(new Orthanc::IDynamicObject);
+            delete *it;
+            *it = NULL;
+          }
+          else
+          {
+            stillSleeping.push_back(*it);
+          }
+        }
+
+        // Compact the still-sleeping commands
+        content_ = stillSleeping;
       }
     };
 
 
-    ThreadedOracle::ThreadedOracle(IEnvironment& environment,
-                                   unsigned int countWorkers) :
-      environment_(environment),
+    ThreadedOracle::ThreadedOracle(unsigned int countWorkers) :
       sleepingThread_(new SleepRunnable, 50 /* milliseconds */)
     {
       threadPool_.SetThreadsCount(countWorkers);
@@ -459,12 +533,28 @@
     }
 
 
-    void ThreadedOracle::Submit(const boost::shared_ptr<IOracleClient>& client,
+    void ThreadedOracle::Start()
+    {
+      sleepingThread_.Start();
+      threadPool_.Start();
+    }
+
+
+    void ThreadedOracle::Stop()
+    {
+      threadPool_.Stop();
+      sleepingThread_.Stop();
+    }
+
+
+    void ThreadedOracle::Submit(IEnvironment& environment,
+                                const boost::shared_ptr<IOracleClient>& client,
                                 IOracleCommand* command /* takes ownership */)
     {
       std::unique_ptr<IOracleCommand> protection(command);
 
-      if (command == NULL)
+      if (!client ||
+          command == NULL)
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
       }
@@ -473,6 +563,13 @@
       {
         switch (command->GetType())
         {
+          case IOracleCommand::Type_Sleep:
+          {
+            SleepRunnable& runnable = dynamic_cast<SleepRunnable&>(sleepingThread_.GetRunnable());
+            runnable.Add(environment, client, dynamic_cast<SleepOracleCommand*>(protection.release()));
+            break;
+          }
+
           default:
             throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
                                             "Command type not implemented by the Threaded Oracle: " +
@@ -481,11 +578,11 @@
       }
       catch (Orthanc::OrthancException& e)
       {
-        environment_.NotifyOracleError(client, protection.release(), e);
+        environment.NotifyOracleError(client, protection.release(), e);
       }
       catch (...)
       {
-        environment_.NotifyOracleError(client, protection.release(), Orthanc::OrthancException(Orthanc::ErrorCode_InternalError));
+        environment.NotifyOracleError(client, protection.release(), Orthanc::OrthancException(Orthanc::ErrorCode_InternalError));
       }
     }
   }
--- a/OrthancStone/Sources/Oracle/ThreadedOracle.h	Thu Sep 17 20:17:50 2026 +0200
+++ b/OrthancStone/Sources/Oracle/ThreadedOracle.h	Sat Sep 19 01:24:20 2026 +0200
@@ -122,30 +122,20 @@
     class ThreadedOracle : public IOracle
     {
     private:
-      class SleepCommands;
       class SleepRunnable;
 
-      IEnvironment&        environment_;
       RunnableThread       sleepingThread_;
       Orthanc::ThreadPool  threadPool_;
 
     public:
-      ThreadedOracle(IEnvironment& environment,
-                     unsigned int countWorkers);
+      ThreadedOracle(unsigned int countWorkers);
+
+      void Start();
 
-      void Start()
-      {
-        sleepingThread_.Start();
-        threadPool_.Start();
-      }
+      void Stop();
 
-      void Stop()
-      {
-        threadPool_.Stop();
-        sleepingThread_.Stop();
-      }
-
-      virtual void Submit(const boost::shared_ptr<IOracleClient>& client,
+      virtual void Submit(IEnvironment& environment,
+                          const boost::shared_ptr<IOracleClient>& client,
                           IOracleCommand* command /* takes ownership */) ORTHANC_OVERRIDE;
     };
   }
--- a/OrthancStone/Sources/Platforms/Native/NativeEnvironment.h	Thu Sep 17 20:17:50 2026 +0200
+++ b/OrthancStone/Sources/Platforms/Native/NativeEnvironment.h	Sat Sep 19 01:24:20 2026 +0200
@@ -39,7 +39,7 @@
     class SuccessCompletion;
     class ErrorCompletion;
 
-    boost::mutex                 mutex_;  // Main mutex of the application, to go single-threaded
+    boost::recursive_mutex       mutex_;  // Main mutex of the application, to go single-threaded
     Orthanc::SharedMessageQueue  oracleQueue_;
     RunnableThread               oracleThread_;
 
--- a/OrthancStone/Sources/Platforms/Native/RunnableThread.h	Thu Sep 17 20:17:50 2026 +0200
+++ b/OrthancStone/Sources/Platforms/Native/RunnableThread.h	Sat Sep 19 01:24:20 2026 +0200
@@ -69,5 +69,10 @@
     {
       StopInternal(true);
     }
+
+    Orthanc::IRunnable& GetRunnable() const
+    {
+      return *runnable_;
+    }
   };
 }
--- a/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp	Thu Sep 17 20:17:50 2026 +0200
+++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp	Sat Sep 19 01:24:20 2026 +0200
@@ -29,6 +29,7 @@
 #  include <Oracle/WebAssemblyOracle_Includes.h>
 #endif
 
+#include "../../Oracle/OracleCallback.h"
 #include "../../Toolbox/StoneToolbox.h"
 
 #include <OrthancException.h>
@@ -880,43 +881,15 @@
 
   namespace New
   {
-    class WebAssemblyOracle::TimeoutCallback
+    static void TimeoutCallback(void *userData)
     {
-    private:
-      IEnvironment&                        environment_;
-      boost::weak_ptr<IOracleClient>       client_;
-      std::unique_ptr<SleepOracleCommand>  command_;
-
-    public:
-      TimeoutCallback(IEnvironment& environment,
-                      const boost::weak_ptr<IOracleClient>& client,
-                      SleepOracleCommand* command) :
-        environment_(environment),
-        client_(client),
-        command_(command)
-      {
-        if (command == NULL)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-        }
-      }
-
-      void Notify()
-      {
-        assert(environment_.get() != NULL);
-        assert(command_.get() != NULL);
-        environment_.NotifyOracleSuccess(client_, command_.release(), new Orthanc::IDynamicObject);
-      }
-
-      static void Callback(void *userData)
-      {
-        std::unique_ptr<TimeoutCallback> callback(reinterpret_cast<TimeoutCallback*>(userData));
-        callback->Notify();
-      }
-    };
+      std::unique_ptr<OracleCallback> callback(reinterpret_cast<OracleCallback*>(userData));
+      callback->NotifySuccess(new Orthanc::IDynamicObject);
+    }
 
 
-    void WebAssemblyOracle::Submit(const boost::shared_ptr<IOracleClient>& client,
+    void WebAssemblyOracle::Submit(IEnvironment& environment,
+                                   const boost::shared_ptr<IOracleClient>& client,
                                    IOracleCommand* command /* takes ownership */)
     {
       // TODO Refactoring - Use "priority"
@@ -935,23 +908,24 @@
           case IOracleCommand::Type_Sleep:
           {
             unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay();
-            emscripten_set_timeout(TimeoutCallback::Callback, timeoutMS,
-                                   new TimeoutCallback(environment_, client, dynamic_cast<SleepOracleCommand*>(protection.release())));
+            emscripten_set_timeout(TimeoutCallback, timeoutMS,
+                                   new OracleCallback(environment, client, dynamic_cast<SleepOracleCommand*>(protection.release())));
             break;
           }
 
           default:
             throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
-                                            "Command type not implemented by the WebAssembly Oracle: " + command->GetType());
+                                            "Command type not implemented by the WebAssembly Oracle: " +
+                                            boost::lexical_cast<std::string>(command->GetType()));
         }
       }
       catch (Orthanc::OrthancException& e)
       {
-        environment_.NotifyOracleError(client, protection.release(), e);
+        environment.NotifyOracleError(client, protection.release(), e);
       }
       catch (...)
       {
-        environment_.NotifyOracleError(client, protection.release(), Orthanc::OrthancException(Orthanc::ErrorCode_InternalError));
+        environment.NotifyOracleError(client, protection.release(), Orthanc::OrthancException(Orthanc::ErrorCode_InternalError));
       }
     }
   }
--- a/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h	Thu Sep 17 20:17:50 2026 +0200
+++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h	Sat Sep 19 01:24:20 2026 +0200
@@ -160,18 +160,9 @@
   {
     class WebAssemblyOracle : public IOracle
     {
-    private:
-      class TimeoutCallback;
-
-      IEnvironment&  environment_;
-
     public:
-      WebAssemblyOracle(IEnvironment& environment) :
-        environment_(environment)
-      {
-      }
-
-      virtual void Submit(const boost::shared_ptr<IOracleClient>& client,
+      virtual void Submit(IEnvironment& environment,
+                          const boost::shared_ptr<IOracleClient>& client,
                           IOracleCommand* command /* takes ownership */) ORTHANC_OVERRIDE;
     };
   }