3333#include " absl/status/status.h"
3434#include " absl/status/statusor.h"
3535#include " absl/strings/str_format.h"
36+ #include " absl/synchronization/mutex.h"
3637#include " checker/validation_result.h"
3738#include " common/ast.h"
3839#include " common/ast_proto.h"
4344#include " parser/parser_interface.h"
4445#include " runtime/embedder_context.h"
4546#include " runtime/runtime.h"
46- #include " cel_expr_python/free_threading_mutex.h"
4747#include " cel_expr_python/py_cel_activation.h"
4848#include " cel_expr_python/py_cel_arena.h"
4949#include " cel_expr_python/py_cel_env_internal.h"
@@ -129,7 +129,8 @@ absl::StatusOr<PyCelExpression> PyCelExpression::Compile(
129129}
130130
131131PyCelExpression::PyCelExpression (PyCelExpression&& other) noexcept {
132- FreeThreadingLockGuard lock (other.mutex_ );
132+ // Lock other.mutex_ to safely move the Program and expression state.
133+ absl::MutexLock lock (other.mutex_ );
133134 expr_ = std::move (other.expr_ );
134135 env_ = std::move (other.env_ );
135136 cel_program_ = std::move (other.cel_program_ );
@@ -155,7 +156,10 @@ PyCelType PyCelExpression::GetReturnType() {
155156}
156157
157158absl::StatusOr<const cel::Program*> PyCelExpression::GetProgram () {
158- FreeThreadingLockGuard lock (mutex_);
159+ // Lock is needed in both GIL-enabled and free-threaded builds because Eval()
160+ // releases the Python GIL before calling GetProgram(), allowing multiple
161+ // threads to concurrently initialize cel_program_.
162+ absl::MutexLock lock (mutex_);
159163 if (cel_program_) {
160164 return cel_program_.get ();
161165 }
@@ -179,17 +183,26 @@ absl::StatusOr<const cel::Program*> PyCelExpression::GetProgram() {
179183absl::StatusOr<PyCelValue> PyCelExpression::Eval (
180184 const PyCelActivation& activation) {
181185 ABSL_CHECK (PyGILState_Check ());
182- CEL_PYTHON_ASSIGN_OR_RETURN (const cel::Program* program, GetProgram ());
183186 std::shared_ptr<PyCelArena> arena = activation.GetArena ();
184187 std::shared_ptr<PyCelEnvInternal> env = activation.GetEnv ();
185- cel::EmbedderContext embedder_context = cel::EmbedderContext::From (&env);
186- cel::EvaluateOptions options;
187- options.message_factory = env->GetMessageFactory ();
188- options.embedder_context = &embedder_context;
189- CEL_PYTHON_ASSIGN_OR_RETURN (
190- cel::Value result,
191- program->Evaluate (arena->GetArena (), *activation.GetActivation (),
192- std::move (options)));
188+ cel::Value result;
189+ {
190+ // Release the GIL before entering C++ program creation and evaluation to
191+ // prevent lock inversion/deadlock with DescriptorPool's internal mutex
192+ // (C++ Locks -> DescriptorPool Mutex -> Python GIL). Callbacks into Python
193+ // (such as PyCelValueProvider::Provide and PyCelFunctionAdapter::Invoke)
194+ // re-acquire the GIL on demand.
195+ py::gil_scoped_release gil_release;
196+ CEL_PYTHON_ASSIGN_OR_RETURN (const cel::Program* program, GetProgram ());
197+ cel::EmbedderContext embedder_context = cel::EmbedderContext::From (&env);
198+ cel::EvaluateOptions options;
199+ options.message_factory = env->GetMessageFactory ();
200+ options.embedder_context = &embedder_context;
201+ CEL_PYTHON_ASSIGN_OR_RETURN (
202+ result,
203+ program->Evaluate (arena->GetArena (), *activation.GetActivation (),
204+ std::move (options)));
205+ }
193206 return PyCelValue (result, arena, std::move (env));
194207}
195208
0 commit comments