API calls seem to spawn threads without ever releasing them. I'm not sure if this is an issue with aiohttp or with how slackclient uses it. Or perhaps I am misusing/misunderstanding the client?
slackclient version: 2.1.0
python version: 3.6.7
OS version(s): Ubuntu 18.04.1 LTS (LXC on Proxmox)
chat_postMessage or anything that involves an api call.htop or top -Hpython threads (they are released once the program ends, though, so you'll have to watch while the program is running)Each API call should make its request and then release/end the thread.
Upon API call completion, sleeping threads are leftover. I think this could be a slow memory leak if many calls are made over time.

Code to generate the screenshot above:
import os
import slack
import time
from dotenv import load_dotenv
load_dotenv()
client = slack.WebClient(token=os.getenv("SLACK_TOKEN"))
for x in range(10):
print("Posting message")
client.chat_postMessage(
channel=os.getenv("TEST_CHANNEL_ID"),
text="Hello from your app! :tada:"
)
time.sleep(1)
time.sleep(10)
I also tested running async and got the same result. Please let me know if you need any additional information from me. Thank you for your work on this great client!
I also noted this while trying to check where all the threads came from new threads created while doing api calls:
client.chat_postMessage(channel=channel, text=text)
Got more and more threads lurking about:
slackclient version: 2.0.1 and 2.1.0 (same issue after upgrade)
python version: 3.7.3
OS version(s): Ubuntu 18.04.2 LTS
Before chat_postMessage call:
Threads: 5
threading.CurrentThread() = <Thread(ThreadPoolExecutor-4_0, started daemon 139764785219328)>
threading.enumerate():
<_MainThread(MainThread, started 139764916946752)>
<Thread(ThreadPoolExecutor-0_0, started daemon 139764810880768)>
<Thread(ThreadPoolExecutor-0_1, started daemon 139764793612032)>
<Thread(ThreadPoolExecutor-2_0, started daemon 139764776826624)>
<Thread(ThreadPoolExecutor-4_0, started daemon 139764785219328)>
After chat_postMessage call:
Threads: 6
threading.CurrentThread() = <Thread(ThreadPoolExecutor-4_0, started daemon 139764785219328)>
threading.enumerate():
<_MainThread(MainThread, started 139764916946752)>
<Thread(ThreadPoolExecutor-0_0, started daemon 139764810880768)>
<Thread(ThreadPoolExecutor-0_1, started daemon 139764793612032)>
<Thread(ThreadPoolExecutor-2_0, started daemon 139764776826624)>
<Thread(ThreadPoolExecutor-4_0, started daemon 139764785219328)>
<Thread(ThreadPoolExecutor-2_1, started daemon 139764766238464)>
Is anyone other than me and @iamlawn seeing this behavior? Are we just doing something incorrectly?
Hi folks, this is indeed a bug! I'll work on having this fixed soon.
Thank you! If it helps, through my own digging it seems like the threads are spawned around the aiohttp sessions created in this method. On a whim, I tried saving newly created sessions to self.session which stopped spawning additional threads for requests sent within a few seconds, but new treads are still spawned for requests made after 15 seconds or so.
If I understand this issue correctly, the pull request #544 has addressed this issue and the fix is included since version. 2.3.1.
@mkwarman
If you're still interested in this issue, could you verify if the issue has been resolved with 2.3.1+?
Hi @seratch, thank you for giving an update on this. I am still seeing the behavior as of slackclient 2.5.0. In the attached image I have one gunicorn worker process that has just made a few API calls after restarting a few minutes ago. The threads are still shown in htop. As far as I know, they aren't causing any problems since they're basically idle, but I would expect them to go away once they are no longer being used. In my app, I use a Flask endpoint to receive slack events and then make posts using a single slack client instantiated on startup.

@mkwarman Thanks for your quick response! This is really helpful. I will check this out.
@mkwarman
I just ran a small unit test on my local machine (it's not Linux but macOS though). I observed the difference between Python 3.6 and 3.8. If you don't mind it, could you verify the behavior with Python 3.8 in your environment? I'll also verify it on Linux later but if I could get your help, I'm grateful to you.
import logging
import multiprocessing
import os
import threading
import unittest
from integration_tests.helpers import async_test
from slack import WebClient
class TestWebClient(unittest.TestCase):
"""Runs integration tests with real Slack API
https://github.com/slackapi/python-slackclient/issues/480
"""
def setUp(self):
self.logger = logging.getLogger(__name__)
self.user_token = os.environ["SLACK_BOT_TOKEN"]
self.sync_client: WebClient = WebClient(token=self.user_token, run_async=False)
self.async_client: WebClient = WebClient(token=self.user_token, run_async=True)
def tearDown(self):
pass
def test_issue_480_processes(self):
client = self.sync_client
before = len(multiprocessing.active_children())
for idx in range(10):
response = client.api_test()
self.assertIsNotNone(response)
after = len(multiprocessing.active_children())
self.assertEqual(0, after - before)
@async_test
async def test_issue_480_processes_async(self):
client = self.async_client
before = len(multiprocessing.active_children())
for idx in range(10):
response = await client.api_test()
self.assertIsNotNone(response)
after = len(multiprocessing.active_children())
self.assertEqual(0, after - before)
# fails with Python 3.6
def test_issue_480_threads(self):
client = self.sync_client
before = threading.active_count()
for idx in range(10):
response = client.api_test()
self.assertIsNotNone(response)
after = threading.active_count()
self.assertEqual(0, after - before)
# fails with Python 3.6
@async_test
async def test_issue_480_threads_async(self):
client = self.async_client
before = threading.active_count()
for idx in range(10):
response = await client.api_test()
self.assertIsNotNone(response)
after = threading.active_count()
self.assertEqual(0, after - before)
Running pytest…
/Users/ksera/github/python-slackclient/env3.6/bin/python -m pytest --cov-report=xml --cov=/Users/ksera/github/python-slackclient/slack integration_tests/web/test_issue_480.py
========================================================================================================= test session starts ==========================================================================================================
platform darwin -- Python 3.6.10, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /Users/ksera/github/python-slackclient, inifile: pytest.ini
plugins: cov-2.8.1
collected 4 items
integration_tests/web/test_issue_480.py ..FF [100%]
=============================================================================================================== FAILURES ===============================================================================================================
_________________________________________________________________________________________________ TestWebClient.test_issue_480_threads _________________________________________________________________________________________________
self = <test_issue_480.TestWebClient testMethod=test_issue_480_threads>
def test_issue_480_threads(self):
client = self.sync_client
before = threading.active_count()
for idx in range(10):
response = client.api_test()
self.assertIsNotNone(response)
after = threading.active_count()
> self.assertEqual(0, after - before)
E AssertionError: 0 != 10
integration_tests/web/test_issue_480.py:53: AssertionError
args = (<test_issue_480.TestWebClient testMethod=test_issue_480_threads_async>,), kwargs = {}, current_loop = <_UnixSelectorEventLoop running=False closed=False debug=False>
def wrapper(*args, **kwargs):
current_loop: AbstractEventLoop = asyncio.get_event_loop()
> return current_loop.run_until_complete(coro(*args, **kwargs))
integration_tests/helpers.py:13:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.pyenv/versions/3.6.10/lib/python3.6/asyncio/base_events.py:488: in run_until_complete
return future.result()
integration_tests/web/test_issue_480.py:63: in test_issue_480_threads_async
self.assertEqual(0, after - before)
E AssertionError: 0 != 10
---------- coverage: platform darwin, python 3.6.10-final-0 ----------
Coverage XML written to file coverage.xml
======================================================================================================= short test summary info ========================================================================================================
FAILED integration_tests/web/test_issue_480.py::TestWebClient::test_issue_480_threads - AssertionError: 0 != 10
FAILED integration_tests/web/test_issue_480.py::TestWebClient::test_issue_480_threads_async - AssertionError: 0 != 10
===================================================================================================== 2 failed, 2 passed in 18.50s =====================================================================================================
(env3.6) $
Running pytest…
/Users/ksera/github/python-slackclient/env/bin/python -m pytest --cov-report=xml --cov=/Users/ksera/github/python-slackclient/slack integration_tests/web/test_issue_480.py
========================================================================================================== test session starts ==========================================================================================================
platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /Users/ksera/github/python-slackclient, inifile: pytest.ini
plugins: cov-2.8.1
collected 4 items
integration_tests/web/test_issue_480.py .... [100%]
---------- coverage: platform darwin, python 3.8.2-final-0 -----------
Coverage XML written to file coverage.xml
==================================================================================================== 4 passed, 81 warnings in 19.48s ====================================================================================================
(env) $
Hey @seratch thank you very much for researching this. I built python 3.8.2 locally and created a new virtualenv pointed to it. After restarting my project and triggering a few API calls I can confirm that the issue is no longer present, I do not see any extra threads hanging around anymore 👍

@mkwarman Happy to hear that! Allow me to close this issue now.