Mercurial > hg > orthanc-python
annotate Sources/PythonLock.cpp @ 28:b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 27 Apr 2020 07:59:35 +0200 |
parents | 952e969a2240 |
children | fd58eb5749ed |
rev | line source |
---|---|
0 | 1 /** |
2 * Python plugin for Orthanc | |
3 * Copyright (C) 2017-2020 Osimis S.A., Belgium | |
4 * | |
5 * This program is free software: you can redistribute it and/or | |
6 * modify it under the terms of the GNU Affero General Public License | |
7 * as published by the Free Software Foundation, either version 3 of | |
8 * the License, or (at your option) any later version. | |
9 * | |
10 * This program is distributed in the hope that it will be useful, but | |
11 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 * Affero General Public License for more details. | |
14 * | |
15 * You should have received a copy of the GNU Affero General Public License | |
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 **/ | |
18 | |
19 | |
20 #include "PythonLock.h" | |
21 | |
22 #include "PythonFunction.h" | |
23 #include "PythonModule.h" | |
24 | |
25 #include <OrthancPluginCppWrapper.h> | |
26 | |
27 #include <boost/thread/mutex.hpp> | |
28 | |
29 static boost::mutex mutex_; | |
30 static PyThreadState* interpreterState_ = NULL; | |
31 static PythonLock::ModuleFunctionsInstaller moduleFunctions_ = NULL; | |
32 static PythonLock::ModuleClassesInstaller moduleClasses_ = NULL; | |
33 static std::string moduleName_; | |
34 static std::string exceptionName_; | |
28
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
35 static bool verbose_ = false; |
0 | 36 |
37 | |
38 struct module_state | |
39 { | |
40 PyObject *exceptionClass_ = NULL; | |
41 }; | |
42 | |
43 #if PY_MAJOR_VERSION >= 3 | |
44 # define GETSTATE(module) ((struct module_state*) PyModule_GetState(module)) | |
45 #else | |
46 # define GETSTATE(module) (&_state) | |
47 static struct module_state _state; | |
48 #endif | |
49 | |
50 | |
13 | 51 PythonLock::PythonLock() : |
52 gstate_(PyGILState_Ensure()) | |
0 | 53 { |
54 //OrthancPlugins::LogInfo("Python lock (GIL) acquired"); | |
55 } | |
56 | |
57 | |
58 PythonLock::~PythonLock() | |
59 { | |
60 PyGILState_Release(gstate_); | |
61 //OrthancPlugins::LogInfo("Python lock (GIL) released"); | |
62 } | |
63 | |
64 | |
65 void PythonLock::ExecuteCommand(const std::string& s) | |
66 { | |
67 if (PyRun_SimpleString(s.c_str()) != 0) | |
68 { | |
69 OrthancPlugins::LogError("Error while executing a Python command"); | |
70 ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); | |
71 } | |
72 } | |
73 | |
74 | |
75 void PythonLock::ExecuteFile(const std::string& path) | |
76 { | |
77 OrthancPlugins::MemoryBuffer buffer; | |
78 buffer.ReadFile(path); | |
79 | |
80 std::string script; | |
81 buffer.ToString(script); | |
82 | |
83 ExecuteCommand(script); | |
84 } | |
85 | |
86 | |
87 bool PythonLock::HasErrorOccurred(std::string& target) | |
88 { | |
89 if (PyErr_Occurred()) | |
90 { | |
91 PyObject *exceptionType = NULL; | |
92 PyObject *exceptionValue = NULL; | |
93 PyObject *traceback = NULL; | |
94 PyErr_Fetch(&exceptionType, &exceptionValue, &traceback); | |
95 | |
96 if (exceptionType == NULL) | |
97 { | |
98 return false; | |
99 } | |
100 | |
101 PyErr_NormalizeException(&exceptionType, &exceptionValue, &traceback); | |
102 | |
103 #if PY_MAJOR_VERSION >= 3 | |
104 if (traceback != NULL) | |
105 { | |
106 PyException_SetTraceback(exceptionValue, traceback); | |
107 } | |
108 #endif | |
109 | |
110 if (exceptionType != NULL) | |
111 { | |
112 PythonObject temp(*this, PyObject_Str(exceptionType)); | |
113 std::string s; | |
114 if (temp.ToUtf8String(s)) | |
115 { | |
116 target += s + "\n"; | |
117 } | |
118 } | |
119 | |
120 if (exceptionValue != NULL) | |
121 { | |
122 PythonObject temp(*this, PyObject_Str(exceptionValue)); | |
123 std::string s; | |
124 if (temp.ToUtf8String(s)) | |
125 { | |
126 target += s + "\n"; | |
127 } | |
128 } | |
129 | |
130 { | |
131 PythonModule module(*this, "traceback"); | |
132 PythonFunction f(*this, module, "format_tb"); | |
133 | |
134 if (traceback != NULL && | |
135 f.IsValid()) | |
136 { | |
137 PythonObject args(*this, PyTuple_New(1)); | |
138 PyTuple_SetItem(args.GetPyObject(), 0, traceback); | |
139 | |
140 std::auto_ptr<PythonObject> value(f.CallUnchecked(args.GetPyObject())); | |
141 | |
142 if (value->IsValid()) | |
143 { | |
144 Py_ssize_t len = PyList_Size(value->GetPyObject()); | |
145 for (Py_ssize_t i = 0; i < len; i++) | |
146 { | |
147 PythonObject item(*this, PyList_GetItem(value->GetPyObject(), i), true /* borrowed */); | |
148 std::string line; | |
149 if (item.ToUtf8String(line)) | |
150 { | |
151 target += "\n" + line; | |
152 } | |
153 } | |
154 } | |
155 } | |
156 } | |
157 | |
158 | |
159 /** | |
160 * "This call takes away a reference to each object: you must own | |
161 * a reference to each object before the call and after the call | |
162 * you no longer own these references. (If you don't understand | |
163 * this, don't use this function. I warned you.)" | |
164 * => I don't use PyErr_Restore() | |
165 **/ | |
166 | |
167 //PyErr_Restore(exceptionType, exceptionValue, traceback); | |
168 //PyErr_Clear(); | |
169 | |
170 return true; | |
171 } | |
172 else | |
173 { | |
174 return false; | |
175 } | |
176 } | |
177 | |
178 | |
179 static void RegisterException(PyObject* module, | |
180 const std::string& name) | |
181 { | |
182 struct module_state *state = GETSTATE(module); | |
183 | |
184 state->exceptionClass_ = PyErr_NewException(const_cast<char*>(name.c_str()), NULL, NULL); | |
185 if (state->exceptionClass_ == NULL) | |
186 { | |
187 Py_DECREF(module); | |
188 OrthancPlugins::LogError("Cannot create the Python exception class"); | |
189 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); | |
190 } | |
191 | |
192 #if 1 | |
193 Py_XINCREF(state->exceptionClass_); | |
194 if (PyModule_AddObject(module, "Exception", state->exceptionClass_) < 0) | |
195 { | |
196 Py_XDECREF(state->exceptionClass_); | |
197 Py_CLEAR(state->exceptionClass_); | |
198 OrthancPlugins::LogError("Cannot create the Python exception class"); | |
199 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); | |
200 } | |
201 #else | |
202 // Install the exception class | |
203 PyObject* dict = PyModule_GetDict(module); | |
204 PyDict_SetItemString(dict, "Exception", state->exceptionClass); | |
205 #endif | |
206 } | |
207 | |
208 | |
209 | |
210 #if PY_MAJOR_VERSION >= 3 | |
211 | |
212 static int sdk_traverse(PyObject *module, visitproc visit, void *arg) | |
213 { | |
214 Py_VISIT(GETSTATE(module)->exceptionClass_); | |
215 return 0; | |
216 } | |
217 | |
218 static int sdk_clear(PyObject *module) | |
219 { | |
220 Py_CLEAR(GETSTATE(module)->exceptionClass_); | |
221 return 0; | |
222 } | |
223 | |
224 static struct PyModuleDef moduledef = | |
225 { | |
226 PyModuleDef_HEAD_INIT, | |
227 NULL, /* m_name => TO BE FILLED */ | |
228 NULL, | |
229 sizeof(struct module_state), | |
230 NULL, /* m_methods => TO BE FILLED */ | |
231 NULL, | |
232 sdk_traverse, | |
233 sdk_clear, | |
234 NULL | |
235 }; | |
236 | |
237 | |
238 PyMODINIT_FUNC InitializeModule() | |
239 { | |
240 if (moduleFunctions_ == NULL || | |
241 moduleClasses_ == NULL || | |
242 moduleName_.empty() || | |
243 exceptionName_.empty()) | |
244 { | |
245 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); | |
246 } | |
247 | |
248 moduledef.m_name = moduleName_.c_str(); | |
249 moduledef.m_methods = moduleFunctions_(); | |
250 | |
251 PyObject *module = PyModule_Create(&moduledef); | |
252 if (module == NULL) | |
253 { | |
254 OrthancPlugins::LogError("Cannot create a Python module"); | |
255 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); | |
256 } | |
257 | |
258 RegisterException(module, moduleName_ + "." + exceptionName_); | |
259 moduleClasses_(module); | |
260 | |
261 return module; | |
262 } | |
263 | |
264 #else | |
265 | |
266 void InitializeModule() | |
267 { | |
268 if (moduleFunctions_ == NULL || | |
269 moduleClasses_ == NULL || | |
270 moduleName_.empty() || | |
271 exceptionName_.empty()) | |
272 { | |
273 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); | |
274 } | |
275 | |
276 PyObject *module = Py_InitModule(moduleName_.c_str(), moduleFunctions_()); | |
277 if (module == NULL) | |
278 { | |
279 OrthancPlugins::LogError("Cannot create a Python module"); | |
280 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); | |
281 } | |
282 | |
283 RegisterException(module, moduleName_ + "." + exceptionName_); | |
284 moduleClasses_(module); | |
285 } | |
286 | |
287 #endif | |
288 | |
289 | |
290 | |
291 void PythonLock::GlobalInitialize(const std::string& moduleName, | |
292 const std::string& exceptionName, | |
293 ModuleFunctionsInstaller moduleFunctions, | |
294 ModuleClassesInstaller moduleClasses, | |
295 bool verbose) | |
296 { | |
297 boost::mutex::scoped_lock lock(mutex_); | |
298 | |
299 if (interpreterState_ != NULL) | |
300 { | |
301 OrthancPlugins::LogError("Cannot initialize twice the Python interpreter"); | |
302 ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); | |
303 } | |
304 | |
305 if (moduleClasses == NULL || | |
306 moduleFunctions == NULL) | |
307 { | |
308 ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer); | |
309 } | |
310 | |
311 if (moduleName.empty() || | |
312 exceptionName.empty()) | |
313 { | |
314 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); | |
315 } | |
316 | |
317 if (exceptionName.find('.') != std::string::npos) | |
318 { | |
319 OrthancPlugins::LogError("The name of the exception cannot contain \".\", found: " + | |
320 exceptionName); | |
321 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); | |
322 } | |
323 | |
324 moduleClasses_ = moduleClasses; | |
325 moduleFunctions_ = moduleFunctions; | |
326 moduleName_ = moduleName; | |
327 exceptionName_ = exceptionName; | |
328 | |
329 std::string executable; | |
330 | |
331 { | |
332 OrthancPlugins::OrthancString str; | |
333 str.Assign(OrthancPluginGetOrthancPath(OrthancPlugins::GetGlobalContext())); | |
334 str.ToString(executable); | |
335 } | |
336 | |
337 OrthancPlugins::LogWarning("Program name: " + executable); | |
338 | |
339 #if PY_MAJOR_VERSION == 2 | |
340 Py_SetProgramName(&executable[0]); /* optional but recommended */ | |
341 #else | |
342 std::wstring wide(executable.begin(), executable.end()); | |
343 Py_SetProgramName(&wide[0]); /* optional but recommended */ | |
344 Py_UnbufferedStdioFlag = 1; // Write immediately to stdout after "sys.stdout.flush()" (only in Python 3.x) | |
345 #endif | |
346 | |
347 Py_InspectFlag = 1; // Don't exit the Orthanc process on Python error | |
348 | |
28
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
349 verbose_ = verbose; |
0 | 350 if (verbose) |
351 { | |
352 Py_VerboseFlag = 1; | |
353 } | |
354 | |
355 #if PY_MAJOR_VERSION >= 3 | |
356 PyImport_AppendInittab(moduleName_.c_str(), InitializeModule); | |
357 #endif | |
358 | |
359 #if PY_VERSION_HEX < 0x03020000 /* 3.2.0 */ | |
360 /** | |
361 * "Changed in version 3.2: This function cannot be called before | |
362 * Py_Initialize() anymore." | |
363 **/ | |
364 if (!PyEval_ThreadsInitialized()) | |
365 { | |
366 PyEval_InitThreads(); | |
367 } | |
368 #endif | |
369 | |
370 Py_InitializeEx(0 /* Python is embedded, skip signal handlers */); | |
371 | |
12 | 372 #if 0 |
0 | 373 #if PY_MAJOR_VERSION == 2 |
374 std::cout << Py_GetPath() << std::endl; | |
375 std::cout << Py_GetProgramName() << std::endl; | |
376 std::cout << Py_GetProgramFullPath() << std::endl; | |
377 #else | |
378 std::wcout << Py_GetPath() << std::endl; | |
379 std::wcout << Py_GetProgramName() << std::endl; | |
380 std::wcout << Py_GetProgramFullPath() << std::endl; | |
381 #endif | |
12 | 382 #endif |
0 | 383 |
384 #if (PY_VERSION_HEX >= 0x03020000 /* 3.2.0 */ && \ | |
385 PY_VERSION_HEX < 0x03070000 /* 3.7.0 */) | |
386 /** | |
387 * Changed in version 3.7: This function is now called by | |
388 * Py_Initialize(), so you don't have to call it yourself anymore. | |
389 **/ | |
390 if (!PyEval_ThreadsInitialized()) | |
391 { | |
392 PyEval_InitThreads(); | |
393 } | |
394 #endif | |
395 | |
396 | |
397 #if PY_MAJOR_VERSION == 2 | |
398 InitializeModule(); | |
399 #endif | |
400 | |
401 // https://fr.slideshare.net/YiLungTsai/embed-python => slide 26 | |
402 interpreterState_ = PyEval_SaveThread(); | |
403 } | |
404 | |
405 | |
406 void PythonLock::AddSysPath(const std::string& path) | |
407 { | |
408 /** | |
409 * "It is recommended that applications embedding the Python | |
410 * interpreter for purposes other than executing a single script | |
411 * pass 0 as updatepath, and update sys.path themselves if | |
412 * desired. See CVE-2008-5983." | |
413 * => Set "sys.path.append()" to the directory of the configuration file by default | |
414 **/ | |
415 | |
416 PythonLock lock; | |
417 | |
418 /** | |
419 * Initialization of "sys.path.append()" must be done before loading | |
420 * any module. | |
421 **/ | |
422 | |
423 PyObject *sysPath = PySys_GetObject(const_cast<char*>("path")); | |
424 if (sysPath == NULL) | |
425 { | |
426 OrthancPlugins::LogError("Cannot find sys.path"); | |
427 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); | |
428 } | |
429 | |
430 PyObject* pyPath = PyUnicode_FromString(path.c_str()); | |
431 int result = PyList_Insert(sysPath, 0, pyPath); | |
432 Py_DECREF(pyPath); | |
433 | |
434 if (result != 0) | |
435 { | |
436 OrthancPlugins::LogError("Cannot run sys.path.append()"); | |
437 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); | |
438 } | |
439 } | |
440 | |
441 | |
442 void PythonLock::GlobalFinalize() | |
443 { | |
444 boost::mutex::scoped_lock lock(mutex_); | |
445 | |
446 if (interpreterState_ != NULL) | |
447 { | |
448 PyEval_RestoreThread(interpreterState_); | |
449 interpreterState_ = NULL; | |
450 } | |
451 | |
452 Py_Finalize(); | |
453 } | |
454 | |
455 | |
456 void PythonLock::RaiseException(PyObject* module, | |
457 OrthancPluginErrorCode code) | |
458 { | |
459 if (code != OrthancPluginErrorCode_Success) | |
460 { | |
461 const char* message = OrthancPluginGetErrorDescription(OrthancPlugins::GetGlobalContext(), code); | |
462 | |
463 struct module_state *state = GETSTATE(module); | |
464 if (state->exceptionClass_ == NULL) | |
465 { | |
466 OrthancPlugins::LogError("No Python exception has been registered"); | |
467 } | |
468 else | |
469 { | |
470 PyErr_SetString(state->exceptionClass_, message); | |
471 } | |
472 } | |
473 } | |
28
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
474 |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
475 |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
476 void PythonLock::LogCall(const std::string& message) |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
477 { |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
478 /** |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
479 * For purity, this function should lock the global "mutex_", but |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
480 * "verbose_" cannot change after the initial call to |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
481 * "GlobalInitialize()". |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
482 **/ |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
483 |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
484 if (verbose_) |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
485 { |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
486 OrthancPlugins::LogInfo(message); |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
487 } |
b2bbb516056e
The "Calling Python..." info logs are disabled if "PythonVerbose" is "false"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
488 } |