Mercurial > hg > orthanc-python
annotate Sources/OnChangeCallback.cpp @ 166:6fada29b6759
updated copyright, as Orthanc Team now replaces Osimis
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 30 May 2024 22:27:45 +0200 |
parents | 71d305c29cfa |
children | c8de83fe7faa |
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 | |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
24 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
25 #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
|
26 #include "PythonString.h" |
0 | 27 |
36
fd58eb5749ed
CMake simplification using DownloadOrthancFramework.cmake
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
28 #include <Compatibility.h> // For std::unique_ptr<> |
0 | 29 |
30 #include <boost/thread.hpp> | |
31 | |
32 | |
33 class PendingChange : public boost::noncopyable | |
34 { | |
35 private: | |
36 OrthancPluginChangeType changeType_; | |
37 OrthancPluginResourceType resourceType_; | |
38 std::string resourceId_; | |
39 | |
40 public: | |
41 PendingChange(OrthancPluginChangeType changeType, | |
42 OrthancPluginResourceType resourceType, | |
43 const char* resourceId) : | |
44 changeType_(changeType), | |
45 resourceType_(resourceType) | |
46 { | |
47 if (resourceId == NULL) | |
48 { | |
49 resourceId_.clear(); | |
50 } | |
51 else | |
52 { | |
53 resourceId_.assign(resourceId); | |
54 } | |
55 } | |
56 | |
57 OrthancPluginChangeType GetChangeType() const | |
58 { | |
59 return changeType_; | |
60 } | |
61 | |
62 OrthancPluginResourceType GetResourceType() const | |
63 { | |
64 return resourceType_; | |
65 } | |
66 | |
67 const std::string& GetResourceId() const | |
68 { | |
69 return resourceId_; | |
70 } | |
71 }; | |
72 | |
73 | |
74 | |
57
46fe70776d61
Fix possible deadlock with "orthanc.RegisterOnChangeCallback()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
56
diff
changeset
|
75 // 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
|
76 // "Orthanc::SharedMessageQueue" from the Orthanc framework |
0 | 77 class PendingChanges : public boost::noncopyable |
78 { | |
79 private: | |
80 typedef std::list<PendingChange*> Queue; | |
81 | |
82 boost::mutex mutex_; | |
83 Queue queue_; | |
84 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
|
85 boost::condition_variable emptied_; |
0 | 86 |
87 public: | |
88 ~PendingChanges() | |
89 { | |
90 for (Queue::iterator it = queue_.begin(); it != queue_.end(); ++it) | |
91 { | |
92 assert(*it != NULL); | |
93 delete *it; | |
94 } | |
95 } | |
96 | |
97 void Enqueue(OrthancPluginChangeType changeType, | |
98 OrthancPluginResourceType resourceType, | |
99 const char* resourceId) | |
100 { | |
101 boost::mutex::scoped_lock lock(mutex_); | |
102 queue_.push_back(new PendingChange(changeType, resourceType, resourceId)); | |
103 elementAvailable_.notify_one(); | |
104 } | |
105 | |
106 PendingChange* Dequeue(unsigned int millisecondsTimeout) | |
107 { | |
13 | 108 if (millisecondsTimeout == 0) |
0 | 109 { |
110 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); | |
111 } | |
112 | |
113 boost::mutex::scoped_lock lock(mutex_); | |
114 | |
115 // Wait for a message to arrive in the FIFO queue | |
116 while (queue_.empty()) | |
117 { | |
118 bool success = elementAvailable_.timed_wait | |
119 (lock, boost::posix_time::milliseconds(millisecondsTimeout)); | |
120 if (!success) | |
121 { | |
122 return NULL; | |
123 } | |
124 } | |
125 | |
36
fd58eb5749ed
CMake simplification using DownloadOrthancFramework.cmake
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
126 std::unique_ptr<PendingChange> change(queue_.front()); |
0 | 127 queue_.pop_front(); |
128 | |
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
|
129 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
|
130 { |
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 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
|
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 |
0 | 134 return change.release(); |
135 } | |
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
|
136 |
55c41aa7053b
On Orthanc stopping, wait for all the queued events to have been processed
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
137 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
|
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 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
|
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 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
|
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 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
|
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 } |
0 | 146 }; |
147 | |
148 | |
149 | |
150 static PendingChanges pendingChanges_; | |
151 static bool stopping_ = false; | |
152 static boost::thread changesThread_; | |
153 static PyObject* changesCallback_ = NULL; | |
154 | |
155 | |
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
|
156 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
|
157 { |
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 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
|
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 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
|
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 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
|
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 } |
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 |
0 | 167 static void ChangesWorker() |
168 { | |
169 while (!stopping_) | |
170 { | |
171 for (;;) | |
172 { | |
173 std::unique_ptr<PendingChange> change(pendingChanges_.Dequeue(100)); | |
174 if (change.get() == NULL) | |
175 { | |
176 break; | |
177 } | |
178 else if (changesCallback_ != NULL) | |
179 { | |
180 try | |
181 { | |
182 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
|
183 |
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
184 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
|
185 |
0 | 186 PythonObject args(lock, PyTuple_New(3)); |
187 PyTuple_SetItem(args.GetPyObject(), 0, PyLong_FromLong(change->GetChangeType())); | |
188 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
|
189 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
|
190 |
0 | 191 PythonObject result(lock, PyObject_CallObject(changesCallback_, args.GetPyObject())); |
3
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
192 |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
193 std::string traceback; |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
194 if (lock.HasErrorOccurred(traceback)) |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
195 { |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
196 OrthancPlugins::LogError("Error in the Python on-change callback, " |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
197 "traceback:\n" + traceback); |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
198 } |
0 | 199 } |
200 catch (OrthancPlugins::PluginException& e) | |
201 { | |
202 OrthancPlugins::LogError("Error during Python on-change callback: " + | |
203 std::string(e.What(OrthancPlugins::GetGlobalContext()))); | |
204 } | |
205 } | |
206 } | |
207 } | |
208 } | |
209 | |
210 | |
211 static OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, | |
212 OrthancPluginResourceType resourceType, | |
213 const char* resourceId) | |
214 { | |
57
46fe70776d61
Fix possible deadlock with "orthanc.RegisterOnChangeCallback()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
56
diff
changeset
|
215 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
|
216 |
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 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
|
218 { |
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
|
219 // 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
|
220 // 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
|
221 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
|
222 |
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
|
223 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
|
224 } |
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 |
0 | 226 return OrthancPluginErrorCode_Success; |
227 } | |
228 | |
229 | |
230 PyObject* RegisterOnChangeCallback(PyObject* module, PyObject* args) | |
231 { | |
232 // The GIL is locked at this point (no need to create "PythonLock") | |
233 | |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
234 class Registration : public ICallbackRegistration |
0 | 235 { |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
236 public: |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
237 virtual void Register() ORTHANC_OVERRIDE |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
238 { |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
239 OrthancPluginRegisterOnChangeCallback(OrthancPlugins::GetGlobalContext(), OnChangeCallback); |
0 | 240 |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
241 stopping_ = false; |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
242 changesThread_ = boost::thread(ChangesWorker); |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
243 } |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
244 }; |
0 | 245 |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
246 Registration registration; |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
247 return ICallbackRegistration::Apply( |
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
248 registration, args, changesCallback_, "Python on-changes callback"); |
0 | 249 } |
250 | |
251 | |
252 void FinalizeOnChangeCallback() | |
253 { | |
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
|
254 StopThread(); |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
58
diff
changeset
|
255 ICallbackRegistration::Unregister(changesCallback_); |
0 | 256 } |