14
14
15
15
import itertools
16
16
import logging
17
+ import multiprocessing
18
+ import os
17
19
import sys
18
20
import time
19
21
import unittest
30
32
from opentelemetry .sdk .trace import Resource , sampling
31
33
from opentelemetry .sdk .util .instrumentation import InstrumentationInfo
32
34
from opentelemetry .semconv .trace import SpanAttributes
35
+ from opentelemetry .test .concurrency_test import ConcurrencyTestBase
33
36
34
37
35
38
class MockDatadogSpanExporter (datadog .DatadogSpanExporter ):
@@ -41,6 +44,9 @@ def __init__(self, *args, **kwargs):
41
44
agent_writer_mock .exit_timeout = 1
42
45
self ._agent_writer = agent_writer_mock
43
46
47
+ def reset (self ):
48
+ self ._agent_writer .reset_mock ()
49
+
44
50
45
51
def get_spans (tracer , exporter , shutdown = True ):
46
52
if shutdown :
@@ -667,3 +673,53 @@ def test_service_name_fallback(self):
667
673
668
674
span = datadog_spans [0 ]
669
675
self .assertEqual (span ["service" ], "fallback_service_name" )
676
+
677
+
678
+ class TestDatadogSpanProcessorConcurrency (ConcurrencyTestBase ):
679
+
680
+ @unittest .skipUnless (
681
+ hasattr (os , "fork" ) and sys .version_info >= (3 , 7 ),
682
+ "needs *nix and minor version 7 or later" ,
683
+ )
684
+ def test_exports_with_fork (self ):
685
+ # pylint: disable=invalid-name
686
+ tracer_provider = trace .TracerProvider ()
687
+ tracer = tracer_provider .get_tracer (__name__ )
688
+
689
+ exporter = MockDatadogSpanExporter ()
690
+
691
+ span_processor = datadog .DatadogExportSpanProcessor (
692
+ exporter , schedule_delay_millis = 50
693
+ )
694
+ tracer_provider .add_span_processor (span_processor )
695
+ with tracer .start_as_current_span ("foo" ):
696
+ pass
697
+ time .sleep (0.5 ) # give some time for the exporter to upload spans
698
+
699
+ self .assertTrue (span_processor .force_flush ())
700
+ self .assertEqual (len (get_spans (tracer , exporter , shutdown = False )), 1 )
701
+ exporter .reset ()
702
+
703
+ def child (conn ):
704
+ def _target ():
705
+ with tracer .start_as_current_span ("span" ) as s :
706
+ s .set_attribute ("i" , "1" )
707
+ with tracer .start_as_current_span ("temp" ):
708
+ pass
709
+
710
+ self .run_with_many_threads (_target , 100 )
711
+
712
+ time .sleep (0.5 )
713
+
714
+ spans = get_spans (tracer , exporter , shutdown = False )
715
+ conn .send (len (spans ) == 200 )
716
+ conn .close ()
717
+
718
+ parent_conn , child_conn = multiprocessing .Pipe ()
719
+ fork_context = multiprocessing .get_context ("fork" )
720
+ p = fork_context .Process (target = child , args = (child_conn ,))
721
+ p .start ()
722
+ self .assertTrue (parent_conn .recv ())
723
+ p .join ()
724
+
725
+ span_processor .shutdown ()
0 commit comments