Mercurial > hg > orthanc-python
annotate Sources/OnChangeCallback.cpp @ 213:f8dc8dd2da76
documented orthanc.RegisterIncomingCStoreInstanceFilter() and orthanc.RegisterReceivedInstanceCallback()
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 03 Jul 2024 14:56:51 +0200 |
parents | 3757a7b9e6f1 |
children | 3678a028f1f6 |
rev | line source |
---|---|
0 | 1 /** |
2 * Python plugin for Orthanc | |
166
6fada29b6759
updated copyright, as Orthanc Team now replaces Osimis
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
155
diff
changeset
|
3 * Copyright (C) 2020-2023 Osimis S.A., Belgium |
6fada29b6759
updated copyright, as Orthanc Team now replaces Osimis
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
155
diff
changeset
|
4 * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium |
155
71d305c29cfa
updated year to 2024
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
114
diff
changeset
|
5 * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium |
0 | 6 * |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU Affero General Public License | |
9 * as published by the Free Software Foundation, either version 3 of | |
10 * the License, or (at your option) any later version. | |
11 * | |
12 * This program is distributed in the hope that it will be useful, but | |
13 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Affero General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Affero General Public License | |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 **/ | |
20 | |
21 | |
22 #include "OnChangeCallback.h" | |
23 | |
200 | 24 #include "PythonHeaderWrapper.h" |
25 | |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
26 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
27 #include "ICallbackRegistration.h" |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
28 #include "PythonString.h" |
0 | 29 |
36
fd58eb5749ed
CMake simplification using DownloadOrthancFramework.cmake
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
30 #include <Compatibility.h> // For std::unique_ptr<> |
0 | 31 |
32 #include <boost/thread.hpp> | |
33 | |
34 | |
35 class PendingChange : public boost::noncopyable | |
36 { | |
37 private: | |
38 OrthancPluginChangeType changeType_; | |
39 OrthancPluginResourceType resourceType_; | |
40 std::string resourceId_; | |
41 | |
42 public: | |
43 PendingChange(OrthancPluginChangeType changeType, | |
44 OrthancPluginResourceType resourceType, | |
45 const char* resourceId) : | |
46 changeType_(changeType), | |
47 resourceType_(resourceType) | |
48 { | |
49 if (resourceId == NULL) | |
50 { | |
51 resourceId_.clear(); | |
52 } | |
53 else | |
54 { | |
55 resourceId_.assign(resourceId); | |
56 } | |
57 } | |
58 | |
59 OrthancPluginChangeType GetChangeType() const | |
60 { | |
61 return changeType_; | |
62 } | |
63 | |
64 OrthancPluginResourceType GetResourceType() const | |
65 { | |
66 return resourceType_; | |
67 } | |
68 | |
69 const std::string& GetResourceId() const | |
70 { | |
71 return resourceId_; | |
72 } | |
73 }; | |
74 | |
75 | |
76 | |
57
46fe70776d61
Fix possible deadlock with "orthanc.RegisterOnChangeCallback()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
56
diff
changeset
|
77 // This corresponds to a simplified, standalone version of |
46fe70776d61
Fix possible deadlock with "orthanc.RegisterOnChangeCallback()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
56
diff
changeset
|
78 // "Orthanc::SharedMessageQueue" from the Orthanc framework |
0 | 79 class PendingChanges : public boost::noncopyable |
80 { | |
81 private: | |
82 typedef std::list<PendingChange*> Queue; | |
83 | |
84 boost::mutex mutex_; | |
85 Queue queue_; | |
86 boost::condition_variable elementAvailable_; | |
91
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
87 boost::condition_variable emptied_; |
0 | 88 |
89 public: | |
90 ~PendingChanges() | |
91 { | |
92 for (Queue::iterator it = queue_.begin(); it != queue_.end(); ++it) | |
93 { | |
94 assert(*it != NULL); | |
95 delete *it; | |
96 } | |
97 } | |
98 | |
99 void Enqueue(OrthancPluginChangeType changeType, | |
100 OrthancPluginResourceType resourceType, | |
101 const char* resourceId) | |
102 { | |
103 boost::mutex::scoped_lock lock(mutex_); | |
104 queue_.push_back(new PendingChange(changeType, resourceType, resourceId)); | |
105 elementAvailable_.notify_one(); | |
106 } | |
107 | |
108 PendingChange* Dequeue(unsigned int millisecondsTimeout) | |
109 { | |
13 | 110 if (millisecondsTimeout == 0) |
0 | 111 { |
112 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); | |
113 } | |
114 | |
115 boost::mutex::scoped_lock lock(mutex_); | |
116 | |
117 // Wait for a message to arrive in the FIFO queue | |
118 while (queue_.empty()) | |
119 { | |
120 bool success = elementAvailable_.timed_wait | |
121 (lock, boost::posix_time::milliseconds(millisecondsTimeout)); | |
122 if (!success) | |
123 { | |
124 return NULL; | |
125 } | |
126 } | |
127 | |
36
fd58eb5749ed
CMake simplification using DownloadOrthancFramework.cmake
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
128 std::unique_ptr<PendingChange> change(queue_.front()); |
0 | 129 queue_.pop_front(); |
130 | |
91
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
131 if (queue_.empty()) |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
132 { |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
133 emptied_.notify_all(); |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
134 } |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
135 |
0 | 136 return change.release(); |
137 } | |
91
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
138 |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
139 void WaitEmpty() |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
140 { |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
141 boost::mutex::scoped_lock lock(mutex_); |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
142 |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
143 while (!queue_.empty()) |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
144 { |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
145 emptied_.wait(lock); |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
146 } |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
147 } |
0 | 148 }; |
149 | |
150 | |
151 | |
152 static PendingChanges pendingChanges_; | |
153 static bool stopping_ = false; | |
154 static boost::thread changesThread_; | |
155 static PyObject* changesCallback_ = NULL; | |
156 | |
157 | |
58
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
158 static void StopThread() |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
159 { |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
160 stopping_ = true; |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
161 |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
162 if (changesThread_.joinable()) |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
163 { |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
164 changesThread_.join(); |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
165 } |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
166 } |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
167 |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
168 |
0 | 169 static void ChangesWorker() |
170 { | |
171 while (!stopping_) | |
172 { | |
173 for (;;) | |
174 { | |
175 std::unique_ptr<PendingChange> change(pendingChanges_.Dequeue(100)); | |
176 if (change.get() == NULL) | |
177 { | |
178 break; | |
179 } | |
180 else if (changesCallback_ != NULL) | |
181 { | |
182 try | |
183 { | |
184 PythonLock lock; | |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
185 |
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
186 PythonString resourceId(lock, change->GetResourceId()); |
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
187 |
0 | 188 PythonObject args(lock, PyTuple_New(3)); |
189 PyTuple_SetItem(args.GetPyObject(), 0, PyLong_FromLong(change->GetChangeType())); | |
190 PyTuple_SetItem(args.GetPyObject(), 1, PyLong_FromLong(change->GetResourceType())); | |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
191 PyTuple_SetItem(args.GetPyObject(), 2, resourceId.Release()); |
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
192 |
0 | 193 PythonObject result(lock, PyObject_CallObject(changesCallback_, args.GetPyObject())); |
3
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
194 |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
195 std::string traceback; |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
196 if (lock.HasErrorOccurred(traceback)) |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
197 { |
171
c8de83fe7faa
removed deprecation warnings
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
166
diff
changeset
|
198 ORTHANC_PLUGINS_LOG_ERROR("Error in the Python on-change callback, traceback:\n" + traceback); |
3
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
199 } |
0 | 200 } |
201 catch (OrthancPlugins::PluginException& e) | |
202 { | |
171
c8de83fe7faa
removed deprecation warnings
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
166
diff
changeset
|
203 ORTHANC_PLUGINS_LOG_ERROR("Error during Python on-change callback: " + |
c8de83fe7faa
removed deprecation warnings
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
166
diff
changeset
|
204 std::string(e.What(OrthancPlugins::GetGlobalContext()))); |
0 | 205 } |
206 } | |
207 } | |
208 } | |
209 } | |
210 | |
211 | |
212 static OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, | |
213 OrthancPluginResourceType resourceType, | |
214 const char* resourceId) | |
215 { | |
57
46fe70776d61
Fix possible deadlock with "orthanc.RegisterOnChangeCallback()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
56
diff
changeset
|
216 pendingChanges_.Enqueue(changeType, resourceType, resourceId); |
58
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
217 |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
218 if (changeType == OrthancPluginChangeType_OrthancStopped) |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
219 { |
91
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
220 // If stopping, make sure to have processed all the events that |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
221 // are pending in the queue before returning (new in 3.4) |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
222 pendingChanges_.WaitEmpty(); |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
223 |
58
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
224 StopThread(); |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
225 } |
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
226 |
0 | 227 return OrthancPluginErrorCode_Success; |
228 } | |
229 | |
230 | |
231 PyObject* RegisterOnChangeCallback(PyObject* module, PyObject* args) | |
232 { | |
233 // The GIL is locked at this point (no need to create "PythonLock") | |
234 | |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
235 class Registration : public ICallbackRegistration |
0 | 236 { |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
237 public: |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
238 virtual void Register() ORTHANC_OVERRIDE |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
239 { |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
240 OrthancPluginRegisterOnChangeCallback(OrthancPlugins::GetGlobalContext(), OnChangeCallback); |
0 | 241 |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
242 stopping_ = false; |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
243 changesThread_ = boost::thread(ChangesWorker); |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
244 } |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
245 }; |
0 | 246 |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
247 Registration registration; |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
248 return ICallbackRegistration::Apply( |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
249 registration, args, changesCallback_, "Python on-changes callback"); |
0 | 250 } |
251 | |
252 | |
253 void FinalizeOnChangeCallback() | |
254 { | |
58
ef1a1ce0c1e3
During Orthanc shutdown, wait for all the pending events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
57
diff
changeset
|
255 StopThread(); |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
256 ICallbackRegistration::Unregister(changesCallback_); |
0 | 257 } |