diff --git a/ddtrace/span.py b/ddtrace/span.py index 97ab6c720e7..105ca804688 100644 --- a/ddtrace/span.py +++ b/ddtrace/span.py @@ -69,15 +69,25 @@ def __init__(self, self._tracer = tracer self._parent = None + # state + self._finished = False + def finish(self, finish_time=None): """ Mark the end time of the span and submit it to the tracer. + If the span has already been finished don't do anything :param int finish_time: the end time of the span in seconds. Defaults to now. """ - ft = finish_time or time.time() - # be defensive so we don't die if start isn't set - self.duration = ft - (self.start or ft) + if self._finished: + return + self._finished = True + + if self.duration is None: + ft = finish_time or time.time() + # be defensive so we don't die if start isn't set + self.duration = ft - (self.start or ft) + if self._tracer: self._tracer.record(self) diff --git a/tests/test_span.py b/tests/test_span.py index 9322f3f177b..c4e36beb74d 100644 --- a/tests/test_span.py +++ b/tests/test_span.py @@ -57,6 +57,26 @@ def test_finish(): s2 = Span(tracer=None, name="foo") s2.finish() +def test_finish_called_multiple_times(): + # we should only record a span the first time finish is called on it + dt = DummyTracer() + assert dt.spans_recorded == 0 + s = Span(dt, 'bar') + s.finish() + s.finish() + assert dt.spans_recorded == 1 + + +def test_finish_set_span_duration(): + # If set the duration on a span, the span should be recorded with this + # duration + dt = DummyTracer() + assert dt.last_span is None + s = Span(dt, 'foo') + s.duration = 1337.0 + s.finish() + assert dt.last_span.duration == 1337.0 + def test_traceback_with_error(): s = Span(None, "foo") try: @@ -121,7 +141,8 @@ class DummyTracer(object): def __init__(self): self.last_span = None + self.spans_recorded = 0 def record(self, span): self.last_span = span - + self.spans_recorded += 1