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,100 @@ 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+ // Saves the current thread state, releasing the GIL
316+ // Does not otherwise alter thread state in any way
317+ python_gil_unlock ()
318+ : _previous_thread_state(PyEval_SaveThread())
319+ { }
320+
321+ // Calls release on gil_lock and, if necessary, saves the current thread state
322+ // if should_reacquire is true, reacquire is called on gil_lock during destruction
323+ python_gil_unlock (python_gil_lock* const gil_lock, bool should_reacquire)
324+ : _gil_lock(gil_lock), _should_reacquire(should_reacquire)
325+ {
326+ if (_gil_lock->release () != nullptr ) {
327+ _previous_thread_state = PyEval_SaveThread ();
328+ }
329+ }
330+
331+ ~python_gil_unlock ()
332+ {
333+ if (_previous_thread_state != nullptr ) {
334+ PyEval_RestoreThread (_previous_thread_state);
335+ }
336+ if (_should_reacquire) {
337+ assert (_gil_lock != nullptr );
338+ _gil_lock->reacquire ();
339+ }
340+ }
341+
342+ python_gil_unlock (const python_gil_unlock&) = delete ;
343+
344+ python_gil_unlock& operator =(const python_gil_unlock&) = delete ;
345+
346+ private:
347+ PyThreadState* _previous_thread_state = nullptr ;
348+ python_gil_lock* const _gil_lock = nullptr ;
349+ const bool _should_reacquire = false ;
350+
351+ }; // class python_gil_unlock
352+
269353 struct RuleCallWrapper
270354 {
271355 RuleCallWrapper (irods::callback& effect_handler, std::string rule_name)
@@ -538,6 +622,7 @@ static irods::error rule_exists(const irods::default_re_ctx&, const std::string&
538622 }
539623 catch (const bp::error_already_set&) {
540624 const std::string formatted_python_exception = extract_python_exception ();
625+ python_gil_unlock gil_release (&gil_lock, false );
541626 // clang-format off
542627 log_re::error ({
543628 {" rule_engine_plugin" , rule_engine_name},
@@ -583,6 +668,7 @@ static irods::error list_rules(const irods::default_re_ctx&, std::vector<std::st
583668 }
584669 catch (const bp::error_already_set&) {
585670 const std::string formatted_python_exception = extract_python_exception ();
671+ python_gil_unlock gil_release (&gil_lock, false );
586672 // clang-format off
587673 log_re::error ({
588674 {" rule_engine_plugin" , rule_engine_name},
@@ -660,6 +746,7 @@ static irods::error exec_rule(const irods::default_re_ctx&,
660746 catch (const bp::error_already_set&) {
661747 const std::string formatted_python_exception = extract_python_exception ();
662748 // clang-format off
749+ python_gil_unlock gil_release (&gil_lock, false );
663750 log_re::error ({
664751 {" rule_engine_plugin" , rule_engine_name},
665752 {" log_message" , " caught python exception" },
@@ -849,6 +936,7 @@ static irods::error exec_rule_text(const irods::default_re_ctx&,
849936 rule_function (rule_arguments_python, CallbackWrapper{effect_handler}, rei));
850937 }
851938 else {
939+ python_gil_unlock gil_release (&gil_lock, false );
852940 // clang-format off
853941 log_re::error ({
854942 {" rule_engine_plugin" , rule_engine_name},
@@ -860,6 +948,7 @@ static irods::error exec_rule_text(const irods::default_re_ctx&,
860948 }
861949 catch (const bp::error_already_set&) {
862950 const std::string formatted_python_exception = extract_python_exception ();
951+ python_gil_unlock gil_release (&gil_lock, false );
863952 // clang-format off
864953 log_re::error ({
865954 {" rule_engine_plugin" , rule_engine_name},
@@ -971,6 +1060,7 @@ static irods::error exec_rule_expression(irods::default_re_ctx&,
9711060 }
9721061 catch (const bp::error_already_set&) {
9731062 const std::string formatted_python_exception = extract_python_exception ();
1063+ python_gil_unlock gil_release (&gil_lock, false );
9741064 // clang-format off
9751065 log_re::error ({
9761066 {" rule_engine_plugin" , rule_engine_name},
0 commit comments