{"id":7789,"library":"threadloop","title":"Threadloop: Tornado IOLoop Backed Concurrent Futures","description":"Threadloop is a Python library that enables running Tornado coroutines from synchronous Python code by leveraging a thread pool, backed by Tornado's IOLoop. It provides a `ThreadLoopExecutor` that mimics the `concurrent.futures.ThreadPoolExecutor` API. The library is currently at version 1.0.2 and was last updated in 2016, indicating it is no longer actively maintained but generally functional.","status":"maintenance","version":"1.0.2","language":"en","source_language":"en","source_url":"https://github.com/breerly/threadloop","tags":["tornado","asyncio","threading","concurrent-futures","ioloop","concurrency"],"install":[{"cmd":"pip install threadloop","lang":"bash","label":"Install stable release"}],"dependencies":[{"reason":"Core functionality relies on Tornado's IOLoop for managing asynchronous operations. Specifically, `tornado>=4.0.0` is required.","package":"tornado","optional":false}],"imports":[{"symbol":"ThreadLoopExecutor","correct":"from threadloop import ThreadLoopExecutor"}],"quickstart":{"code":"import tornado.gen\nfrom threadloop import ThreadLoopExecutor\n\n@tornado.gen.coroutine\ndef my_coroutine(name):\n    print(f\"Hello from {name} in coroutine!\")\n    yield tornado.gen.sleep(0.1) # Simulate async work\n    print(f\"Goodbye from {name}!\")\n    return f\"Result from {name}\"\n\ndef sync_function(x, y):\n    print(f\"Running sync_function with {x} and {y}\")\n    return x + y\n\nif __name__ == '__main__':\n    # Example 1: Running a Tornado coroutine in the ThreadLoopExecutor\n    with ThreadLoopExecutor() as executor:\n        future_coro = executor.submit(my_coroutine, \"CoroutineWorker\")\n        result_coro = future_coro.result() # Blocks until coroutine completes\n        print(f\"Coroutine result: {result_coro}\")\n\n    # Example 2: Running a regular synchronous function\n    with ThreadLoopExecutor() as executor:\n        future_sync = executor.submit(sync_function, 10, 20)\n        result_sync = future_sync.result()\n        print(f\"Sync function result: {result_sync}\")\n","lang":"python","description":"This quickstart demonstrates how to initialize `ThreadLoopExecutor` as a context manager and submit both Tornado coroutines and regular synchronous functions. The `submit` method returns a future, and `.result()` can be called to block until the task completes and retrieve its return value. Note that `tornado.gen.coroutine` is used for demonstration, which is an older decorator; modern Tornado often uses `async def` functions."},"warnings":[{"fix":"Thoroughly test `threadloop` with your specific Python and Tornado versions. Consider modern alternatives like `concurrent.futures.ThreadPoolExecutor` for general threading or `asyncio` with `loop.run_in_executor` for integrating blocking calls into an event loop for new projects.","message":"The `threadloop` library has not been updated since 2016 (version 1.0.2) and may have compatibility issues with newer Python (3.6+) and Tornado versions. Newer Python versions, especially 3.13+, introduce significant changes to threading (e.g., free-threaded CPython without GIL by default), which might affect `threadloop`'s behavior or performance.","severity":"breaking","affected_versions":"<=1.0.2"},{"fix":"Always retrieve the result or check for exceptions on `Future` objects returned by `submit()` or `map()`. For example, `future.result()` will re-raise any exception that occurred in the worker thread. Alternatively, use `future.add_done_callback(error_handler)` to process results or errors asynchronously.","message":"Tasks submitted to `ThreadLoopExecutor` (or any `concurrent.futures.ThreadPoolExecutor`) can fail silently if their `Future` objects are not inspected (e.g., by calling `.result()`, `.exception()`, or attaching `add_done_callback`). Exceptions raised within submitted tasks will not propagate to the main thread unless explicitly retrieved.","severity":"gotcha","affected_versions":"All"},{"fix":"For CPU-bound parallelism, use `multiprocessing.Pool` or `concurrent.futures.ProcessPoolExecutor` which utilize separate processes, bypassing the GIL. For I/O-bound tasks, threading is generally effective. Be aware that Python 3.13+ with free-threaded builds can offer true parallelism for CPU-bound threaded code.","message":"Python's Global Interpreter Lock (GIL) means that CPU-bound tasks executed within `ThreadLoopExecutor` (which uses threads) will not achieve true parallel execution across multiple CPU cores in standard CPython interpreters (pre-3.13 free-threaded builds). Threads are best for I/O-bound tasks where the GIL is released during blocking operations.","severity":"gotcha","affected_versions":"All (pre-Python 3.13 free-threaded builds)"},{"fix":"Always use `ThreadLoopExecutor` as a context manager (`with ThreadLoopExecutor() as executor:`) to ensure proper shutdown and resource management. If manually managing, ensure `executor.shutdown()` is called only after all tasks have been submitted and you are ready to terminate the pool.","message":"Attempting to submit tasks to a `ThreadLoopExecutor` after it has been shut down will result in a `RuntimeError` or similar exception. This can happen if the executor's lifecycle is not managed correctly, especially when not using it as a context manager.","severity":"gotcha","affected_versions":"All"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Pass the function object without parentheses: `executor.submit(my_function, arg1, arg2)` instead of `executor.submit(my_function(arg1, arg2))`. Arguments should be passed separately to `submit` or within the iterable for `map`.","cause":"Passing the result of a function call (e.g., `my_function()`) instead of the function object itself (`my_function`) to `executor.submit()` or `executor.map()`. This means the function is executed immediately, and its return value (often a non-callable) is then passed to `submit`.","error":"TypeError: 'str' object is not callable"},{"fix":"Ensure you are not attempting to `join()` a thread from within that same thread. When using `ThreadLoopExecutor`, rely on its context manager for proper shutdown (`with ... as executor:`) which handles joining implicitly. If managing `Thread` objects directly, ensure `thread.start()` is called before `thread.join()`.","cause":"Attempting to call `join()` on the current thread, which would cause a deadlock, or attempting to `join()` a thread before it has been started. While `ThreadLoopExecutor` manages its own joining, incorrect manual interaction with underlying `Thread` objects can lead to this.","error":"RuntimeError: cannot join current thread"}]}