Repository: scoutapp/scout_apm_python

Issue:
Core Agent inheriting and holding open sockets from parent
Reported via Slack:

```

Mike O'Toole 9:12 AM
hey there -- are there any instructions available on installing/starting the core-agent separately from a django app?
cschneid:scout-orange: 9:13 AM
@Mike O'Toole - the binary gets downloaded to /tmp/scout_apm_core/… and has a help command.
Mike O'Toole 9:13 AM
we're running into an issue where the core-agent process seems to hang on to the ports that are uWSGI is binding when it first starts up. Then when we try to restart our uWSGI process, it tries to bind the ports again, and it can't because the core-agent process has still got them
cschneid:scout-orange: 9:14 AM
That is odd, it’s a totally different process, so shouldn’t ever have access to port file descriptors.
Mike O'Toole 9:15 AM
yeah -- that's what i thought too, but looking at lsof this is what im seeing:
core-agen 23301         chuck-uwsgi    8u  IPv4 7817953      0t0  TCP *:3031 (LISTEN)
core-agen 23301         chuck-uwsgi    9u  IPv4 7817954      0t0  TCP *:8080 (LISTEN)
cschneid:scout-orange: 9:15 AM
ok, well that’s not intended… :slightly_smiling_face:
Mike O'Toole 9:15 AM
3031/8080 are the ports that uWSGI is binding
cschneid:scout-orange: 9:16 AM
For now, you’re looking for core-agent start --socket-path <match the default, or the explicit config> 
Mike O'Toole 9:16 AM
sweet, thanks
cschneid:scout-orange: 9:17 AM
the default socket path is:
    def derive_socket_path(self):
        return "{}/{}/core-agent.sock".format(
            self.config.value("core_agent_dir"),
            self.config.value("core_agent_full_name"),
which would be something like: /tmp/scout_apm_core/scout_apm_core-latest-x86_64-apple-darwin/core-agent.sock
I’m really confused by the core agent holding onto those ports. We launch via             subprocess.check_call( ...  which I suppose would maintain file descriptors?
If close_fds is true, all file descriptors except 0, 1 and 2 will be closed before the child process is executed. (POSIX only). The default varies by platform: Always true on POSIX. 
from the docs.
sounds like it should have closed the socket handles?
Mike O'Toole 9:22 AM
hmmm weird
cschneid:scout-orange: 9:22 AM
What version of python are you on? I can try to reproduce
Mike O'Toole 9:24 AM
python 2.7.6
ahh, here's someone having a related issue with uWSGI

https://stackoverflow.com/questions/48376664/uwsgi-subprocess-create-a-new-process-the-socket-close-wait

sounds like that config option is what we need
cschneid:scout-orange: 9:25 AM
ok, the docs I shared were from 3.5, I wonder if that behavior changed
```


Discussion:
```
Mike O'Toole 9:39 AM
sweet -- looks like the uwsgi flag close-on-exec2 fixes the issue
```


I'll look into setting `close_fds`, and maybe there's something we can do to auto-detect uWSGI and do a thing there. This may also relate to #213 which had some weird process behaviour in a bash subshell on Python 2.

Resolving patch:
diff --git a/src/scout_apm/core/core_agent_manager.py b/src/scout_apm/core/core_agent_manager.py
--- a/src/scout_apm/core/core_agent_manager.py
+++ b/src/scout_apm/core/core_agent_manager.py
@@ -59,12 +59,15 @@ def download(self):
     def run(self):
         try:
             subprocess.check_call(
-                self.agent_binary()
-                + self.daemonize_flag()
-                + self.log_level()
-                + self.log_file()
-                + self.config_file()
-                + self.socket_path()
+                (
+                    self.agent_binary()
+                    + self.daemonize_flag()
+                    + self.log_level()
+                    + self.log_file()
+                    + self.config_file()
+                    + self.socket_path()
+                ),
+                close_fds=True,
             )
         except Exception:
             # TODO detect failure of launch properly


Test patch: