changeset 6602:6a8368c85b1a

Save the jobs registry in DB only if it has changed.
author Alain Mazy <am@orthanc.team>
date Mon, 23 Feb 2026 16:40:06 +0100
parents 17f41205ed0e
children c00a24b2cada
files NEWS OrthancFramework/Sources/JobsEngine/JobsEngine.cpp OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp OrthancFramework/Sources/JobsEngine/JobsRegistry.h OrthancServer/Sources/ServerContext.cpp
diffstat 5 files changed, 72 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri Feb 20 17:54:28 2026 +0100
+++ b/NEWS	Mon Feb 23 16:40:06 2026 +0100
@@ -24,7 +24,7 @@
 Lua
 ---
 
-* Added a new "OutgoingCStoreInstanceFilter()" function. (TODO: document in the book + implement in plugins SDK)
+* Added a new "OutgoingCStoreInstanceFilter()" function. (TODO: document in the book + implement in plugins SDK + Remove PermissiveStoreSopClasses ?)
 
 Plugins
 -------
@@ -45,6 +45,8 @@
   https://github.com/orthanc-server/orthanc-builder/issues/33
 * Fix /tools/reset fails to restart server with error "Internal error: Cannot initialize ICU: U_INVALID_FORMAT_ERROR"
   https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=255
+* Save the jobs registry in DB only if it has changed.
+  https://discourse.orthanc-server.org/t/frequent-idle-messages-between-postgres-and-orthanc/6406
 * Upgraded dependencies for static builds:
   - boost 1.89.0
   - dcmtk 3.7.0
--- a/OrthancFramework/Sources/JobsEngine/JobsEngine.cpp	Fri Feb 20 17:54:28 2026 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobsEngine.cpp	Mon Feb 23 16:40:06 2026 +0100
@@ -109,6 +109,8 @@
     
   void JobsEngine::RetryHandler(JobsEngine* engine)
   {
+    Logging::SetCurrentThreadName("JOBS-RETRY");
+
     assert(engine != NULL);
 
     while (engine->IsRunning())
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp	Fri Feb 20 17:54:28 2026 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp	Mon Feb 23 16:40:06 2026 +0100
@@ -47,6 +47,20 @@
   static const char* ERROR_PAYLOAD_TYPE = "ErrorPayloadType";
   static const char* ERROR_PAYLOAD = "ErrorPayload";
 
+  class JobsRegistry::LastModificationTimeUpdater
+  {
+    JobsRegistry*   that_;
+  public:
+    LastModificationTimeUpdater(JobsRegistry* that) :
+      that_(that)
+    {
+    }
+
+    ~LastModificationTimeUpdater()
+    {
+      that_->lastModificationTime_ = boost::posix_time::microsec_clock::universal_time();
+    }
+  };
 
   class JobsRegistry::JobHandler : public boost::noncopyable
   {
@@ -628,6 +642,7 @@
   void JobsRegistry::SetMaxCompletedJobs(size_t n)
   {
     boost::mutex::scoped_lock lock(mutex_);
+    LastModificationTimeUpdater updater(this);
     CheckInvariants();
 
     LOG(INFO) << "The size of the history of the jobs engine is set to: " << n << " job(s)";
@@ -691,6 +706,7 @@
     LOG(INFO) << "Deleting job: " << id;
 
     boost::mutex::scoped_lock lock(mutex_);
+    LastModificationTimeUpdater updater(this);
     CheckInvariants();
 
     JobsIndex::iterator found = jobsIndex_.find(id);
@@ -756,6 +772,7 @@
                                      const std::string& key)
   {
     boost::mutex::scoped_lock lock(mutex_);
+    LastModificationTimeUpdater updater(this);
     CheckInvariants();
 
     JobsIndex::const_iterator found = jobsIndex_.find(job);
@@ -792,6 +809,7 @@
 
     {
       boost::mutex::scoped_lock lock(mutex_);
+      LastModificationTimeUpdater updater(this);
       CheckInvariants();
 
       id = handler->GetId();
@@ -845,6 +863,7 @@
   }
 
   JobsRegistry::JobsRegistry(size_t maxCompletedJobs) :
+    lastModificationTime_(boost::posix_time::neg_infin),
     maxCompletedJobs_(maxCompletedJobs),
     observer_(NULL)
   {
@@ -881,6 +900,8 @@
 
       for (;;)
       {
+        LastModificationTimeUpdater updater(this);
+
         if (!GetStateInternal(state, id))
         {
           // Job has finished and has been lost (typically happens if
@@ -952,6 +973,7 @@
     LOG(INFO) << "Changing priority to " << priority << " for job: " << id;
 
     boost::mutex::scoped_lock lock(mutex_);
+    LastModificationTimeUpdater updater(this);
     CheckInvariants();
 
     JobsIndex::iterator found = jobsIndex_.find(id);
@@ -1020,6 +1042,7 @@
     LOG(INFO) << "Pausing job: " << id;
 
     boost::mutex::scoped_lock lock(mutex_);
+    LastModificationTimeUpdater updater(this);
     CheckInvariants();
 
     JobsIndex::iterator found = jobsIndex_.find(id);
@@ -1068,6 +1091,7 @@
     LOG(INFO) << "Canceling job: " << id;
 
     boost::mutex::scoped_lock lock(mutex_);
+    LastModificationTimeUpdater updater(this);
     CheckInvariants();
 
     JobsIndex::iterator found = jobsIndex_.find(id);
@@ -1125,6 +1149,7 @@
     LOG(INFO) << "Resuming job: " << id;
 
     boost::mutex::scoped_lock lock(mutex_);
+    LastModificationTimeUpdater updater(this);
     CheckInvariants();
 
     JobsIndex::iterator found = jobsIndex_.find(id);
@@ -1155,6 +1180,7 @@
     LOG(INFO) << "Resubmitting failed job: " << id;
 
     boost::mutex::scoped_lock lock(mutex_);
+    LastModificationTimeUpdater updater(this);
     CheckInvariants();
 
     JobsIndex::iterator found = jobsIndex_.find(id);
@@ -1214,6 +1240,7 @@
     {
       if ((*it)->IsRetryReady(now))
       {
+        LastModificationTimeUpdater updater(this);
         LOG(INFO) << "Retrying job: " << (*it)->GetId();
         (*it)->SetState(JobState_Pending);
         pendingJobs_.push(*it);
@@ -1280,6 +1307,8 @@
         }
       }
 
+      LastModificationTimeUpdater updater(&registry_);
+
       handler_ = registry_.pendingJobs_.top();
       registry_.pendingJobs_.pop();
 
@@ -1299,6 +1328,7 @@
     if (IsValid())
     {
       boost::mutex::scoped_lock lock(registry_.mutex_);
+      LastModificationTimeUpdater updater(&registry_);
 
       try
       {
@@ -1495,6 +1525,7 @@
       status.GetErrorPayload() = errorPayload;
 
       boost::mutex::scoped_lock lock(registry_.mutex_);
+      LastModificationTimeUpdater updater(&registry_);
       registry_.CheckInvariants();
       assert(handler_->GetState() == JobState_Running);
 
@@ -1515,6 +1546,7 @@
       JobStatus status(code, details, *job_);
 
       boost::mutex::scoped_lock lock(registry_.mutex_);
+      LastModificationTimeUpdater updater(&registry_);
       registry_.CheckInvariants();
       assert(handler_->GetState() == JobState_Running);
 
@@ -1548,6 +1580,7 @@
   JobsRegistry::JobsRegistry(IJobUnserializer& unserializer,
                              const Json::Value& s,
                              size_t maxCompletedJobs) :
+    lastModificationTime_(boost::posix_time::neg_infin),
     maxCompletedJobs_(maxCompletedJobs),
     observer_(NULL)
   {
@@ -1638,4 +1671,11 @@
       }
     }
   }
+
+  void JobsRegistry::GetLastModificationTime(boost::posix_time::ptime& modificationTime) const
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    
+    modificationTime = lastModificationTime_;
+  }
 }
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.h	Fri Feb 20 17:54:28 2026 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.h	Mon Feb 23 16:40:06 2026 +0100
@@ -70,6 +70,7 @@
     };
 
     class JobHandler;
+    class LastModificationTimeUpdater;
 
     struct PriorityComparator
     {
@@ -84,7 +85,8 @@
                                 std::vector<JobHandler*>,   // Could be a "std::deque"
                                 PriorityComparator>         PendingJobs;
 
-    boost::mutex               mutex_;
+    mutable boost::mutex       mutex_;
+    boost::posix_time::ptime   lastModificationTime_;
     JobsIndex                  jobsIndex_;
     PendingJobs                pendingJobs_;
     CompletedJobs              completedJobs_;
@@ -197,6 +199,8 @@
                        unsigned int& success,
                        unsigned int& errors);
 
+    void GetLastModificationTime(boost::posix_time::ptime& modificationTime) const;
+    
     class ORTHANC_PUBLIC RunningJob : public boost::noncopyable
     {
     private:
--- a/OrthancServer/Sources/ServerContext.cpp	Fri Feb 20 17:54:28 2026 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Mon Feb 23 16:40:06 2026 +0100
@@ -326,21 +326,30 @@
   {
     if (saveJobs_)
     {
-      LOG(TRACE) << "Serializing the content of the jobs engine";
-    
-      try
-      {
-        Json::Value value;
-        jobsEngine_.GetRegistry().Serialize(value);
+      static boost::posix_time::ptime lastSerializedModification = boost::posix_time::neg_infin;
+      boost::posix_time::ptime lastModification = boost::posix_time::neg_infin;
+
+      jobsEngine_.GetRegistry().GetLastModificationTime(lastModification);
 
-        std::string serialized;
-        Toolbox::WriteFastJson(serialized, value);
+      if (lastModification > lastSerializedModification)
+      {
+        LOG(TRACE) << "Serializing the content of the jobs engine";
+      
+        try
+        {
+          Json::Value value;
+          jobsEngine_.GetRegistry().Serialize(value);
 
-        index_.SetGlobalProperty(GlobalProperty_JobsRegistry, false /* not shared */, serialized);
-      }
-      catch (OrthancException& e)
-      {
-        LOG(ERROR) << "Cannot serialize the jobs engine: " << e.What();
+          std::string serialized;
+          Toolbox::WriteFastJson(serialized, value);
+
+          index_.SetGlobalProperty(GlobalProperty_JobsRegistry, false /* not shared */, serialized);
+          lastSerializedModification = lastModification;
+        }
+        catch (OrthancException& e)
+        {
+          LOG(ERROR) << "Cannot serialize the jobs engine: " << e.What();
+        }
       }
     }
   }