We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Intermittent `RuntimeError: the memalloc module was not started` error
### Which version of dd-trace-py are you using?

`ddtrace==0.57.0`

### What is the result that you get?

`RuntimeError: the memalloc module was not started`

![148817478-801fc910-af78-4f45-a06c-b77e8924aeb7](https://user-images.githubusercontent.com/49695/148818367-e625ecfa-80a5-4c24-b08b-0cc29448dec4.png)

### What is the result that you expected?

No errors.

This seems to be happening a few times a day.

We have tried setting `DD_PROFILING_HEAP_ENABLED=False` and `DD_PROFILING_MEMALLOC=0` in the environment, but the errors continue to appear.


Configuration in Django:
```
import os
from ddtrace import config, tracer

# DataDog Setup
tracer.configure(hostname=os.environ.get("HOST_IP"))
tracer.configure(enabled=True)
tracer.set_tags(
    {"env": os.environ.get("ENVIRONMENT"), "namespace": os.environ.get("NAMESPACE")}
)
config.django["analytics_enabled"] = True
config.django["cache_service_name"] = "xxx-cache"
config.django["database_service_name_prefix"] = "xxx"
config.django["distributed_tracing_enabled"] = True
config.django["instrument_middleware"] = True
config.django["service_name"] = "xxx"

```


--- 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/collector/memalloc.py`
Content:
```
1 # -*- encoding: utf-8 -*-
2 import logging
3 import math
4 import os
5 import threading
6 import typing
7 
8 import attr
9 
10 
11 try:
12     from ddtrace.profiling.collector import _memalloc
13 except ImportError:
14     _memalloc = None  # type: ignore[assignment]
15 
16 from ddtrace.internal.utils import attr as attr_utils
17 from ddtrace.internal.utils import formats
18 from ddtrace.profiling import _threading
19 from ddtrace.profiling import collector
20 from ddtrace.profiling import event
21 
22 
23 LOG = logging.getLogger(__name__)
24 
25 
26 @event.event_class
27 class MemoryAllocSampleEvent(event.StackBasedEvent):
28     """A sample storing memory allocation tracked."""
29 
30     size = attr.ib(default=0, type=int)
31     """Allocation size in bytes."""
32 
33     capture_pct = attr.ib(default=None, type=float)
34     """The capture percentage."""
35 
36     nevents = attr.ib(default=0, type=int)
37     """The total number of allocation events sampled."""
38 
39 
40 @event.event_class
41 class MemoryHeapSampleEvent(event.StackBasedEvent):
42     """A sample storing memory allocation tracked."""
43 
44     size = attr.ib(default=0, type=int)
45     """Allocation size in bytes."""
46 
47     sample_size = attr.ib(default=0, type=int)
48     """The sampling size."""
49 
50 
51 def _get_default_heap_sample_size(
52     default_heap_sample_size=1024 * 1024,  # type: int
53 ):
54     # type: (...) -> int
55     heap_sample_size = os.environ.get("DD_PROFILING_HEAP_SAMPLE_SIZE")
56     if heap_sample_size is not None:
57         return int(heap_sample_size)
58 
59     if not formats.asbool(os.environ.get("DD_PROFILING_HEAP_ENABLED", "1")):
60         return 0
61 
62     try:
63         from ddtrace.vendor import psutil
64 
65         total_mem = psutil.swap_memory().total + psutil.virtual_memory().total
66     except Exception:
67         LOG.warning(
68             "Unable to get total memory available, using default value of %d KB",
69             default_heap_sample_size / 1024,
70             exc_info=True,
71         )
72         return default_heap_sample_size
73 
74     # This is TRACEBACK_ARRAY_MAX_COUNT
75     max_samples = 2 ** 16
76 
77     return max(math.ceil(total_mem / max_samples), default_heap_sample_size)
78 
79 
80 @attr.s
81 class MemoryCollector(collector.PeriodicCollector):
82     """Memory allocation collector."""
83 
84     _DEFAULT_MAX_EVENTS = 16
85     _DEFAULT_INTERVAL = 0.5
86 
87     # Arbitrary interval to empty the _memalloc event buffer
88     _interval = attr.ib(default=_DEFAULT_INTERVAL, repr=False)
89 
90     # TODO make this dynamic based on the 1. interval and 2. the max number of events allowed in the Recorder
91     _max_events = attr.ib(
92         factory=attr_utils.from_env(
93             "_DD_PROFILING_MEMORY_EVENTS_BUFFER",
94             _DEFAULT_MAX_EVENTS,
95             int,
96         )
97     )
98     max_nframe = attr.ib(factory=attr_utils.from_env("DD_PROFILING_MAX_FRAMES", 64, int))
99     heap_sample_size = attr.ib(type=int, factory=_get_default_heap_sample_size)
100     ignore_profiler = attr.ib(factory=attr_utils.from_env("DD_PROFILING_IGNORE_PROFILER", False, formats.asbool))
101 
102     def _start_service(self):
103         # type: (...) -> None
104         """Start collecting memory profiles."""
105         if _memalloc is None:
106             raise collector.CollectorUnavailable
107 
108         _memalloc.start(self.max_nframe, self._max_events, self.heap_sample_size)
109 
110         super(MemoryCollector, self)._start_service()
111 
112     def _stop_service(self):
113         # type: (...) -> None
114         super(MemoryCollector, self)._stop_service()
115 
116         if _memalloc is not None:
117             try:
118                 _memalloc.stop()
119             except RuntimeError:
120                 pass
121 
122     def _get_thread_id_ignore_set(self):
123         # type: () -> typing.Set[int]
124         # This method is not perfect and prone to race condition in theory, but very little in practice.
125         # Anyhow it's not a big deal — it's a best effort feature.
126         return {
127             thread.ident
128             for thread in threading.enumerate()
129             if getattr(thread, "_ddtrace_profiling_ignore", False) and thread.ident is not None
130         }
131 
132     def snapshot(self):
133         thread_id_ignore_set = self._get_thread_id_ignore_set()
134         return (
135             tuple(
136                 MemoryHeapSampleEvent(
137                     thread_id=thread_id,
138                     thread_name=_threading.get_thread_name(thread_id),
139                     thread_native_id=_threading.get_thread_native_id(thread_id),
140                     frames=stack,
141                     nframes=nframes,
142                     size=size,
143                     sample_size=self.heap_sample_size,
144                 )
145                 for (stack, nframes, thread_id), size in _memalloc.heap()
146                 if not self.ignore_profiler or thread_id not in thread_id_ignore_set
147             ),
148         )
149 
150     def collect(self):
151         events, count, alloc_count = _memalloc.iter_events()
152         capture_pct = 100 * count / alloc_count
153         thread_id_ignore_set = self._get_thread_id_ignore_set()
154         # TODO: The event timestamp is slightly off since it's going to be the time we copy the data from the
155         # _memalloc buffer to our Recorder. This is fine for now, but we might want to store the nanoseconds
156         # timestamp in C and then return it via iter_events.
157         return (
158             tuple(
159                 MemoryAllocSampleEvent(
160                     thread_id=thread_id,
161                     thread_name=_threading.get_thread_name(thread_id),
162                     thread_native_id=_threading.get_thread_native_id(thread_id),
163                     frames=stack,
164                     nframes=nframes,
165                     size=size,
166                     capture_pct=capture_pct,
167                     nevents=alloc_count,
168                 )
169                 for (stack, nframes, thread_id), size, domain in events
170                 if not self.ignore_profiler or thread_id not in thread_id_ignore_set
171             ),
172         )
173
```
--- 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/collector/memalloc.py b/ddtrace/profiling/collector/memalloc.py
--- a/ddtrace/profiling/collector/memalloc.py
+++ b/ddtrace/profiling/collector/memalloc.py
@@ -148,7 +148,13 @@
         )
 
     def collect(self):
-        events, count, alloc_count = _memalloc.iter_events()
+        try:
+            events, count, alloc_count = _memalloc.iter_events()
+        except RuntimeError:
+            # DEV: This can happen if either _memalloc has not been started or has been stopped.
+            LOG.debug("Unable to collect memory events from process %d", os.getpid(), exc_info=True)
+            return tuple()
+
         capture_pct = 100 * count / alloc_count
         thread_id_ignore_set = self._get_thread_id_ignore_set()
         # TODO: The event timestamp is slightly off since it's going to be the time we copy the data from the

{"golden_diff": "diff --git a/ddtrace/profiling/collector/memalloc.py b/ddtrace/profiling/collector/memalloc.py\n--- a/ddtrace/profiling/collector/memalloc.py\n+++ b/ddtrace/profiling/collector/memalloc.py\n@@ -148,7 +148,13 @@\n         )\n \n     def collect(self):\n-        events, count, alloc_count = _memalloc.iter_events()\n+        try:\n+            events, count, alloc_count = _memalloc.iter_events()\n+        except RuntimeError:\n+            # DEV: This can happen if either _memalloc has not been started or has been stopped.\n+            LOG.debug(\"Unable to collect memory events from process %d\", os.getpid(), exc_info=True)\n+            return tuple()\n+\n         capture_pct = 100 * count / alloc_count\n         thread_id_ignore_set = self._get_thread_id_ignore_set()\n         # TODO: The event timestamp is slightly off since it's going to be the time we copy the data from the\n", "issue": "Intermittent `RuntimeError: the memalloc module was not started` error\n### Which version of dd-trace-py are you using?\r\n\r\n`ddtrace==0.57.0`\r\n\r\n### What is the result that you get?\r\n\r\n`RuntimeError: the memalloc module was not started`\r\n\r\n![148817478-801fc910-af78-4f45-a06c-b77e8924aeb7](https://user-images.githubusercontent.com/49695/148818367-e625ecfa-80a5-4c24-b08b-0cc29448dec4.png)\r\n\r\n### What is the result that you expected?\r\n\r\nNo errors.\r\n\r\nThis seems to be happening a few times a day.\r\n\r\nWe have tried setting `DD_PROFILING_HEAP_ENABLED=False` and `DD_PROFILING_MEMALLOC=0` in the environment, but the errors continue to appear.\r\n\r\n\r\nConfiguration in Django:\r\n```\r\nimport os\r\nfrom ddtrace import config, tracer\r\n\r\n# DataDog Setup\r\ntracer.configure(hostname=os.environ.get(\"HOST_IP\"))\r\ntracer.configure(enabled=True)\r\ntracer.set_tags(\r\n    {\"env\": os.environ.get(\"ENVIRONMENT\"), \"namespace\": os.environ.get(\"NAMESPACE\")}\r\n)\r\nconfig.django[\"analytics_enabled\"] = True\r\nconfig.django[\"cache_service_name\"] = \"xxx-cache\"\r\nconfig.django[\"database_service_name_prefix\"] = \"xxx\"\r\nconfig.django[\"distributed_tracing_enabled\"] = True\r\nconfig.django[\"instrument_middleware\"] = True\r\nconfig.django[\"service_name\"] = \"xxx\"\r\n\r\n```\r\n\n", "before_files": [{"content": "# -*- encoding: utf-8 -*-\nimport logging\nimport math\nimport os\nimport threading\nimport typing\n\nimport attr\n\n\ntry:\n    from ddtrace.profiling.collector import _memalloc\nexcept ImportError:\n    _memalloc = None  # type: ignore[assignment]\n\nfrom ddtrace.internal.utils import attr as attr_utils\nfrom ddtrace.internal.utils import formats\nfrom ddtrace.profiling import _threading\nfrom ddtrace.profiling import collector\nfrom ddtrace.profiling import event\n\n\nLOG = logging.getLogger(__name__)\n\n\n@event.event_class\nclass MemoryAllocSampleEvent(event.StackBasedEvent):\n    \"\"\"A sample storing memory allocation tracked.\"\"\"\n\n    size = attr.ib(default=0, type=int)\n    \"\"\"Allocation size in bytes.\"\"\"\n\n    capture_pct = attr.ib(default=None, type=float)\n    \"\"\"The capture percentage.\"\"\"\n\n    nevents = attr.ib(default=0, type=int)\n    \"\"\"The total number of allocation events sampled.\"\"\"\n\n\n@event.event_class\nclass MemoryHeapSampleEvent(event.StackBasedEvent):\n    \"\"\"A sample storing memory allocation tracked.\"\"\"\n\n    size = attr.ib(default=0, type=int)\n    \"\"\"Allocation size in bytes.\"\"\"\n\n    sample_size = attr.ib(default=0, type=int)\n    \"\"\"The sampling size.\"\"\"\n\n\ndef _get_default_heap_sample_size(\n    default_heap_sample_size=1024 * 1024,  # type: int\n):\n    # type: (...) -> int\n    heap_sample_size = os.environ.get(\"DD_PROFILING_HEAP_SAMPLE_SIZE\")\n    if heap_sample_size is not None:\n        return int(heap_sample_size)\n\n    if not formats.asbool(os.environ.get(\"DD_PROFILING_HEAP_ENABLED\", \"1\")):\n        return 0\n\n    try:\n        from ddtrace.vendor import psutil\n\n        total_mem = psutil.swap_memory().total + psutil.virtual_memory().total\n    except Exception:\n        LOG.warning(\n            \"Unable to get total memory available, using default value of %d KB\",\n            default_heap_sample_size / 1024,\n            exc_info=True,\n        )\n        return default_heap_sample_size\n\n    # This is TRACEBACK_ARRAY_MAX_COUNT\n    max_samples = 2 ** 16\n\n    return max(math.ceil(total_mem / max_samples), default_heap_sample_size)\n\n\n@attr.s\nclass MemoryCollector(collector.PeriodicCollector):\n    \"\"\"Memory allocation collector.\"\"\"\n\n    _DEFAULT_MAX_EVENTS = 16\n    _DEFAULT_INTERVAL = 0.5\n\n    # Arbitrary interval to empty the _memalloc event buffer\n    _interval = attr.ib(default=_DEFAULT_INTERVAL, repr=False)\n\n    # TODO make this dynamic based on the 1. interval and 2. the max number of events allowed in the Recorder\n    _max_events = attr.ib(\n        factory=attr_utils.from_env(\n            \"_DD_PROFILING_MEMORY_EVENTS_BUFFER\",\n            _DEFAULT_MAX_EVENTS,\n            int,\n        )\n    )\n    max_nframe = attr.ib(factory=attr_utils.from_env(\"DD_PROFILING_MAX_FRAMES\", 64, int))\n    heap_sample_size = attr.ib(type=int, factory=_get_default_heap_sample_size)\n    ignore_profiler = attr.ib(factory=attr_utils.from_env(\"DD_PROFILING_IGNORE_PROFILER\", False, formats.asbool))\n\n    def _start_service(self):\n        # type: (...) -> None\n        \"\"\"Start collecting memory profiles.\"\"\"\n        if _memalloc is None:\n            raise collector.CollectorUnavailable\n\n        _memalloc.start(self.max_nframe, self._max_events, self.heap_sample_size)\n\n        super(MemoryCollector, self)._start_service()\n\n    def _stop_service(self):\n        # type: (...) -> None\n        super(MemoryCollector, self)._stop_service()\n\n        if _memalloc is not None:\n            try:\n                _memalloc.stop()\n            except RuntimeError:\n                pass\n\n    def _get_thread_id_ignore_set(self):\n        # type: () -> typing.Set[int]\n        # This method is not perfect and prone to race condition in theory, but very little in practice.\n        # Anyhow it's not a big deal \u2014 it's a best effort feature.\n        return {\n            thread.ident\n            for thread in threading.enumerate()\n            if getattr(thread, \"_ddtrace_profiling_ignore\", False) and thread.ident is not None\n        }\n\n    def snapshot(self):\n        thread_id_ignore_set = self._get_thread_id_ignore_set()\n        return (\n            tuple(\n                MemoryHeapSampleEvent(\n                    thread_id=thread_id,\n                    thread_name=_threading.get_thread_name(thread_id),\n                    thread_native_id=_threading.get_thread_native_id(thread_id),\n                    frames=stack,\n                    nframes=nframes,\n                    size=size,\n                    sample_size=self.heap_sample_size,\n                )\n                for (stack, nframes, thread_id), size in _memalloc.heap()\n                if not self.ignore_profiler or thread_id not in thread_id_ignore_set\n            ),\n        )\n\n    def collect(self):\n        events, count, alloc_count = _memalloc.iter_events()\n        capture_pct = 100 * count / alloc_count\n        thread_id_ignore_set = self._get_thread_id_ignore_set()\n        # TODO: The event timestamp is slightly off since it's going to be the time we copy the data from the\n        # _memalloc buffer to our Recorder. This is fine for now, but we might want to store the nanoseconds\n        # timestamp in C and then return it via iter_events.\n        return (\n            tuple(\n                MemoryAllocSampleEvent(\n                    thread_id=thread_id,\n                    thread_name=_threading.get_thread_name(thread_id),\n                    thread_native_id=_threading.get_thread_native_id(thread_id),\n                    frames=stack,\n                    nframes=nframes,\n                    size=size,\n                    capture_pct=capture_pct,\n                    nevents=alloc_count,\n                )\n                for (stack, nframes, thread_id), size, domain in events\n                if not self.ignore_profiler or thread_id not in thread_id_ignore_set\n            ),\n        )\n", "path": "ddtrace/profiling/collector/memalloc.py"}], "after_files": [{"content": "# -*- encoding: utf-8 -*-\nimport logging\nimport math\nimport os\nimport threading\nimport typing\n\nimport attr\n\n\ntry:\n    from ddtrace.profiling.collector import _memalloc\nexcept ImportError:\n    _memalloc = None  # type: ignore[assignment]\n\nfrom ddtrace.internal.utils import attr as attr_utils\nfrom ddtrace.internal.utils import formats\nfrom ddtrace.profiling import _threading\nfrom ddtrace.profiling import collector\nfrom ddtrace.profiling import event\n\n\nLOG = logging.getLogger(__name__)\n\n\n@event.event_class\nclass MemoryAllocSampleEvent(event.StackBasedEvent):\n    \"\"\"A sample storing memory allocation tracked.\"\"\"\n\n    size = attr.ib(default=0, type=int)\n    \"\"\"Allocation size in bytes.\"\"\"\n\n    capture_pct = attr.ib(default=None, type=float)\n    \"\"\"The capture percentage.\"\"\"\n\n    nevents = attr.ib(default=0, type=int)\n    \"\"\"The total number of allocation events sampled.\"\"\"\n\n\n@event.event_class\nclass MemoryHeapSampleEvent(event.StackBasedEvent):\n    \"\"\"A sample storing memory allocation tracked.\"\"\"\n\n    size = attr.ib(default=0, type=int)\n    \"\"\"Allocation size in bytes.\"\"\"\n\n    sample_size = attr.ib(default=0, type=int)\n    \"\"\"The sampling size.\"\"\"\n\n\ndef _get_default_heap_sample_size(\n    default_heap_sample_size=1024 * 1024,  # type: int\n):\n    # type: (...) -> int\n    heap_sample_size = os.environ.get(\"DD_PROFILING_HEAP_SAMPLE_SIZE\")\n    if heap_sample_size is not None:\n        return int(heap_sample_size)\n\n    if not formats.asbool(os.environ.get(\"DD_PROFILING_HEAP_ENABLED\", \"1\")):\n        return 0\n\n    try:\n        from ddtrace.vendor import psutil\n\n        total_mem = psutil.swap_memory().total + psutil.virtual_memory().total\n    except Exception:\n        LOG.warning(\n            \"Unable to get total memory available, using default value of %d KB\",\n            default_heap_sample_size / 1024,\n            exc_info=True,\n        )\n        return default_heap_sample_size\n\n    # This is TRACEBACK_ARRAY_MAX_COUNT\n    max_samples = 2 ** 16\n\n    return max(math.ceil(total_mem / max_samples), default_heap_sample_size)\n\n\n@attr.s\nclass MemoryCollector(collector.PeriodicCollector):\n    \"\"\"Memory allocation collector.\"\"\"\n\n    _DEFAULT_MAX_EVENTS = 16\n    _DEFAULT_INTERVAL = 0.5\n\n    # Arbitrary interval to empty the _memalloc event buffer\n    _interval = attr.ib(default=_DEFAULT_INTERVAL, repr=False)\n\n    # TODO make this dynamic based on the 1. interval and 2. the max number of events allowed in the Recorder\n    _max_events = attr.ib(\n        factory=attr_utils.from_env(\n            \"_DD_PROFILING_MEMORY_EVENTS_BUFFER\",\n            _DEFAULT_MAX_EVENTS,\n            int,\n        )\n    )\n    max_nframe = attr.ib(factory=attr_utils.from_env(\"DD_PROFILING_MAX_FRAMES\", 64, int))\n    heap_sample_size = attr.ib(type=int, factory=_get_default_heap_sample_size)\n    ignore_profiler = attr.ib(factory=attr_utils.from_env(\"DD_PROFILING_IGNORE_PROFILER\", False, formats.asbool))\n\n    def _start_service(self):\n        # type: (...) -> None\n        \"\"\"Start collecting memory profiles.\"\"\"\n        if _memalloc is None:\n            raise collector.CollectorUnavailable\n\n        _memalloc.start(self.max_nframe, self._max_events, self.heap_sample_size)\n\n        super(MemoryCollector, self)._start_service()\n\n    def _stop_service(self):\n        # type: (...) -> None\n        super(MemoryCollector, self)._stop_service()\n\n        if _memalloc is not None:\n            try:\n                _memalloc.stop()\n            except RuntimeError:\n                pass\n\n    def _get_thread_id_ignore_set(self):\n        # type: () -> typing.Set[int]\n        # This method is not perfect and prone to race condition in theory, but very little in practice.\n        # Anyhow it's not a big deal \u2014 it's a best effort feature.\n        return {\n            thread.ident\n            for thread in threading.enumerate()\n            if getattr(thread, \"_ddtrace_profiling_ignore\", False) and thread.ident is not None\n        }\n\n    def snapshot(self):\n        thread_id_ignore_set = self._get_thread_id_ignore_set()\n        return (\n            tuple(\n                MemoryHeapSampleEvent(\n                    thread_id=thread_id,\n                    thread_name=_threading.get_thread_name(thread_id),\n                    thread_native_id=_threading.get_thread_native_id(thread_id),\n                    frames=stack,\n                    nframes=nframes,\n                    size=size,\n                    sample_size=self.heap_sample_size,\n                )\n                for (stack, nframes, thread_id), size in _memalloc.heap()\n                if not self.ignore_profiler or thread_id not in thread_id_ignore_set\n            ),\n        )\n\n    def collect(self):\n        try:\n            events, count, alloc_count = _memalloc.iter_events()\n        except RuntimeError:\n            # DEV: This can happen if either _memalloc has not been started or has been stopped.\n            LOG.debug(\"Unable to collect memory events from process %d\", os.getpid(), exc_info=True)\n            return tuple()\n\n        capture_pct = 100 * count / alloc_count\n        thread_id_ignore_set = self._get_thread_id_ignore_set()\n        # TODO: The event timestamp is slightly off since it's going to be the time we copy the data from the\n        # _memalloc buffer to our Recorder. This is fine for now, but we might want to store the nanoseconds\n        # timestamp in C and then return it via iter_events.\n        return (\n            tuple(\n                MemoryAllocSampleEvent(\n                    thread_id=thread_id,\n                    thread_name=_threading.get_thread_name(thread_id),\n                    thread_native_id=_threading.get_thread_native_id(thread_id),\n                    frames=stack,\n                    nframes=nframes,\n                    size=size,\n                    capture_pct=capture_pct,\n                    nevents=alloc_count,\n                )\n                for (stack, nframes, thread_id), size, domain in events\n                if not self.ignore_profiler or thread_id not in thread_id_ignore_set\n            ),\n        )\n", "path": "ddtrace/profiling/collector/memalloc.py"}]}