Skip to content

Close function doesn't actually close everything #191

@agitelzon

Description

@agitelzon

Is this a support request?
Support Ticket #31907

Describe the bug
We are running a python flask app on a set of linux servers and in an AWS lambda. In our app, we create a ldclient instance and try to close() it when we are done. The ldclient.get().close() function call didn't close all threads. The left over threads pile up and over time hit the system thread limit, because the application process is still running and is called on a new task. We start getting an error of RuntimeError: can't start new thread

In short, the bug is that the close() function didn't close all threads. If set_config get's called a 2nd time, as documented, a new ldclient instance is created, the old one never completely goes away.

To reproduce

import atexit
import ldclient
import threading

def __set_launchdarkly_configuration(app):
    ld_key = config.envars.get("LAUNCHDARKLY_SDK_KEY")
    if ld_key:
        ld_config = ldclient.config.Config(ld_key)
    else:
        ld_config = ldclient.config.Config("NOT_CONFIGURED", offline=True)
        logger.warning(
            "LAUNCHDARKLY_SDK_KEY is not set, feature flags will always return defaults"
        )
    ldclient.set_config(ld_config)
    atexit.register(lambda: ldclient.get().close())

    @atexit.register
    def teardown_launchdarkly_configuration(exception=None):
        logger.info(f"teardown launchdarkly_configuration, exception?: {exception}")
        logger.info(f"Number of active threads: {threading.active_count()}")
 
# Call __set_launchdarkly_configuration function, ldclient.get(), and client.variation

Expected behavior
Ultimately, I expected the function ldclient.get().close() to close all threads and release all ldclient objects. I would expect my next ldclient.get().is_initialized() to be false because I closed ldclient so it should be in a state where I have to call set_config

I expected ldclient.set_config(ld_config) to be smart enough to only create new instances if there doesn't exist an active instance or if the config values changed from the previous call. I know that ldclient.get() enforces the singleton pattern, then I would expect `ldclient.set_config() to as well.

Maybe there should be something like a get_config function to allow me to verify that the settings in code are the same as the active config and then I could re-use the existing ldclient instead of creating a new one.

Logs

builtins.RuntimeError: can't start new thread
Traceback (most recent call last):
<removed>
File /function/ldclient/__init__.py, line 42, in set_config
	{code_line}
File /function/ldclient/client.py, line 97, in __init__
	{code_line}
File /function/ldclient/client.py, line 120, in _set_event_processor
	{code_line}
File /function/ldclient/event_processor.py, line 396, in __init__
	{code_line}
File /function/ldclient/repeating_timer.py, line 17, in start
	{code_line}
File /usr/local/lib/python3.7/threading.py, line 852, in start
	{code_line}
RuntimeError: can't start new thread

SDK version
We updated to version 7.5.1 and still saw this error. We tried v8.0.0 as well, same issue.

Language version, developer tools
Python 3.7

OS/platform
Docker container running python:3.7-slim and Ubuntu 20.04

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions