11// include this first to fix macro redef warnings
22#include < pyconfig.h>
33
4+ #include < cassert>
45#include < cstdint>
56#include < cstring>
67#include < ctime>
2728#include < irods/rodsErrorTable.h>
2829#include < irods/irods_default_paths.hpp>
2930#include < irods/irods_error.hpp>
31+ #include < irods/irods_exception.hpp>
3032#include < irods/irods_logger.hpp>
3133#include < irods/irods_re_plugin.hpp>
3234#include < irods/irods_re_structs.hpp>
@@ -254,18 +256,97 @@ namespace
254256
255257 ~python_gil_lock ()
256258 {
257- PyGILState_Release (_previous_gil_state);
259+ if (!_released) {
260+ PyGILState_Release (_previous_gil_state);
261+ }
258262 }
259263
260264 python_gil_lock (const python_gil_lock&) = delete ;
261265
262266 python_gil_lock& operator =(const python_gil_lock&) = delete ;
263267
268+ inline bool released ()
269+ {
270+ return _released;
271+ }
272+
273+ // restores python state to prior to last PyGILState_Ensure call
274+ // returns active PyThreadState
275+ // doesn't necessarily release the GIL, only this instance's hold on it.
276+ // if GIL was acquired further up the stack, it will still be locked.
277+ inline PyThreadState* release ()
278+ {
279+ if (_released) {
280+ THROW (RULE_ENGINE_ERROR, " release called on already-released python_gil_lock" );
281+ }
282+
283+ PyThreadState* current_thread_state = PyThreadState_Get ();
284+ if (current_thread_state->gilstate_counter <= 1 ) {
285+ current_thread_state = nullptr ;
286+ }
287+
288+ PyGILState_Release (_previous_gil_state);
289+
290+ _released = true ;
291+ return current_thread_state;
292+ }
293+
294+ // reacquires GIL after a call to release
295+ inline void reacquire ()
296+ {
297+ if (_released) {
298+ THROW (RULE_ENGINE_ERROR, " reacquire called on non-released python_gil_lock" );
299+ }
300+
301+ _previous_gil_state = PyGILState_Ensure ();
302+ _released = false ;
303+ }
304+
264305 private:
265306 PyGILState_STATE _previous_gil_state; // GIL state prior to calling PyGILState_Ensure
307+ bool _released = false ; // whether or not PyGILState_Release has already been called
266308
267309 }; // class python_gil_lock
268310
311+ // Helper class that unlocks GIL while in scope
312+ class python_gil_unlock
313+ {
314+ public:
315+
316+ python_gil_unlock ()
317+ : _previous_thread_state(PyEval_SaveThread())
318+ { }
319+
320+ python_gil_lock (const python_gil_lock const * gil_lock, bool should_reacquire)
321+ : _gil_lock(gil_lock), _should_reacquire(should_reacquire)
322+ {
323+ if (_gil_lock.release () != nullptr ) {
324+ _previous_thread_state = PyEval_SaveThread ();
325+ }
326+ }
327+
328+ ~python_gil_unlock ()
329+ {
330+ if (_previous_thread_state != nullptr ) {
331+ PyEval_RestoreThread (_previous_thread_state);
332+ }
333+ if (should_reacquire) {
334+ assert (_gil_lock != null);
335+ _gil_lock.reacquire ();
336+ }
337+ }
338+
339+ python_gil_unlock (const python_gil_unlock&) = delete ;
340+
341+ python_gil_unlock& operator =(const python_gil_unlock&) = delete ;
342+
343+ private:
344+ PyThreadState* _previous_thread_state = nullptr ;
345+ const python_gil_lock const * _gil_lock = nullptr ;
346+ bool _should_reacquire = false ;
347+
348+ }; // class python_gil_unlock
349+
269350 struct RuleCallWrapper
270351 {
271352 RuleCallWrapper (irods::callback& effect_handler, std::string rule_name)
@@ -538,6 +619,7 @@ static irods::error rule_exists(const irods::default_re_ctx&, const std::string&
538619 }
539620 catch (const bp::error_already_set&) {
540621 const std::string formatted_python_exception = extract_python_exception ();
622+ python_gil_unlock gil_release (&gil_lock, false );
541623 // clang-format off
542624 log_re::error ({
543625 {" rule_engine_plugin" , rule_engine_name},
@@ -583,6 +665,7 @@ static irods::error list_rules(const irods::default_re_ctx&, std::vector<std::st
583665 }
584666 catch (const bp::error_already_set&) {
585667 const std::string formatted_python_exception = extract_python_exception ();
668+ python_gil_unlock gil_release (&gil_lock, false );
586669 // clang-format off
587670 log_re::error ({
588671 {" rule_engine_plugin" , rule_engine_name},
@@ -660,6 +743,7 @@ static irods::error exec_rule(const irods::default_re_ctx&,
660743 catch (const bp::error_already_set&) {
661744 const std::string formatted_python_exception = extract_python_exception ();
662745 // clang-format off
746+ python_gil_unlock gil_release (&gil_lock, false );
663747 log_re::error ({
664748 {" rule_engine_plugin" , rule_engine_name},
665749 {" log_message" , " caught python exception" },
@@ -849,6 +933,7 @@ static irods::error exec_rule_text(const irods::default_re_ctx&,
849933 rule_function (rule_arguments_python, CallbackWrapper{effect_handler}, rei));
850934 }
851935 else {
936+ python_gil_unlock gil_release (&gil_lock, false );
852937 // clang-format off
853938 log_re::error ({
854939 {" rule_engine_plugin" , rule_engine_name},
@@ -860,6 +945,7 @@ static irods::error exec_rule_text(const irods::default_re_ctx&,
860945 }
861946 catch (const bp::error_already_set&) {
862947 const std::string formatted_python_exception = extract_python_exception ();
948+ python_gil_unlock gil_release (&gil_lock, false );
863949 // clang-format off
864950 log_re::error ({
865951 {" rule_engine_plugin" , rule_engine_name},
@@ -971,6 +1057,7 @@ static irods::error exec_rule_expression(irods::default_re_ctx&,
9711057 }
9721058 catch (const bp::error_already_set&) {
9731059 const std::string formatted_python_exception = extract_python_exception ();
1060+ python_gil_unlock gil_release (&gil_lock, false );
9741061 // clang-format off
9751062 log_re::error ({
9761063 {" rule_engine_plugin" , rule_engine_name},
0 commit comments