diff --git a/examples/example.c b/examples/example.c index 99a04d2fc..29e5fd140 100644 --- a/examples/example.c +++ b/examples/example.c @@ -232,6 +232,10 @@ main(int argc, char **argv) sentry_value_t grandchild_ctx = sentry_span_start_child(child_ctx, "littlest.teapot", NULL); + sentry_value_t unfinished_ctx + = sentry_span_start_child(child_ctx, "large.teapot", NULL); + + sentry_value_decref(unfinished_ctx); sentry_span_finish(grandchild_ctx); sentry_span_finish(child_ctx); } diff --git a/src/sentry_core.c b/src/sentry_core.c index b78767f20..4054fe0f7 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -808,7 +808,24 @@ sentry_transaction_finish() sentry_value_remove_by_key(tx, "description"); sentry_value_remove_by_key(tx, "status"); - // TODO: prune unfinished child spans + sentry_value_t spans = sentry_value_get_by_key(tx, "spans"); + size_t span_count = sentry_value_get_length(spans); + // Go backwards to avoid accidentally skipping elements + for (size_t i = span_count; i > 0; i--) { + // TODO: Assume that tags and data from scope do not need to be merged + // into spans. This may be completely wrong. + bool should_remove = false; + { + sentry_value_t span = sentry_value_get_by_index(spans, i - 1); + should_remove = sentry_value_is_null( + sentry_value_get_by_key(span, "timestamp")); + } + if (should_remove) { + SENTRY_DEBUG("dropped an unfinished span from transaction"); + sentry_value_remove_by_index(spans, i - 1); + } + } + // This decrefs for us, generates an event ID, merges scope return sentry__capture_event(tx); } diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index e505f11eb..f11e25cc0 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -455,5 +455,61 @@ SENTRY_TEST(overflow_spans) sentry_close(); } +static void +check_spans(sentry_envelope_t *envelope, void *data) +{ + uint64_t *called = data; + *called += 1; + + sentry_value_t transaction = sentry_envelope_get_transaction(envelope); + TEST_CHECK(!sentry_value_is_null(transaction)); + + size_t span_count = sentry_value_get_length( + sentry_value_get_by_key(transaction, "spans")); + TEST_CHECK_INT_EQUAL(span_count, 1); + + sentry_envelope_free(envelope); +} + +SENTRY_TEST(drop_unfinished_spans) +{ + uint64_t called_transport = 0; + + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + + sentry_transport_t *transport = sentry_transport_new(check_spans); + sentry_transport_set_state(transport, &called_transport); + sentry_options_set_transport(options, transport); + + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_options_set_max_spans(options, 2); + sentry_init(options); + + sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); + sentry_transaction_start(tx_cxt); + + sentry_value_t child + = sentry_span_start_child(sentry_value_new_null(), "honk", "goose"); + TEST_CHECK(!sentry_value_is_null(child)); + + sentry_value_t grandchild = sentry_span_start_child(child, "beep", "car"); + TEST_CHECK(!sentry_value_is_null(grandchild)); + sentry_span_finish(grandchild); + + sentry_value_t scope_tx = sentry__scope_get_span(); + TEST_CHECK_INT_EQUAL( + sentry_value_get_length(sentry_value_get_by_key(scope_tx, "spans")), 2); + + sentry_uuid_t event_id = sentry_transaction_finish(); + TEST_CHECK(!sentry_uuid_is_nil(&event_id)); + + sentry_value_decref(child); + + sentry_close(); + + TEST_CHECK_INT_EQUAL(called_transport, 1); +} + #undef IS_NULL #undef CHECK_STRING_PROPERTY diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 69fd32a58..de9b83afa 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -14,6 +14,7 @@ XX(child_spans) XX(concurrent_init) XX(count_sampled_events) XX(custom_logger) +XX(drop_unfinished_spans) XX(dsn_parsing_complete) XX(dsn_parsing_invalid) XX(dsn_store_url_with_path)