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