Skip to content

SocketModeClient.close() leaks current_session_runner thread (built-in client) #1873

Closed

Description

@animaartificialis

Reproducible in:

$ pip freeze | grep slack
slack_sdk==3.41.0

$ python --version
Python 3.11.2

$ uname -srv
Linux 6.12.57+deb12-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.57-1~bpo12+1 (2025-11-17)

Also reproducible on main (commit at the time of filing) — the relevant lines in slack_sdk/socket_mode/builtin/client.py are unchanged from the v3.41.0 release.

The Slack SDK version

slack_sdk==3.41.0

Python runtime version

Python 3.11.2

OS info

Linux 6.12.57+deb12-amd64

Steps to reproduce:

The built-in SocketModeClient starts three IntervalRunner threads in __init__:

  • current_session_runner — interval 0.1 s (line ~126)
  • current_app_monitor — interval ping_interval (default 5 s) (line ~129)
  • message_processor — interval 0.001 s (line ~134)

close() shuts down current_app_monitor, message_processor, and message_workers, but does not call current_session_runner.shutdown() (lines ~224–232). So every SocketModeClient instance leaks one IntervalRunner thread running a 100 ms loop.

Minimal reproducer (no network, no real token needed):

import threading, time
from slack_sdk.socket_mode.builtin.client import SocketModeClient

c = SocketModeClient(app_token="xapp-fake-not-used")
c.close()
time.sleep(0.5)

print("current_session_runner.is_alive():", c.current_session_runner.is_alive())
print("current_app_monitor.is_alive():    ", c.current_app_monitor.is_alive())
print("message_processor.is_alive():      ", c.message_processor.is_alive())

# Repeat to show the linear leak
for _ in range(5):
    c2 = SocketModeClient(app_token="xapp-fake-not-used")
    c2.close()
time.sleep(0.5)
print("active_count after 5 more cycles:", threading.active_count())

Expected result:

After close(), all three runner threads exit and threading.active_count() returns to its baseline.

Actual result:

current_session_runner.is_alive(): True
current_app_monitor.is_alive():    False
message_processor.is_alive():      False
active_count after 5 more cycles: 7

Each init/close cycle leaks exactly one thread (the current_session_runner). In a long-running watcher that reconnects occasionally — e.g. on transient network blips or when the caller recreates the client in response to is_connected() == False — the leaked threads accumulate. Each one is a 100 ms loop, and combined with the still-running message_processor's 1 ms loop on the live instance, CPU usage climbs noticeably (in our case to 100 % of one core, with 26 threads — 19 in `clock_nanosleep` — after ~2 days of operation against a single workspace).

Proposed fix

One additional shutdown call in `close()`:

```python
def close(self):
self.closed = True
self.auto_reconnect_enabled = False
self.disconnect()
if self.current_session_runner.is_alive(): # <-- added
self.current_session_runner.shutdown() # <-- added
if self.current_app_monitor.is_alive():
self.current_app_monitor.shutdown()
if self.message_processor.is_alive():
self.message_processor.shutdown()
self.message_workers.shutdown()
```

Happy to send a PR if it would help.

Thanks for maintaining the SDK!

Activity

  1. github-actions commented on Jun 15, 2026

    @github-actions

    👋 It looks like this issue has been open for 30 days with no activity. We'll mark this as stale for now, and wait 10 days for an update or for further comment before closing this issue out. If you think this issue needs to be prioritized, please comment to get the thread going again! Maintainers also review issues marked as stale on a regular basis and comment or adjust status if the issue needs to be reprioritized.

  2. github-actions commented on Jun 29, 2026

    @github-actions

    As this issue has been inactive for more than one month, we will be closing it. Thank you to all the participants! If you would like to raise a related issue, please create a new issue which includes your specific details and references this issue number.

  3. WilliamBergamin commented on Sep 3, 2026

    @WilliamBergamin
    Contributor

    Hello @animaartificialis thanks for bringing this up 💯 I was able to reproduce this behavior locally 🥇

    Our bad it seems like our automations closed this issue before we were able to address it

  4. added
    bugM-T: A confirmed bug report. Issues are confirmed when the reproduction steps are documented
    and removed on Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    auto-triage-skipbugM-T: A confirmed bug report. Issues are confirmed when the reproduction steps are documented

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions