We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Profiling causing gunicorn timeout (flask app)
### Which version of dd-trace-py are you using?

```
ddtrace[profiling]==0.37.0
```

### Which version of the libraries are you using?

```
flask==1.1.2
gunicorn==20.0.4
ddtrace[profiling]==0.37.0
datadog==0.36.0
```


### How can we reproduce your problem?
I'm using a simple hello world app flask application for the demonstration of this problem but the issue is happening also when we tried to integrated the profiling with our Flask project, i thought the issue was related to our project code but in this demo proved that it's a general issue, in `wsgi.py` you can add :

```
import ddtrace.profiling.auto
```

my gunicorn config is the default "simple" one : 

```
gunicorn --bind 0.0.0.0:8000 wsgi:app
```
### What is the result that you get?

```
docker run -p 8000:8000 flask/hello-world
[2020-05-07 15:17:06 +0000] [6] [INFO] Starting gunicorn 20.0.4
[2020-05-07 15:17:06 +0000] [6] [INFO] Listening at: http://0.0.0.0:8000 (6)
[2020-05-07 15:17:06 +0000] [6] [INFO] Using worker: sync
[2020-05-07 15:17:06 +0000] [9] [INFO] Booting worker with pid: 9
[2020-05-07 15:17:52 +0000] [6] [CRITICAL] WORKER TIMEOUT (pid:9)
[2020-05-07 15:17:52 +0000] [9] [INFO] Worker exiting (pid: 9)
[2020-05-07 15:17:52 +0000] [13] [INFO] Booting worker with pid: 13
```

### What is the result that you expected?
When i removed the auto profiler the app works and got the response.
Also strangely when using the "manual" profiler 

```
from ddtrace.profiling import Profiler

prof = Profiler()
prof.start()
``` 

The app works as expected and i get profiling events in my Datadog account, so my believe is that the auto profiler causes a lock somewhere in its code resulting in Gunicorn workers timeout.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ddtrace/profiling/_periodic.py`
Content:
```
1 # -*- encoding: utf-8 -*-
2 import sys
3 import threading
4 import time
5 
6 from ddtrace.profiling import _service
7 from ddtrace.profiling import _nogevent
8 from ddtrace.vendor import attr
9 
10 
11 class PeriodicThread(threading.Thread):
12     """Periodic thread.
13 
14     This class can be used to instantiate a worker thread that will run its `run_periodic` function every `interval`
15     seconds.
16 
17     """
18 
19     _ddtrace_profiling_ignore = True
20 
21     def __init__(self, interval, target, name=None, on_shutdown=None):
22         """Create a periodic thread.
23 
24         :param interval: The interval in seconds to wait between execution of the periodic function.
25         :param target: The periodic function to execute every interval.
26         :param name: The name of the thread.
27         :param on_shutdown: The function to call when the thread shuts down.
28         """
29         super(PeriodicThread, self).__init__(name=name)
30         self._target = target
31         self._on_shutdown = on_shutdown
32         self.interval = interval
33         self.quit = threading.Event()
34         self.daemon = True
35 
36     def stop(self):
37         """Stop the thread."""
38         # NOTE: make sure the thread is alive before using self.quit:
39         # 1. self.quit is Lock-based
40         # 2. if we're a child trying to stop a Thread,
41         #    the Lock might have been locked in a parent process while forking so that'd block forever
42         if self.is_alive():
43             self.quit.set()
44 
45     def run(self):
46         """Run the target function periodically."""
47         while not self.quit.wait(self.interval):
48             self._target()
49         if self._on_shutdown is not None:
50             self._on_shutdown()
51 
52 
53 class _GeventPeriodicThread(PeriodicThread):
54     """Periodic thread.
55 
56     This class can be used to instantiate a worker thread that will run its `run_periodic` function every `interval`
57     seconds.
58 
59     """
60 
61     # That's the value Python 2 uses in its `threading` module
62     SLEEP_INTERVAL = 0.005
63 
64     def __init__(self, interval, target, name=None, on_shutdown=None):
65         """Create a periodic thread.
66 
67         :param interval: The interval in seconds to wait between execution of the periodic function.
68         :param target: The periodic function to execute every interval.
69         :param name: The name of the thread.
70         :param on_shutdown: The function to call when the thread shuts down.
71         """
72         super(_GeventPeriodicThread, self).__init__(interval, target, name, on_shutdown)
73         self._tident = None
74 
75     @property
76     def ident(self):
77         return self._tident
78 
79     def start(self):
80         """Start the thread."""
81         self.quit = False
82         self.has_quit = False
83         self._tident = _nogevent.start_new_thread(self.run, tuple())
84         if _nogevent.threading_get_native_id:
85             self._native_id = _nogevent.threading_get_native_id()
86 
87     def join(self, timeout=None):
88         # FIXME: handle the timeout argument
89         while not self.has_quit:
90             time.sleep(self.SLEEP_INTERVAL)
91 
92     def stop(self):
93         """Stop the thread."""
94         self.quit = True
95 
96     def run(self):
97         """Run the target function periodically."""
98         # Do not use the threading._active_limbo_lock here because it's a gevent lock
99         threading._active[self._tident] = self
100         try:
101             while self.quit is False:
102                 self._target()
103                 slept = 0
104                 while self.quit is False and slept < self.interval:
105                     _nogevent.sleep(self.SLEEP_INTERVAL)
106                     slept += self.SLEEP_INTERVAL
107             if self._on_shutdown is not None:
108                 self._on_shutdown()
109         except Exception:
110             # Exceptions might happen during interpreter shutdown.
111             # We're mimicking what `threading.Thread` does in daemon mode, we ignore them.
112             # See `threading.Thread._bootstrap` for details.
113             if sys is not None:
114                 raise
115         finally:
116             try:
117                 del threading._active[self._tident]
118                 self.has_quit = True
119             except Exception:
120                 # Exceptions might happen during interpreter shutdown.
121                 # We're mimicking what `threading.Thread` does in daemon mode, we ignore them.
122                 # See `threading.Thread._bootstrap` for details.
123                 if sys is not None:
124                     raise
125 
126 
127 def PeriodicRealThread(*args, **kwargs):
128     """Create a PeriodicRealThread based on the underlying thread implementation (native, gevent, etc).
129 
130     This is exactly like PeriodicThread, except that it runs on a *real* OS thread. Be aware that this might be tricky
131     in e.g. the gevent case, where Lock object must not be shared with the MainThread (otherwise it'd dead lock).
132 
133     """
134     if _nogevent.is_module_patched("threading"):
135         return _GeventPeriodicThread(*args, **kwargs)
136     return PeriodicThread(*args, **kwargs)
137 
138 
139 @attr.s
140 class PeriodicService(_service.Service):
141     """A service that runs periodically."""
142 
143     _interval = attr.ib()
144     _worker = attr.ib(default=None, init=False, repr=False)
145 
146     _real_thread = False
147     "Class variable to override if the service should run in a real OS thread."
148 
149     @property
150     def interval(self):
151         return self._interval
152 
153     @interval.setter
154     def interval(self, value):
155         self._interval = value
156         # Update the interval of the PeriodicThread based on ours
157         if self._worker:
158             self._worker.interval = value
159 
160     def start(self):
161         """Start the periodic service."""
162         super(PeriodicService, self).start()
163         periodic_thread_class = PeriodicRealThread if self._real_thread else PeriodicThread
164         self._worker = periodic_thread_class(
165             self.interval,
166             target=self.periodic,
167             name="%s:%s" % (self.__class__.__module__, self.__class__.__name__),
168             on_shutdown=self.on_shutdown,
169         )
170         self._worker.start()
171 
172     def join(self, timeout=None):
173         if self._worker:
174             self._worker.join(timeout)
175 
176     def stop(self):
177         """Stop the periodic collector."""
178         if self._worker:
179             self._worker.stop()
180         super(PeriodicService, self).stop()
181 
182     @staticmethod
183     def on_shutdown():
184         pass
185 
186     @staticmethod
187     def periodic():
188         pass
189
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ddtrace/profiling/_periodic.py b/ddtrace/profiling/_periodic.py
--- a/ddtrace/profiling/_periodic.py
+++ b/ddtrace/profiling/_periodic.py
@@ -71,6 +71,12 @@
         """
         super(_GeventPeriodicThread, self).__init__(interval, target, name, on_shutdown)
         self._tident = None
+        self._periodic_started = False
+        self._periodic_stopped = False
+
+    def _reset_internal_locks(self, is_alive=False):
+        # Called by Python via `threading._after_fork`
+        self._periodic_stopped = True
 
     @property
     def ident(self):
@@ -79,14 +85,22 @@
     def start(self):
         """Start the thread."""
         self.quit = False
-        self.has_quit = False
+        if self._tident is not None:
+            raise RuntimeError("threads can only be started once")
         self._tident = _nogevent.start_new_thread(self.run, tuple())
         if _nogevent.threading_get_native_id:
             self._native_id = _nogevent.threading_get_native_id()
 
+        # Wait for the thread to be started to avoid race conditions
+        while not self._periodic_started:
+            time.sleep(self.SLEEP_INTERVAL)
+
+    def is_alive(self):
+        return not self._periodic_stopped and self._periodic_started
+
     def join(self, timeout=None):
         # FIXME: handle the timeout argument
-        while not self.has_quit:
+        while self.is_alive():
             time.sleep(self.SLEEP_INTERVAL)
 
     def stop(self):
@@ -97,6 +111,9 @@
         """Run the target function periodically."""
         # Do not use the threading._active_limbo_lock here because it's a gevent lock
         threading._active[self._tident] = self
+
+        self._periodic_started = True
+
         try:
             while self.quit is False:
                 self._target()
@@ -114,8 +131,8 @@
                 raise
         finally:
             try:
+                self._periodic_stopped = True
                 del threading._active[self._tident]
-                self.has_quit = True
             except Exception:
                 # Exceptions might happen during interpreter shutdown.
                 # We're mimicking what `threading.Thread` does in daemon mode, we ignore them.

{"golden_diff": "diff --git a/ddtrace/profiling/_periodic.py b/ddtrace/profiling/_periodic.py\n--- a/ddtrace/profiling/_periodic.py\n+++ b/ddtrace/profiling/_periodic.py\n@@ -71,6 +71,12 @@\n         \"\"\"\n         super(_GeventPeriodicThread, self).__init__(interval, target, name, on_shutdown)\n         self._tident = None\n+        self._periodic_started = False\n+        self._periodic_stopped = False\n+\n+    def _reset_internal_locks(self, is_alive=False):\n+        # Called by Python via `threading._after_fork`\n+        self._periodic_stopped = True\n \n     @property\n     def ident(self):\n@@ -79,14 +85,22 @@\n     def start(self):\n         \"\"\"Start the thread.\"\"\"\n         self.quit = False\n-        self.has_quit = False\n+        if self._tident is not None:\n+            raise RuntimeError(\"threads can only be started once\")\n         self._tident = _nogevent.start_new_thread(self.run, tuple())\n         if _nogevent.threading_get_native_id:\n             self._native_id = _nogevent.threading_get_native_id()\n \n+        # Wait for the thread to be started to avoid race conditions\n+        while not self._periodic_started:\n+            time.sleep(self.SLEEP_INTERVAL)\n+\n+    def is_alive(self):\n+        return not self._periodic_stopped and self._periodic_started\n+\n     def join(self, timeout=None):\n         # FIXME: handle the timeout argument\n-        while not self.has_quit:\n+        while self.is_alive():\n             time.sleep(self.SLEEP_INTERVAL)\n \n     def stop(self):\n@@ -97,6 +111,9 @@\n         \"\"\"Run the target function periodically.\"\"\"\n         # Do not use the threading._active_limbo_lock here because it's a gevent lock\n         threading._active[self._tident] = self\n+\n+        self._periodic_started = True\n+\n         try:\n             while self.quit is False:\n                 self._target()\n@@ -114,8 +131,8 @@\n                 raise\n         finally:\n             try:\n+                self._periodic_stopped = True\n                 del threading._active[self._tident]\n-                self.has_quit = True\n             except Exception:\n                 # Exceptions might happen during interpreter shutdown.\n                 # We're mimicking what `threading.Thread` does in daemon mode, we ignore them.\n", "issue": "Profiling causing gunicorn timeout (flask app)\n### Which version of dd-trace-py are you using?\r\n\r\n```\r\nddtrace[profiling]==0.37.0\r\n```\r\n\r\n### Which version of the libraries are you using?\r\n\r\n```\r\nflask==1.1.2\r\ngunicorn==20.0.4\r\nddtrace[profiling]==0.37.0\r\ndatadog==0.36.0\r\n```\r\n\r\n\r\n### How can we reproduce your problem?\r\nI'm using a simple hello world app flask application for the demonstration of this problem but the issue is happening also when we tried to integrated the profiling with our Flask project, i thought the issue was related to our project code but in this demo proved that it's a general issue, in `wsgi.py` you can add :\r\n\r\n```\r\nimport ddtrace.profiling.auto\r\n```\r\n\r\nmy gunicorn config is the default \"simple\" one : \r\n\r\n```\r\ngunicorn --bind 0.0.0.0:8000 wsgi:app\r\n```\r\n### What is the result that you get?\r\n\r\n```\r\ndocker run -p 8000:8000 flask/hello-world\r\n[2020-05-07 15:17:06 +0000] [6] [INFO] Starting gunicorn 20.0.4\r\n[2020-05-07 15:17:06 +0000] [6] [INFO] Listening at: http://0.0.0.0:8000 (6)\r\n[2020-05-07 15:17:06 +0000] [6] [INFO] Using worker: sync\r\n[2020-05-07 15:17:06 +0000] [9] [INFO] Booting worker with pid: 9\r\n[2020-05-07 15:17:52 +0000] [6] [CRITICAL] WORKER TIMEOUT (pid:9)\r\n[2020-05-07 15:17:52 +0000] [9] [INFO] Worker exiting (pid: 9)\r\n[2020-05-07 15:17:52 +0000] [13] [INFO] Booting worker with pid: 13\r\n```\r\n\r\n### What is the result that you expected?\r\nWhen i removed the auto profiler the app works and got the response.\r\nAlso strangely when using the \"manual\" profiler \r\n\r\n```\r\nfrom ddtrace.profiling import Profiler\r\n\r\nprof = Profiler()\r\nprof.start()\r\n``` \r\n\r\nThe app works as expected and i get profiling events in my Datadog account, so my believe is that the auto profiler causes a lock somewhere in its code resulting in Gunicorn workers timeout.\r\n\n", "before_files": [{"content": "# -*- encoding: utf-8 -*-\nimport sys\nimport threading\nimport time\n\nfrom ddtrace.profiling import _service\nfrom ddtrace.profiling import _nogevent\nfrom ddtrace.vendor import attr\n\n\nclass PeriodicThread(threading.Thread):\n    \"\"\"Periodic thread.\n\n    This class can be used to instantiate a worker thread that will run its `run_periodic` function every `interval`\n    seconds.\n\n    \"\"\"\n\n    _ddtrace_profiling_ignore = True\n\n    def __init__(self, interval, target, name=None, on_shutdown=None):\n        \"\"\"Create a periodic thread.\n\n        :param interval: The interval in seconds to wait between execution of the periodic function.\n        :param target: The periodic function to execute every interval.\n        :param name: The name of the thread.\n        :param on_shutdown: The function to call when the thread shuts down.\n        \"\"\"\n        super(PeriodicThread, self).__init__(name=name)\n        self._target = target\n        self._on_shutdown = on_shutdown\n        self.interval = interval\n        self.quit = threading.Event()\n        self.daemon = True\n\n    def stop(self):\n        \"\"\"Stop the thread.\"\"\"\n        # NOTE: make sure the thread is alive before using self.quit:\n        # 1. self.quit is Lock-based\n        # 2. if we're a child trying to stop a Thread,\n        #    the Lock might have been locked in a parent process while forking so that'd block forever\n        if self.is_alive():\n            self.quit.set()\n\n    def run(self):\n        \"\"\"Run the target function periodically.\"\"\"\n        while not self.quit.wait(self.interval):\n            self._target()\n        if self._on_shutdown is not None:\n            self._on_shutdown()\n\n\nclass _GeventPeriodicThread(PeriodicThread):\n    \"\"\"Periodic thread.\n\n    This class can be used to instantiate a worker thread that will run its `run_periodic` function every `interval`\n    seconds.\n\n    \"\"\"\n\n    # That's the value Python\u00a02 uses in its `threading` module\n    SLEEP_INTERVAL = 0.005\n\n    def __init__(self, interval, target, name=None, on_shutdown=None):\n        \"\"\"Create a periodic thread.\n\n        :param interval: The interval in seconds to wait between execution of the periodic function.\n        :param target: The periodic function to execute every interval.\n        :param name: The name of the thread.\n        :param on_shutdown: The function to call when the thread shuts down.\n        \"\"\"\n        super(_GeventPeriodicThread, self).__init__(interval, target, name, on_shutdown)\n        self._tident = None\n\n    @property\n    def ident(self):\n        return self._tident\n\n    def start(self):\n        \"\"\"Start the thread.\"\"\"\n        self.quit = False\n        self.has_quit = False\n        self._tident = _nogevent.start_new_thread(self.run, tuple())\n        if _nogevent.threading_get_native_id:\n            self._native_id = _nogevent.threading_get_native_id()\n\n    def join(self, timeout=None):\n        # FIXME: handle the timeout argument\n        while not self.has_quit:\n            time.sleep(self.SLEEP_INTERVAL)\n\n    def stop(self):\n        \"\"\"Stop the thread.\"\"\"\n        self.quit = True\n\n    def run(self):\n        \"\"\"Run the target function periodically.\"\"\"\n        # Do not use the threading._active_limbo_lock here because it's a gevent lock\n        threading._active[self._tident] = self\n        try:\n            while self.quit is False:\n                self._target()\n                slept = 0\n                while self.quit is False and slept < self.interval:\n                    _nogevent.sleep(self.SLEEP_INTERVAL)\n                    slept += self.SLEEP_INTERVAL\n            if self._on_shutdown is not None:\n                self._on_shutdown()\n        except Exception:\n            # Exceptions might happen during interpreter shutdown.\n            # We're mimicking what `threading.Thread` does in daemon mode, we ignore them.\n            # See `threading.Thread._bootstrap` for details.\n            if sys is not None:\n                raise\n        finally:\n            try:\n                del threading._active[self._tident]\n                self.has_quit = True\n            except Exception:\n                # Exceptions might happen during interpreter shutdown.\n                # We're mimicking what `threading.Thread` does in daemon mode, we ignore them.\n                # See `threading.Thread._bootstrap` for details.\n                if sys is not None:\n                    raise\n\n\ndef PeriodicRealThread(*args, **kwargs):\n    \"\"\"Create a PeriodicRealThread based on the underlying thread implementation (native, gevent, etc).\n\n    This is exactly like PeriodicThread, except that it runs on a *real* OS thread. Be aware that this might be tricky\n    in e.g. the gevent case, where Lock object must not be shared with the MainThread (otherwise it'd dead lock).\n\n    \"\"\"\n    if _nogevent.is_module_patched(\"threading\"):\n        return _GeventPeriodicThread(*args, **kwargs)\n    return PeriodicThread(*args, **kwargs)\n\n\n@attr.s\nclass PeriodicService(_service.Service):\n    \"\"\"A service that runs periodically.\"\"\"\n\n    _interval = attr.ib()\n    _worker = attr.ib(default=None, init=False, repr=False)\n\n    _real_thread = False\n    \"Class variable to override if the service should run in a real OS thread.\"\n\n    @property\n    def interval(self):\n        return self._interval\n\n    @interval.setter\n    def interval(self, value):\n        self._interval = value\n        # Update the interval of the PeriodicThread based on ours\n        if self._worker:\n            self._worker.interval = value\n\n    def start(self):\n        \"\"\"Start the periodic service.\"\"\"\n        super(PeriodicService, self).start()\n        periodic_thread_class = PeriodicRealThread if self._real_thread else PeriodicThread\n        self._worker = periodic_thread_class(\n            self.interval,\n            target=self.periodic,\n            name=\"%s:%s\" % (self.__class__.__module__, self.__class__.__name__),\n            on_shutdown=self.on_shutdown,\n        )\n        self._worker.start()\n\n    def join(self, timeout=None):\n        if self._worker:\n            self._worker.join(timeout)\n\n    def stop(self):\n        \"\"\"Stop the periodic collector.\"\"\"\n        if self._worker:\n            self._worker.stop()\n        super(PeriodicService, self).stop()\n\n    @staticmethod\n    def on_shutdown():\n        pass\n\n    @staticmethod\n    def periodic():\n        pass\n", "path": "ddtrace/profiling/_periodic.py"}], "after_files": [{"content": "# -*- encoding: utf-8 -*-\nimport sys\nimport threading\nimport time\n\nfrom ddtrace.profiling import _service\nfrom ddtrace.profiling import _nogevent\nfrom ddtrace.vendor import attr\n\n\nclass PeriodicThread(threading.Thread):\n    \"\"\"Periodic thread.\n\n    This class can be used to instantiate a worker thread that will run its `run_periodic` function every `interval`\n    seconds.\n\n    \"\"\"\n\n    _ddtrace_profiling_ignore = True\n\n    def __init__(self, interval, target, name=None, on_shutdown=None):\n        \"\"\"Create a periodic thread.\n\n        :param interval: The interval in seconds to wait between execution of the periodic function.\n        :param target: The periodic function to execute every interval.\n        :param name: The name of the thread.\n        :param on_shutdown: The function to call when the thread shuts down.\n        \"\"\"\n        super(PeriodicThread, self).__init__(name=name)\n        self._target = target\n        self._on_shutdown = on_shutdown\n        self.interval = interval\n        self.quit = threading.Event()\n        self.daemon = True\n\n    def stop(self):\n        \"\"\"Stop the thread.\"\"\"\n        # NOTE: make sure the thread is alive before using self.quit:\n        # 1. self.quit is Lock-based\n        # 2. if we're a child trying to stop a Thread,\n        #    the Lock might have been locked in a parent process while forking so that'd block forever\n        if self.is_alive():\n            self.quit.set()\n\n    def run(self):\n        \"\"\"Run the target function periodically.\"\"\"\n        while not self.quit.wait(self.interval):\n            self._target()\n        if self._on_shutdown is not None:\n            self._on_shutdown()\n\n\nclass _GeventPeriodicThread(PeriodicThread):\n    \"\"\"Periodic thread.\n\n    This class can be used to instantiate a worker thread that will run its `run_periodic` function every `interval`\n    seconds.\n\n    \"\"\"\n\n    # That's the value Python\u00a02 uses in its `threading` module\n    SLEEP_INTERVAL = 0.005\n\n    def __init__(self, interval, target, name=None, on_shutdown=None):\n        \"\"\"Create a periodic thread.\n\n        :param interval: The interval in seconds to wait between execution of the periodic function.\n        :param target: The periodic function to execute every interval.\n        :param name: The name of the thread.\n        :param on_shutdown: The function to call when the thread shuts down.\n        \"\"\"\n        super(_GeventPeriodicThread, self).__init__(interval, target, name, on_shutdown)\n        self._tident = None\n        self._periodic_started = False\n        self._periodic_stopped = False\n\n    def _reset_internal_locks(self, is_alive=False):\n        # Called by Python via `threading._after_fork`\n        self._periodic_stopped = True\n\n    @property\n    def ident(self):\n        return self._tident\n\n    def start(self):\n        \"\"\"Start the thread.\"\"\"\n        self.quit = False\n        if self._tident is not None:\n            raise RuntimeError(\"threads can only be started once\")\n        self._tident = _nogevent.start_new_thread(self.run, tuple())\n        if _nogevent.threading_get_native_id:\n            self._native_id = _nogevent.threading_get_native_id()\n\n        # Wait for the thread to be started to avoid race conditions\n        while not self._periodic_started:\n            time.sleep(self.SLEEP_INTERVAL)\n\n    def is_alive(self):\n        return not self._periodic_stopped and self._periodic_started\n\n    def join(self, timeout=None):\n        # FIXME: handle the timeout argument\n        while self.is_alive():\n            time.sleep(self.SLEEP_INTERVAL)\n\n    def stop(self):\n        \"\"\"Stop the thread.\"\"\"\n        self.quit = True\n\n    def run(self):\n        \"\"\"Run the target function periodically.\"\"\"\n        # Do not use the threading._active_limbo_lock here because it's a gevent lock\n        threading._active[self._tident] = self\n\n        self._periodic_started = True\n\n        try:\n            while self.quit is False:\n                self._target()\n                slept = 0\n                while self.quit is False and slept < self.interval:\n                    _nogevent.sleep(self.SLEEP_INTERVAL)\n                    slept += self.SLEEP_INTERVAL\n            if self._on_shutdown is not None:\n                self._on_shutdown()\n        except Exception:\n            # Exceptions might happen during interpreter shutdown.\n            # We're mimicking what `threading.Thread` does in daemon mode, we ignore them.\n            # See `threading.Thread._bootstrap` for details.\n            if sys is not None:\n                raise\n        finally:\n            try:\n                self._periodic_stopped = True\n                del threading._active[self._tident]\n            except Exception:\n                # Exceptions might happen during interpreter shutdown.\n                # We're mimicking what `threading.Thread` does in daemon mode, we ignore them.\n                # See `threading.Thread._bootstrap` for details.\n                if sys is not None:\n                    raise\n\n\ndef PeriodicRealThread(*args, **kwargs):\n    \"\"\"Create a PeriodicRealThread based on the underlying thread implementation (native, gevent, etc).\n\n    This is exactly like PeriodicThread, except that it runs on a *real* OS thread. Be aware that this might be tricky\n    in e.g. the gevent case, where Lock object must not be shared with the MainThread (otherwise it'd dead lock).\n\n    \"\"\"\n    if _nogevent.is_module_patched(\"threading\"):\n        return _GeventPeriodicThread(*args, **kwargs)\n    return PeriodicThread(*args, **kwargs)\n\n\n@attr.s\nclass PeriodicService(_service.Service):\n    \"\"\"A service that runs periodically.\"\"\"\n\n    _interval = attr.ib()\n    _worker = attr.ib(default=None, init=False, repr=False)\n\n    _real_thread = False\n    \"Class variable to override if the service should run in a real OS thread.\"\n\n    @property\n    def interval(self):\n        return self._interval\n\n    @interval.setter\n    def interval(self, value):\n        self._interval = value\n        # Update the interval of the PeriodicThread based on ours\n        if self._worker:\n            self._worker.interval = value\n\n    def start(self):\n        \"\"\"Start the periodic service.\"\"\"\n        super(PeriodicService, self).start()\n        periodic_thread_class = PeriodicRealThread if self._real_thread else PeriodicThread\n        self._worker = periodic_thread_class(\n            self.interval,\n            target=self.periodic,\n            name=\"%s:%s\" % (self.__class__.__module__, self.__class__.__name__),\n            on_shutdown=self.on_shutdown,\n        )\n        self._worker.start()\n\n    def join(self, timeout=None):\n        if self._worker:\n            self._worker.join(timeout)\n\n    def stop(self):\n        \"\"\"Stop the periodic collector.\"\"\"\n        if self._worker:\n            self._worker.stop()\n        super(PeriodicService, self).stop()\n\n    @staticmethod\n    def on_shutdown():\n        pass\n\n    @staticmethod\n    def periodic():\n        pass\n", "path": "ddtrace/profiling/_periodic.py"}]}