33# Licensed under the MIT License. See License.txt in the project root for
44# license information.
55# --------------------------------------------------------------------------
6- """ This module contains functions of managing event loops for the IoTHub client
7- """
6+ """This module contains functions of managing event loops for the IoTHub client"""
7+
88import asyncio
99import threading
1010import logging
1616 "CLIENT_INTERNAL_LOOP" : None ,
1717 "CLIENT_HANDLER_RUNNER_LOOP" : None ,
1818}
19+ # Janus queues bind to the first loop they use, so concurrent callers must receive the same loop.
20+ _loop_creation_lock = threading .Lock ()
1921
2022
2123def _cleanup ():
2224 """Clear all running loops and end respective threads.
2325 ONLY FOR TESTING USAGE
2426 By using this function, you can wipe all global loops.
27+ Do not call while clients or inboxes are still in use.
2528 DO NOT USE THIS IN PRODUCTION CODE
2629 """
27- for loop_name , loop in loops .items ():
28- if loop is not None :
29- logger .debug ("Stopping event loop - {}" .format (loop_name ))
30- loop .call_soon_threadsafe (loop .stop )
31- # NOTE: Stopping the loop will also end the thread, because the only thing keeping
32- # the thread alive was the loop running
33- loops [loop_name ] = None
30+ with _loop_creation_lock :
31+ for loop_name , loop in loops .items ():
32+ if loop is not None :
33+ logger .debug ("Stopping event loop - {}" .format (loop_name ))
34+ loop .call_soon_threadsafe (loop .stop )
35+ # NOTE: Stopping the loop will also end the thread, because the only thing keeping
36+ # the thread alive was the loop running
37+ loops [loop_name ] = None
3438
3539
3640def _make_new_loop (loop_name ):
@@ -45,23 +49,29 @@ def _make_new_loop(loop_name):
4549 loops [loop_name ] = new_loop
4650
4751
52+ def _get_or_create_loop (loop_name ):
53+ loop = loops [loop_name ]
54+ if loop is None :
55+ with _loop_creation_lock :
56+ # Another caller may have created the loop while this caller waited for the lock.
57+ loop = loops [loop_name ]
58+ if loop is None :
59+ _make_new_loop (loop_name )
60+ loop = loops [loop_name ]
61+ return loop
62+
63+
4864def get_client_internal_loop ():
4965 """Return the loop for internal client operations"""
50- if loops ["CLIENT_INTERNAL_LOOP" ] is None :
51- _make_new_loop ("CLIENT_INTERNAL_LOOP" )
52- return loops ["CLIENT_INTERNAL_LOOP" ]
66+ return _get_or_create_loop ("CLIENT_INTERNAL_LOOP" )
5367
5468
5569def get_client_handler_runner_loop ():
5670 """Return the loop for handler runners"""
57- if loops ["CLIENT_HANDLER_RUNNER_LOOP" ] is None :
58- _make_new_loop ("CLIENT_HANDLER_RUNNER_LOOP" )
59- return loops ["CLIENT_HANDLER_RUNNER_LOOP" ]
71+ return _get_or_create_loop ("CLIENT_HANDLER_RUNNER_LOOP" )
6072
6173
6274def get_client_handler_loop ():
6375 """Return the loop for invoking user-provided handlers on the client"""
6476 # TODO: Try and store the user loop somehow
65- if loops ["CLIENT_HANDLER_LOOP" ] is None :
66- _make_new_loop ("CLIENT_HANDLER_LOOP" )
67- return loops ["CLIENT_HANDLER_LOOP" ]
77+ return _get_or_create_loop ("CLIENT_HANDLER_LOOP" )
0 commit comments