Repository: dbt-labs/dbt-core

Issue:
Unclosed connections prevent dbt from exiting on Snowflake with keepalives enabled
## Issue

### Issue description
The `client_session_keep_alive` config for snowflake-connector-python is implemented using a threaded heartbeat. During the `dbt docs generate` command (and possibly others), dbt doesn't close 100% of the connections it opens. As a result, the snowflake-connector-python threads continue to heartbeat in perpetuity, preventing dbt from exiting. See the comment [here](https://github.com/snowflakedb/snowflake-connector-python/issues/104#issuecomment-424350077) while slowly and deliberately raising the palm of your hand  into the vicinity of your face.

We should, in general, ensure that dbt always closes any connections that it opens.

### Results
dbt finishes executing, but control is not returned to the terminal. Instead, python hangs on a lock held by snowflake-connector-python, and the user needs to ctrl-c to exit.

### System information
The output of `dbt --version`:
```
0.12.2
```

The operating system you're running on: Any

### Steps to reproduce
1. Set up a Snowflake profile, configuring `client_session_keep_alive` to be `True`
2. Run `dbt docs generate`
3. wait forever


Discussion:
Step 3 is my favorite
I don't want to take credit for this since I can't tell you how it happened, but I am ~90% sure this is fixed in 0.13.0, possibly as part of the connection management work I did. I sure can't reproduce it, and I can easily reproduce it on 0.12.2.
@beckjake So we're seeing this now. We're running in GitLab CI and it hangs on "flush usage events". Setting to False fixes it. Here's some info about the runners if that's useful. https://docs.gitlab.com/ee/user/gitlab_com/#shared-runners

The command we're using is `dbt docs generate --profiles-dir profile --target prod`

Edit:
We're on 0.14.0 and we see this after upgrading to 0.14.1.
Thanks @tayloramurphy - I just moved this card into the LMA milestone + reopened the issue. We'll check it out!
We're also seeing this with `run-operation` on 0.14.0. First observed in Kubernetes but seems like it can be reproduced from other envs as well.

Resolving patch:
diff --git a/core/dbt/main.py b/core/dbt/main.py
--- a/core/dbt/main.py
+++ b/core/dbt/main.py
@@ -23,7 +23,7 @@
 import dbt.task.run_operation as run_operation_task
 from dbt.task.list import ListTask
 from dbt.task.rpc_server import RPCServerTask
-from dbt.adapters.factory import reset_adapters
+from dbt.adapters.factory import reset_adapters, cleanup_connections
 
 import dbt.tracking
 import dbt.ui.printer
@@ -123,6 +123,15 @@ def initialize_config_values(parsed):
     cfg.set_values(parsed.profiles_dir)
 
 
+@contextmanager
+def adapter_management():
+    reset_adapters()
+    try:
+        yield
+    finally:
+        cleanup_connections()
+
+
 def handle_and_check(args):
     with log_manager.applicationbound():
         parsed = parse_args(args)
@@ -143,10 +152,10 @@ def handle_and_check(args):
 
             initialize_config_values(parsed)
 
-            reset_adapters()
+            with adapter_management():
 
-            task, res = run_from_args(parsed)
-            success = task.interpret_results(res)
+                task, res = run_from_args(parsed)
+                success = task.interpret_results(res)
 
             return res, success
 


Test patch: