JBKahn: Strange that the tests passed and it also exited with an error code.
brettlangdon: Yeah, there is something going on with our Django tests... but only in CircleCI it seems right now, can sort of ignore that failure :/
JBKahn:  😢 well it otherwise looks pretty good
diff --git a/ddtrace/internal/writer.py b/ddtrace/internal/writer.py
index 64dbe15dd..7ee814c52 100644
--- a/ddtrace/internal/writer.py
+++ b/ddtrace/internal/writer.py
@@ -1,7 +1,6 @@
 # stdlib
 import itertools
 import random
-import os
 import time
 
 from .. import api
@@ -33,7 +32,7 @@ class AgentWriter(_worker.PeriodicWorkerThread):
         super(AgentWriter, self).__init__(interval=self.QUEUE_PROCESSING_INTERVAL,
                                           exit_timeout=shutdown_timeout,
                                           name=self.__class__.__name__)
-        self._reset_queue()
+        self._trace_queue = Q(maxsize=MAX_TRACES)
         self._filters = filters
         self._priority_sampler = priority_sampler
         self._last_error_ts = 0
@@ -43,6 +42,23 @@ class AgentWriter(_worker.PeriodicWorkerThread):
         self._stats_rate_counter = 0
         self.start()
 
+    def recreate(self):
+        """ Create a new instance of :class:`AgentWriter` using the same settings from this instance
+
+        :rtype: :class:`AgentWriter`
+        :returns: A new :class:`AgentWriter` instance
+        """
+        return self.__class__(
+            hostname=self.api.hostname,
+            port=self.api.port,
+            uds_path=self.api.uds_path,
+            https=self.api.https,
+            shutdown_timeout=self.exit_timeout,
+            filters=self._filters,
+            priority_sampler=self._priority_sampler,
+            dogstatsd=self.dogstatsd,
+        )
+
     def _send_stats(self):
         """Determine if we're sending stats or not.
 
@@ -62,18 +78,7 @@ class AgentWriter(_worker.PeriodicWorkerThread):
 
         return False
 
-    def _reset_queue(self):
-        self._pid = os.getpid()
-        self._trace_queue = Q(maxsize=MAX_TRACES)
-
     def write(self, spans=None, services=None):
-        # if this queue was created in a different process (i.e. this was
-        # forked) reset everything so that we can safely work from it.
-        pid = os.getpid()
-        if self._pid != pid:
-            log.debug('resetting queues. pids(old:%s new:%s)', self._pid, pid)
-            self._reset_queue()
-
         if spans:
             self._trace_queue.put(spans)
 
diff --git a/ddtrace/tracer.py b/ddtrace/tracer.py
index c74f62dd9..0ffed1a6f 100644
--- a/ddtrace/tracer.py
+++ b/ddtrace/tracer.py
@@ -378,8 +378,7 @@ class Tracer(object):
         context.add_span(span)
 
         # check for new process if runtime metrics worker has already been started
-        if self._runtime_worker:
-            self._check_new_process()
+        self._check_new_process()
 
         # update set of services handled by tracer
         if service and service not in self._services:
@@ -427,6 +426,9 @@ class Tracer(object):
         # and generated a new runtime id
         self._update_dogstatsd_constant_tags()
 
+        # Re-create the background writer thread
+        self.writer = self.writer.recreate()
+
     def trace(self, name, service=None, resource=None, span_type=None):
         """
         Return a span that will trace an operation called `name`. The context that created

Django API APM traces disappeared after upgrade from 0.29.0 to 0.30.0
In talking with @brettlangdon we couldn't find a quick solution, but downgrading the library version brought them back. Interestingly enough, even the custom traces we were using within the API disappeared but all the ones that exist outside of Django i.e. workers, are all fine on version 0.30.0
diff --git a/tests/test_tracer.py b/tests/test_tracer.py
index 04f72f1d4..ee9e9f6d6 100644
--- a/tests/test_tracer.py
+++ b/tests/test_tracer.py
@@ -1,7 +1,8 @@
 """
 tests for Tracer and utilities.
 """
-
+import contextlib
+import multiprocessing
 from os import getpid
 import sys
 import warnings
@@ -588,3 +589,63 @@ def test_tracer_dogstatsd_url():
     with pytest.raises(ValueError) as e:
         t = ddtrace.Tracer(dogstatsd_url='foo://foobar:12')
         assert str(e) == 'Unknown url format for `foo://foobar:12`'
+
+
+def test_tracer_fork():
+    t = ddtrace.Tracer()
+    original_pid = t._pid
+    original_writer = t.writer
+
+    @contextlib.contextmanager
+    def capture_failures(errors):
+        try:
+            yield
+        except AssertionError as e:
+            errors.put(e)
+
+    def task(t, errors):
+        # Start a new span to trigger process checking
+        with t.trace('test', service='test') as span:
+
+            # Assert we recreated the writer and have a new queue
+            with capture_failures(errors):
+                assert t._pid != original_pid
+                assert t.writer != original_writer
+                assert t.writer._trace_queue != original_writer._trace_queue
+
+            # Stop the background worker so we don't accidetnally flush the
+            # queue before we can assert on it
+            t.writer.stop()
+            t.writer.join()
+
+        # Assert the trace got written into the correct queue
+        assert original_writer._trace_queue.qsize() == 0
+        assert t.writer._trace_queue.qsize() == 1
+        assert [[span]] == list(t.writer._trace_queue.get())
+
+    # Assert tracer in a new process correctly recreates the writer
+    errors = multiprocessing.Queue()
+    p = multiprocessing.Process(target=task, args=(t, errors))
+    try:
+        p.start()
+    finally:
+        p.join(timeout=2)
+
+    while errors.qsize() > 0:
+        raise errors.get()
+
+    # Ensure writing into the tracer in this process still works as expected
+    with t.trace('test', service='test') as span:
+        assert t._pid == original_pid
+        assert t.writer == original_writer
+        assert t.writer._trace_queue == original_writer._trace_queue
+
+        # Stop the background worker so we don't accidentally flush the
+        # queue before we can assert on it
+        t.writer.stop()
+        t.writer.join()
+
+    # Assert the trace got written into the correct queue
+    assert original_writer._trace_queue.qsize() == 1
+    assert t.writer._trace_queue.qsize() == 1
+    assert [[span]] == list(t.writer._trace_queue.get())

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-mock opentracing mock
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
coverage==7.8.0
-e git+https://github.com/DataDog/dd-trace-py.git@d25109514bf4488a5e7f3042a960f92122fc8f1e#egg=ddtrace
exceptiongroup==1.2.2
iniconfig==2.1.0
mock==5.2.0
opentracing==2.4.0
packaging==24.2
pluggy==1.5.0
psutil==7.0.0
pytest==8.3.5
pytest-cov==6.0.0
pytest-mock==3.14.0
tomli==2.2.1

name: dd-trace-py
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.8.0
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - mock==5.2.0
      - opentracing==2.4.0
      - packaging==24.2
      - pluggy==1.5.0
      - psutil==7.0.0
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - tomli==2.2.1
prefix: /opt/conda/envs/dd-trace-py

tests/test_tracer.py::test_tracer_fork
tests/test_tracer.py::TracerTestCase::test_adding_services tests/test_tracer.py::TracerTestCase::test_configure_dogstatsd_host tests/test_tracer.py::TracerTestCase::test_configure_dogstatsd_host_port tests/test_tracer.py::TracerTestCase::test_configure_dogstatsd_url_host_port tests/test_tracer.py::TracerTestCase::test_configure_dogstatsd_url_socket tests/test_tracer.py::TracerTestCase::test_configure_runtime_worker tests/test_tracer.py::TracerTestCase::test_default_provider_get tests/test_tracer.py::TracerTestCase::test_default_provider_set tests/test_tracer.py::TracerTestCase::test_default_provider_trace tests/test_tracer.py::TracerTestCase::test_global_context tests/test_tracer.py::TracerTestCase::test_only_root_span_runtime tests/test_tracer.py::TracerTestCase::test_span_no_runtime_tags tests/test_tracer.py::TracerTestCase::test_start_child_from_context tests/test_tracer.py::TracerTestCase::test_start_child_span tests/test_tracer.py::TracerTestCase::test_start_child_span_attributes tests/test_tracer.py::TracerTestCase::test_start_span tests/test_tracer.py::TracerTestCase::test_start_span_optional tests/test_tracer.py::TracerTestCase::test_tracer tests/test_tracer.py::TracerTestCase::test_tracer_current_root_span_missing_context tests/test_tracer.py::TracerTestCase::test_tracer_current_span tests/test_tracer.py::TracerTestCase::test_tracer_current_span_missing_context tests/test_tracer.py::TracerTestCase::test_tracer_disabled tests/test_tracer.py::TracerTestCase::test_tracer_disabled_mem_leak tests/test_tracer.py::TracerTestCase::test_tracer_global_tags tests/test_tracer.py::TracerTestCase::test_tracer_pid tests/test_tracer.py::TracerTestCase::test_tracer_vars tests/test_tracer.py::TracerTestCase::test_tracer_wrap tests/test_tracer.py::TracerTestCase::test_tracer_wrap_class tests/test_tracer.py::TracerTestCase::test_tracer_wrap_default_name tests/test_tracer.py::TracerTestCase::test_tracer_wrap_exception tests/test_tracer.py::TracerTestCase::test_tracer_wrap_factory tests/test_tracer.py::TracerTestCase::test_tracer_wrap_factory_nested tests/test_tracer.py::TracerTestCase::test_tracer_wrap_multiple_calls tests/test_tracer.py::TracerTestCase::test_tracer_wrap_span_nesting tests/test_tracer.py::TracerTestCase::test_tracer_wrap_span_nesting_current_root_span tests/test_tracer.py::test_installed_excepthook tests/test_tracer.py::test_excepthook tests/test_tracer.py::test_tracer_url tests/test_tracer.py::test_tracer_dogstatsd_url
ddtrace/tracer.py ddtrace/internal/writer.py
ddtrace/tracer.py ddtrace/internal/writer.py