From e4d9a712bc77577f5ee7fb133cd0a9de56624955 Mon Sep 17 00:00:00 2001 From: Akanksha Trehun Date: Tue, 15 Sep 2026 21:26:44 +0530 Subject: [PATCH] Close the event loop invoke_method creates for itself Every synchronous invoke_method call went through get_running_loop failing with RuntimeError, then created a brand new event loop with asyncio.new_event_loop, ran it to completion, and left it open. Each call leaked a whole event loop, visible as an unclosed event loop ResourceWarning. Now the loop is closed in a finally block, but only when this call created it, not when it reused an already running one. Signed-off-by: Akanksha Trehun --- dapr/clients/http/dapr_invocation_http_client.py | 8 +++++++- .../test_http_service_invocation_client.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/dapr/clients/http/dapr_invocation_http_client.py b/dapr/clients/http/dapr_invocation_http_client.py index 604c483c0..be572cccb 100644 --- a/dapr/clients/http/dapr_invocation_http_client.py +++ b/dapr/clients/http/dapr_invocation_http_client.py @@ -157,11 +157,17 @@ def invoke_method( try: loop = asyncio.get_running_loop() + owns_loop = False except RuntimeError: loop = asyncio.new_event_loop() + owns_loop = True asyncio.set_event_loop(loop) awaitable = self.invoke_method_async( app_id, method_name, data, content_type, metadata, http_verb, http_querystring, timeout ) - return loop.run_until_complete(awaitable) + try: + return loop.run_until_complete(awaitable) + finally: + if owns_loop: + loop.close() diff --git a/tests/clients/test_http_service_invocation_client.py b/tests/clients/test_http_service_invocation_client.py index a0a7aadd6..76a209bcc 100644 --- a/tests/clients/test_http_service_invocation_client.py +++ b/tests/clients/test_http_service_invocation_client.py @@ -65,6 +65,21 @@ def test_basic_invoke(self): self.assertEqual(b'STRING_BODY', response.data) self.assertEqual(self.invoke_url, self.server.request_path()) + def test_invoke_closes_the_event_loop_it_creates(self): + self.server.set_response(b'STRING_BODY') + + import asyncio + import warnings + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + self.client.invoke_method(self.app_id, self.method_name, '') + self.client.invoke_method(self.app_id, self.method_name, '') + + unclosed_loop_warnings = [w for w in caught if 'unclosed event loop' in str(w.message)] + self.assertEqual([], unclosed_loop_warnings) + self.assertTrue(asyncio.get_event_loop_policy().get_event_loop().is_closed()) + def test_coroutine_basic_invoke(self): self.server.set_response(b'STRING_BODY')