diff --git a/Cargo.lock b/Cargo.lock index 56177f8cce..a44107be96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,12 +154,6 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - [[package]] name = "assert-json-diff" version = "2.0.2" @@ -347,7 +341,7 @@ dependencies = [ "bitflags 2.13.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.11.0", "log", "prettyplease", "proc-macro2", @@ -542,7 +536,6 @@ name = "build_common" version = "0.0.1" dependencies = [ "cbindgen 0.29.0", - "serde", "serde_json", ] @@ -639,7 +632,6 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "975982cdb7ad6a142be15bdf84aea7ec6a9e5d4d797c004d43185b24cfe4e684" dependencies = [ - "clap", "heck 0.5.0", "indexmap 2.12.1", "log", @@ -1295,7 +1287,6 @@ version = "0.0.1" dependencies = [ "anyhow", "arc-swap", - "arrayref", "base64 0.22.1", "bincode", "chrono", @@ -1304,7 +1295,6 @@ dependencies = [ "datadog-sidecar-macros", "futures", "http 1.4.2", - "http-body-util", "httpmock", "libc 0.2.186", "libdd-capabilities", @@ -1326,13 +1316,10 @@ dependencies = [ "libdd-trace-utils", "manual_future", "memory-stats", - "microseh", "nix 0.29.0", "prctl", "priority-queue", "rand 0.8.8", - "rmp-serde", - "sendfd", "serde", "serde_json", "serde_with", @@ -1347,8 +1334,6 @@ dependencies = [ "tracing-log", "tracing-subscriber", "winapi 0.3.9", - "windows 0.51.1", - "windows-sys 0.52.0", "zwohash", ] @@ -1371,10 +1356,8 @@ dependencies = [ "libdd-telemetry-ffi", "libdd-tinybytes", "libdd-trace-utils", - "paste", "rmp-serde", "serde_json", - "tempfile", "tracing", ] @@ -2302,13 +2285,14 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "1.6.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http 1.4.2", "http-body", @@ -2770,7 +2754,7 @@ dependencies = [ [[package]] name = "libdd-capabilities" -version = "3.0.1" +version = "4.0.0" dependencies = [ "anyhow", "bytes", @@ -2782,7 +2766,7 @@ dependencies = [ [[package]] name = "libdd-capabilities-impl" -version = "5.0.0" +version = "6.0.0" dependencies = [ "anyhow", "bytes", @@ -2796,15 +2780,15 @@ dependencies = [ [[package]] name = "libdd-common" -version = "6.0.0" +version = "7.0.0" dependencies = [ "anyhow", + "arc-swap", "bytes", "cc", "const_format", "criterion", "futures", - "futures-core", "futures-util", "hex", "http 1.4.2", @@ -2827,7 +2811,6 @@ dependencies = [ "regex-lite", "reqwest 0.13.2", "rustls", - "rustls-native-certs", "rustls-platform-verifier", "rustls-webpki", "serde", @@ -2835,7 +2818,6 @@ dependencies = [ "tempfile", "thiserror 2.0.18", "tokio", - "tokio-rustls", "tower-service", "windows-sys 0.52.0", ] @@ -2884,7 +2866,7 @@ dependencies = [ "page_size", "portable-atomic", "rand 0.8.8", - "schemars", + "schemars 0.8.21", "serde", "serde_json", "symbolic-common", @@ -2907,7 +2889,6 @@ dependencies = [ "libdd-common", "libdd-common-ffi", "libdd-crashtracker", - "serde", "serde_json", "symbolic-common", "symbolic-demangle", @@ -2917,7 +2898,7 @@ dependencies = [ [[package]] name = "libdd-data-pipeline" -version = "10.0.0" +version = "11.0.0" dependencies = [ "anyhow", "arc-swap", @@ -2928,7 +2909,6 @@ dependencies = [ "duplicate", "either", "futures", - "getrandom 0.2.15", "h2", "http 1.4.2", "http-body-util", @@ -2945,7 +2925,6 @@ dependencies = [ "libdd-shared-runtime", "libdd-telemetry", "libdd-tinybytes", - "libdd-trace-normalization", "libdd-trace-obfuscation", "libdd-trace-protobuf", "libdd-trace-stats", @@ -2970,8 +2949,9 @@ dependencies = [ [[package]] name = "libdd-data-pipeline-core" -version = "1.0.0" +version = "2.0.0" dependencies = [ + "anyhow", "bytes", "futures", "http 1.4.2", @@ -2979,6 +2959,7 @@ dependencies = [ "libdd-common", "libdd-tinybytes", "libdd-trace-obfuscation", + "libdd-trace-stats", "libdd-trace-utils", "serde_json", "thiserror 2.0.18", @@ -2987,7 +2968,7 @@ dependencies = [ [[package]] name = "libdd-ddsketch" -version = "1.1.1" +version = "1.1.2" dependencies = [ "criterion", "prost", @@ -2999,7 +2980,7 @@ dependencies = [ [[package]] name = "libdd-dogstatsd-client" -version = "6.0.0" +version = "7.0.0" dependencies = [ "anyhow", "async-trait", @@ -3034,12 +3015,9 @@ dependencies = [ "pyo3", "semver", "serde", - "serde-bool", "serde_json", - "serde_with", "thiserror 2.0.18", "tokio", - "url", ] [[package]] @@ -3057,7 +3035,6 @@ dependencies = [ "anyhow", "bincode", "criterion", - "futures", "glibc_version", "io-lifetimes", "libc 0.2.186", @@ -3070,14 +3047,11 @@ dependencies = [ "memfd", "nix 0.29.0", "page_size", - "pretty_assertions", - "sendfd", "serde", "spawn_worker", "tempfile", "tokio", "tracing", - "tracing-subscriber", "winapi 0.3.9", "windows-sys 0.48.0", "zwohash", @@ -3095,7 +3069,7 @@ dependencies = [ [[package]] name = "libdd-library-config" -version = "4.0.0" +version = "4.1.0" dependencies = [ "anyhow", "libc 0.2.186", @@ -3103,7 +3077,6 @@ dependencies = [ "memfd", "prost", "rand 0.8.8", - "rmp", "rmp-serde", "serde", "serde_yaml", @@ -3121,7 +3094,6 @@ dependencies = [ "libdd-common", "libdd-common-ffi", "libdd-library-config", - "tempfile", ] [[package]] @@ -3173,7 +3145,6 @@ dependencies = [ "serde_json", "tokio", "tokio-util", - "uuid", ] [[package]] @@ -3196,24 +3167,20 @@ dependencies = [ "bitmaps", "bolero", "byteorder", - "bytes", "chrono", "criterion", "crossbeam-channel", "crossbeam-utils", "cxx", "cxx-build", - "futures", "hashbrown 0.17.1", "http 1.4.2", - "http-body-util", - "httparse", "indexmap 2.12.1", "libdd-alloc", "libdd-common", "libdd-profiling", "libdd-profiling-protobuf", - "mime", + "opentelemetry-proto", "parking_lot", "proptest", "prost", @@ -3245,7 +3212,7 @@ dependencies = [ [[package]] name = "libdd-remote-config" -version = "5.0.0" +version = "6.0.0" dependencies = [ "anyhow", "base64 0.22.1", @@ -3268,10 +3235,8 @@ dependencies = [ "manual_future", "prost", "rand 0.8.8", - "ring", "serde", "serde_json", - "serde_with", "sha2", "strum", "strum_macros", @@ -3285,7 +3250,7 @@ dependencies = [ [[package]] name = "libdd-shared-runtime" -version = "4.0.0" +version = "5.0.0" dependencies = [ "async-trait", "futures", @@ -3301,14 +3266,13 @@ dependencies = [ [[package]] name = "libdd-telemetry" -version = "8.0.0" +version = "9.0.0" dependencies = [ "anyhow", "async-trait", "base64 0.22.1", "bytes", "futures", - "getrandom 0.2.15", "hashbrown 0.17.1", "http 1.4.2", "httpmock", @@ -3338,7 +3302,6 @@ version = "0.0.1" dependencies = [ "build_common", "function_name", - "libc 0.2.186", "libdd-capabilities-impl", "libdd-common", "libdd-common-ffi", @@ -3350,7 +3313,7 @@ dependencies = [ [[package]] name = "libdd-tinybytes" -version = "1.1.3" +version = "1.1.4" dependencies = [ "libdd-tinybytes", "once_cell", @@ -3364,7 +3327,7 @@ dependencies = [ [[package]] name = "libdd-trace-normalization" -version = "4.0.0" +version = "4.1.0" dependencies = [ "anyhow", "arbitrary", @@ -3376,7 +3339,7 @@ dependencies = [ [[package]] name = "libdd-trace-obfuscation" -version = "8.0.0" +version = "9.0.0" dependencies = [ "anyhow", "criterion", @@ -3390,11 +3353,12 @@ dependencies = [ "percent-encoding", "serde", "serde_json", + "thiserror 2.0.18", ] [[package]] name = "libdd-trace-protobuf" -version = "5.0.0" +version = "5.0.1" dependencies = [ "bolero", "prost", @@ -3408,7 +3372,7 @@ dependencies = [ [[package]] name = "libdd-trace-stats" -version = "9.0.0" +version = "10.0.0" dependencies = [ "anyhow", "arc-swap", @@ -3432,14 +3396,13 @@ dependencies = [ "rmp-serde", "serde", "tokio", - "tokio-util", "tracing", "web-time", ] [[package]] name = "libdd-trace-utils" -version = "12.0.0" +version = "13.0.0" dependencies = [ "anyhow", "base64 0.22.1", @@ -3552,7 +3515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3701,17 +3664,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "microseh" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26b2a7c5ccfb370edd57fda423f3a551516ee127e10bc22a6215e8c63b20a38" -dependencies = [ - "cc", - "libc 0.2.186", - "windows-sys 0.42.0", -] - [[package]] name = "mime" version = "0.3.17" @@ -4088,6 +4040,36 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "opentelemetry" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cdb0b1b267eb9db3331b434ed9ddab10d50e280a9adf9d13e5233e2002b61b5" +dependencies = [ + "js-sys", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25da1ac11a0aeccf38d7f77ee0348715adaf8340f65ad46c94a02c6b20e2f65d" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb39533d9d1c912123efd7d41d7e0c29d16917b60ce15b4c8d87cb1af7f67520" +dependencies = [ + "opentelemetry", + "portable-atomic", +] + [[package]] name = "os_info" version = "3.14.0" @@ -4337,7 +4319,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "059a34f111a9dee2ce1ac2826a68b24601c4298cfeb1a587c3cb493d5ab46f52" dependencies = [ - "libc 0.1.12", + "libc 0.2.186", "nix 0.30.1", ] @@ -4491,7 +4473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck 0.5.0", - "itertools 0.10.5", + "itertools 0.11.0", "log", "multimap", "petgraph", @@ -4510,7 +4492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.11.0", "proc-macro2", "quote", "syn 2.0.118", @@ -4584,9 +4566,9 @@ checksum = "7dc55d7dec32ecaf61e0bd90b3d2392d721a28b95cfd23c3e176eccefbeab2f2" [[package]] name = "pyo3" -version = "0.28.3" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" +checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b" dependencies = [ "libc 0.2.186", "once_cell", @@ -4598,18 +4580,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.28.3" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" +checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.28.3" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" +checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327" dependencies = [ "libc 0.2.186", "pyo3-build-config", @@ -4617,9 +4599,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.28.3" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" +checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -4629,13 +4611,12 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.28.3" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" +checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952" dependencies = [ "heck 0.5.0", "proc-macro2", - "pyo3-build-config", "quote", "syn 2.0.118", ] @@ -5326,16 +5307,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sendfd" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604b71b8fc267e13bb3023a2c901126c8f349393666a6d98ac1ae5729b701798" -dependencies = [ - "libc 0.2.186", - "tokio", -] - [[package]] name = "serde" version = "1.0.228" @@ -5346,15 +5317,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-bool" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fdd050c9c2ed5ae1fb29e71be0a6efdd9df43c7cb13ea5826528cfe10c51db0" -dependencies = [ - "serde", -] - [[package]] name = "serde-transcode" version = "1.1.1" @@ -5419,7 +5381,6 @@ version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ - "indexmap 2.12.1", "itoa 1.0.14", "memchr", "serde", @@ -6792,7 +6753,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -6904,21 +6865,6 @@ dependencies = [ "windows-link 0.1.1", ] -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.45.0" diff --git a/appsec/tests/integration/src/test/www/signal-flush/run.sh b/appsec/tests/integration/src/test/www/signal-flush/run.sh new file mode 100755 index 0000000000..cb7e4ac5c5 --- /dev/null +++ b/appsec/tests/integration/src/test/www/signal-flush/run.sh @@ -0,0 +1,21 @@ +#!/bin/bash -e + +set -x + +mkdir -p /tmp/logs +logs=( + /tmp/logs/appsec.log + /tmp/logs/helper.log + /tmp/logs/php_error.log + /tmp/logs/php_server.log + /tmp/logs/sidecar.log +) +touch "${logs[@]}" + +enable_extensions.sh +echo datadog.trace.cli_enabled=true >> /etc/php/php.ini + +php -S 0.0.0.0:80 -t /var/www/public \ + >> /tmp/logs/php_server.log 2>&1 & + +exec tail -n +1 -F "${logs[@]}" diff --git a/appsec/tests/integration/src/test/www/signal-flush/signal_flush_worker.php b/appsec/tests/integration/src/test/www/signal-flush/signal_flush_worker.php new file mode 100644 index 0000000000..c9e1866a99 --- /dev/null +++ b/appsec/tests/integration/src/test/www/signal-flush/signal_flush_worker.php @@ -0,0 +1,49 @@ + i32 { + let result = flush.run(); + if terminate_process { + libc::_exit(0); + } + result +} diff --git a/config.m4 b/config.m4 index bbd6b8a03c..5630a7c7d2 100644 --- a/config.m4 +++ b/config.m4 @@ -419,8 +419,13 @@ if test "$PHP_DDTRACE" != "no" && test "$PHP_DDTRACE_PROFILING" = "no"; then PHP_CHECK_LIBRARY(rt, shm_open, [EXTRA_LDFLAGS="$EXTRA_LDFLAGS -lrt"; DDTRACE_SHARED_LIBADD="${DDTRACE_SHARED_LIBADD:-} -lrt"]) - dnl rust imports these, so we need them to link + dnl Platform linker requirements for the Rust library case $host_os in + linux*) + dnl The signal worker calls _exit with shared TLS. Resolve libc symbols + dnl when loading the extension, including when Rust is linked as a static archive. + EXTRA_LDFLAGS="$EXTRA_LDFLAGS -Wl,-z,now" + ;; darwin*) EXTRA_LDFLAGS="$EXTRA_LDFLAGS -framework CoreFoundation -framework Security" PHP_ADD_FRAMEWORK([CoreFoundation]) diff --git a/ext/sidecar.c b/ext/sidecar.c index e5439e451c..37dc5e9398 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -13,6 +13,7 @@ #include "telemetry.h" #include "process_tags.h" #include "remote_config.h" +#include "signals.h" #include "string_utils.h" #include "target_metadata.h" #include "ffi_utils.h" @@ -113,8 +114,12 @@ DATADOG_PUBLIC ddog_SidecarTransport **ddtrace_get_sidecar_transport(void) { } #endif +static void dd_sidecar_setup_signal_transport(ddog_SidecarTransport *transport, bool replace); + static ddog_SidecarTransport *datadog_sidecar_connect_callback(void) { - return datadog_sidecar_connect(false); + ddog_SidecarTransport *transport = datadog_sidecar_connect(false); + dd_sidecar_setup_signal_transport(transport, true); + return transport; } static void dd_sidecar_post_connect(ddog_SidecarTransport **transport, bool is_fork, const char *logpath) { @@ -207,6 +212,7 @@ static void datadog_sidecar_setup_thread_mode(void); static void dd_sidecar_on_reconnect(ddog_SidecarTransport *transport) { if (!datadog_endpoint || !dogstatsd_endpoint) { + dd_sidecar_setup_signal_transport(transport, true); return; } @@ -233,6 +239,10 @@ static void dd_sidecar_on_reconnect(ddog_SidecarTransport *transport) { } tsrm_mutex_unlock(DATADOG_G(sidecar_universal_service_tags_mutex)); + + // Reconnect callbacks run before the new sender replaces DATADOG_G(sidecar). + // Prepare against the replacement passed to this callback, not the old sender. + dd_sidecar_setup_signal_transport(transport, true); } static ddog_SidecarTransport *dd_sidecar_connect(bool as_worker, bool is_fork) { @@ -308,6 +318,28 @@ static ddog_SidecarTransport *dd_sidecar_connect(bool as_worker, bool is_fork) { return sidecar_transport; } +static void dd_sidecar_setup_signal_transport(ddog_SidecarTransport *transport, bool replace) { +#ifdef __linux__ + if ((!replace && datadog_signals_has_sidecar_flush()) || !transport || + (!get_global_DD_TRACE_FORCE_FLUSH_ON_SIGTERM() && !get_global_DD_TRACE_FORCE_FLUSH_ON_SIGINT())) { + return; + } + + ddog_SignalFlush *flush = NULL; + bool prepared = datadog_ffi_try("Failed preparing sidecar signal flush", + ddog_sidecar_prepare_signal_flush( + transport, (ddog_SidecarFlushOptions){.traces_and_stats = true}, &flush)); + if (prepared || replace) { + // Takes ownership, including when another normal thread published first. + // A failed refresh clears the stale object so a later RINIT can retry. + datadog_signals_set_sidecar_flush(flush, replace); + } +#else + (void)transport; + (void)replace; +#endif +} + static void datadog_sidecar_setup_thread_mode() { #ifndef _WIN32 int32_t current_pid = (int32_t)getpid(); @@ -470,6 +502,7 @@ void datadog_sidecar_setup(ddog_RemoteConfigFlags flags) { if (DATADOG_G(sidecar) && !datadog_sidecar_for_signal) { datadog_sidecar_for_signal = DATADOG_G(sidecar); } + dd_sidecar_setup_signal_transport(DATADOG_G(sidecar), false); } void datadog_sidecar_minit(void) { @@ -489,6 +522,9 @@ void datadog_sidecar_minit(void) { void datadog_sidecar_handle_fork(void) { #ifndef _WIN32 +#ifdef __linux__ + datadog_signals_reset_sidecar_flush_after_fork(); +#endif ddog_RemoteConfigFlags flags = {0}; bool enable_sidecar = datadog_sidecar_should_enable(&flags); @@ -546,6 +582,7 @@ void datadog_sidecar_handle_fork(void) { if (DATADOG_G(sidecar)) { datadog_sidecar_for_signal = DATADOG_G(sidecar); } + dd_sidecar_setup_signal_transport(DATADOG_G(sidecar), false); #endif } @@ -580,6 +617,7 @@ void datadog_sidecar_ensure_active(void) { datadog_sidecar_for_signal = DATADOG_G(sidecar); } } + dd_sidecar_setup_signal_transport(DATADOG_G(sidecar), false); } void datadog_sidecar_finalize(bool clear_id) { diff --git a/ext/signals.c b/ext/signals.c index 8926b4b442..45bc57389f 100644 --- a/ext/signals.c +++ b/ext/signals.c @@ -46,7 +46,11 @@ #endif #if __linux +#include +#include #include +#include +#include #include #endif @@ -300,85 +304,164 @@ static struct sigaction dd_sigint_sigterm_sigaction; static struct sigaction dd_sigterm_prev_sigaction; static struct sigaction dd_sigint_prev_sigaction; -struct { - int sig; - siginfo_t si; - void *uc; -} dd_signal_data; - -static int dd_call_prev_handler(bool flush) { - struct sigaction prev_sigaction = dd_signal_data.sig == SIGINT ? dd_sigint_prev_sigaction : dd_sigterm_prev_sigaction; - void *prev_handler = (prev_sigaction.sa_flags & SA_SIGINFO) ? (void *)prev_sigaction.sa_sigaction : (void *)prev_sigaction.sa_handler; - if (prev_handler == SIG_IGN) { - return 0; - } +// The request is prepared in ordinary context. Publication, signal delivery, +// and shutdown compete for this gate; a claimed request is never replaced or rearmed. +enum { + DD_SIGNAL_DISABLED, + DD_SIGNAL_INSTALLING, + DD_SIGNAL_READY, + DD_SIGNAL_FLUSHING_DEFAULT, + DD_SIGNAL_FLUSHING_CUSTOM, + DD_SIGNAL_STOPPED, +}; +static _Atomic(int) dd_signal_state; +static _Atomic(int) dd_signal_owner_pid; +static ddog_SignalFlush *dd_signal_flush; +// Only one worker can use this stack. Its storage lives as long as the extension. +static _Alignas(16) char dd_signal_cleanup_stack[MIN_STACKSZ]; + +// Before READY is published: -1 (not started). clone's PARENT_SETTID writes a positive +// TID; CHILD_CLEARTID clears it and wakes futex waiters when the worker has fully exited. +// A failed start clears it explicitly. This also covers a child exiting before clone returns. +static _Atomic(int) dd_signal_worker_tid; +_Static_assert(ATOMIC_INT_LOCK_FREE == 2, "signal atomics must be lock-free"); +_Static_assert(sizeof(dd_signal_worker_tid) == sizeof(int), "signal TID must have the kernel int layout"); + +bool datadog_signals_has_sidecar_flush(void) { + return atomic_load(&dd_signal_state) != DD_SIGNAL_DISABLED; +} - if (flush) { - ddog_sidecar_flush(&datadog_sidecar_for_signal, (ddog_SidecarFlushOptions){.traces_and_stats = true}); +void datadog_signals_set_sidecar_flush(ddog_SignalFlush *flush, bool replace) { + if (!flush && !replace) { + return; } - - if (prev_handler == SIG_DFL) { - _exit(0); + // A handler must not suspend its own publisher while INSTALLING. Other threads + // may spin on that state, so the protected section must contain no blocking calls. + sigset_t publication_signals, old_signals; + sigfillset(&publication_signals); + if (sigprocmask(SIG_BLOCK, &publication_signals, &old_signals) < 0) { + ddog_sidecar_signal_flush_drop(flush); + return; } - - if (prev_sigaction.sa_flags & SA_SIGINFO) { - (*prev_sigaction.sa_sigaction)(dd_signal_data.sig, &dd_signal_data.si, dd_signal_data.uc); - } else { - (*prev_sigaction.sa_handler)(dd_signal_data.sig); + int state = atomic_load(&dd_signal_state); + if ((state == DD_SIGNAL_DISABLED || (replace && state == DD_SIGNAL_READY)) && + atomic_compare_exchange_strong(&dd_signal_state, &state, DD_SIGNAL_INSTALLING)) { + ddog_SignalFlush *previous = dd_signal_flush; + dd_signal_flush = flush; + atomic_store(&dd_signal_worker_tid, -1); + atomic_store(&dd_signal_state, flush ? DD_SIGNAL_READY : DD_SIGNAL_DISABLED); + // Drop AFTER publication: another thread's handler may have interrupted malloc. + flush = previous; } + ddog_sidecar_signal_flush_drop(flush); + sigprocmask(SIG_SETMASK, &old_signals, NULL); +} - return 0; +void datadog_signals_reset_sidecar_flush_after_fork(void) { + // The old PID makes handlers ignore inherited state until this reset is complete. + ddog_sidecar_signal_flush_drop(dd_signal_flush); + dd_signal_flush = NULL; + atomic_store(&dd_signal_worker_tid, 0); + atomic_store(&dd_signal_state, DD_SIGNAL_DISABLED); + atomic_store(&dd_signal_owner_pid, getpid()); +} +static void dd_signals_drop_sidecar_flush(void) { + // A signal may cause clean shutdown in a fork child before its PHP fork hook runs. + if (atomic_load(&dd_signal_owner_pid) != getpid()) { + datadog_signals_reset_sidecar_flush_after_fork(); + } + for (;;) { + int state = atomic_load(&dd_signal_state); + if (state == DD_SIGNAL_INSTALLING) { + sched_yield(); + continue; + } + if (state == DD_SIGNAL_FLUSHING_DEFAULT || state == DD_SIGNAL_FLUSHING_CUSTOM) { + int tid; + while ((tid = atomic_load(&dd_signal_worker_tid)) != 0) { + if (tid == -1) { + sched_yield(); // The handler has claimed the request but not finished clone. + } else { + // Kernel clear_child_tid uses a shared futex. Reload after EINTR/EAGAIN + // or a spurious wake; only zero proves the request and extension can be released. + syscall(SYS_futex, &dd_signal_worker_tid, FUTEX_WAIT, tid, NULL, NULL, 0); + } + } + } + if (atomic_compare_exchange_strong(&dd_signal_state, &state, DD_SIGNAL_STOPPED)) { + break; + } + } + ddog_sidecar_signal_flush_drop(dd_signal_flush); + dd_signal_flush = NULL; } -static int dd_sigterm_cleanup_thread(void *arg) { - // Block all signals to prevent delivery to this thread - sigset_t set; - sigfillset(&set); - sigprocmask(SIG_BLOCK, &set, NULL); - - // Make the Go runtime believe, we are actually running on a signal stack - stack_t altstack; - altstack.ss_sp = dd_signal_async_stack; - if (altstack.ss_sp) { - altstack.ss_size = dd_signal_async_stack_size; - altstack.ss_flags = 0; - sigaltstack(&altstack, NULL); +// True means a default-disposition worker owns process termination. False means chain +// the previous handler, including when startup fails or a custom handler owns shutdown. +static bool dd_signals_start_flush(bool terminate) { + if (getpid() != atomic_load(&dd_signal_owner_pid)) { + return false; + } + int state = DD_SIGNAL_READY; + int claimed = terminate ? DD_SIGNAL_FLUSHING_DEFAULT : DD_SIGNAL_FLUSHING_CUSTOM; + while (!atomic_compare_exchange_strong(&dd_signal_state, &state, claimed)) { + if (state != DD_SIGNAL_INSTALLING) { + return terminate && state == DD_SIGNAL_FLUSHING_DEFAULT; + } + sched_yield(); + state = DD_SIGNAL_READY; } - return dd_call_prev_handler(true); + int flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | + CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID; + int result = datadog_clone_thread(datadog_sidecar_signal_flush_run, dd_signal_cleanup_stack + MIN_STACKSZ, + flags, dd_signal_flush, terminate, &dd_signal_worker_tid); + if (result < 0) { + atomic_store(&dd_signal_worker_tid, 0); + } + return result >= 0 && terminate; } static void dd_sigint_sigterm_handler(int sig, siginfo_t *si, void *uc) { - dd_signal_data.sig = sig; - memcpy(&dd_signal_data.si, si, sizeof(*si)); - dd_signal_data.uc = uc; - - if (datadog_sidecar_for_signal) { - // Spawn a thread using clone() to perform sidecar cleanup asynchronously to avoid async unsafeness in the signal handler - void *stack_top = dd_signal_async_stack + dd_signal_async_stack_size; - int flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM; - if (datadog_clone_thread(dd_sigterm_cleanup_thread, stack_top, flags, NULL) < 0) { - // If the cleanup thread could not be started, we just do it ourselves. Will block, but that's okay then. - dd_call_prev_handler(true); - } + struct sigaction *previous = sig == SIGINT ? &dd_sigint_prev_sigaction : &dd_sigterm_prev_sigaction; + if (previous->sa_handler == SIG_IGN) { + return; + } + int saved_errno = errno; + // Block signals before claiming the request: a nested handler must not enter PHP + // shutdown while clone is starting. libc's mask APIs exclude reserved signals, so + // use the kernel mask for the worker's inherited TLS. The PHP thread can use syscall(). + uint64_t all_signals = UINT64_MAX, old_signals; + bool defer_termination = false; + if (syscall(SYS_rt_sigprocmask, SIG_SETMASK, &all_signals, &old_signals, sizeof(all_signals)) == 0) { + defer_termination = dd_signals_start_flush(previous->sa_handler == SIG_DFL); + syscall(SYS_rt_sigprocmask, SIG_SETMASK, &old_signals, NULL, sizeof(old_signals)); + } + errno = saved_errno; + if (defer_termination) { + return; + } + if (previous->sa_handler == SIG_DFL) { + _exit(0); + } else if (previous->sa_flags & SA_SIGINFO) { + previous->sa_sigaction(sig, si, uc); } else { - dd_call_prev_handler(false); + previous->sa_handler(sig); } } #endif void datadog_signals_minit(void) { #if __linux + atomic_store(&dd_signal_owner_pid, getpid()); dd_sigint_sigterm_sigaction.sa_sigaction = dd_sigint_sigterm_handler; dd_sigint_sigterm_sigaction.sa_flags = SA_SIGINFO; sigemptyset(&dd_sigint_sigterm_sigaction.sa_mask); if (get_global_DD_TRACE_FORCE_FLUSH_ON_SIGTERM()) { - dd_signals_init_async_stack(); sigaction(SIGTERM, &dd_sigint_sigterm_sigaction, &dd_sigterm_prev_sigaction); } if (get_global_DD_TRACE_FORCE_FLUSH_ON_SIGINT()) { - dd_signals_init_async_stack(); sigaction(SIGINT, &dd_sigint_sigterm_sigaction, &dd_sigint_prev_sigaction); } #endif @@ -386,6 +469,8 @@ void datadog_signals_minit(void) { void datadog_signals_mshutdown(void) { #if __linux + // wait for the signal cleanup thread to exit + dd_signals_drop_sidecar_flush(); if (dd_sigint_sigterm_sigaction.sa_sigaction) { if (get_global_DD_TRACE_FORCE_FLUSH_ON_SIGTERM()) { sigaction(SIGTERM, &dd_sigterm_prev_sigaction, NULL); diff --git a/ext/signals.h b/ext/signals.h index 326abb9542..94111b5e80 100644 --- a/ext/signals.h +++ b/ext/signals.h @@ -1,9 +1,21 @@ #ifndef DD_TRACE_SIGNALS_H #define DD_TRACE_SIGNALS_H +#include + +typedef struct ddog_SignalFlush ddog_SignalFlush; + void datadog_set_coredumpfilter(void); void datadog_signals_first_rinit(void); void datadog_signals_minit(void); void datadog_signals_mshutdown(void); +bool datadog_signals_has_sidecar_flush(void); +// Always takes ownership of `flush`, including when it cannot be published. +// With `replace` false, publishes only when no flush object is installed. With +// `replace` true, replaces or clears an installed object unless a signal handler +// has already claimed it. Passing NULL while replacing clears an unclaimed stale +// object so setup can retry later. +void datadog_signals_set_sidecar_flush(ddog_SignalFlush *flush, bool replace); +void datadog_signals_reset_sidecar_flush_after_fork(void); #endif // DD_TRACE_SIGNALS_H diff --git a/ext/threads.c b/ext/threads.c index aee7d37476..aa2fd53245 100644 --- a/ext/threads.c +++ b/ext/threads.c @@ -156,23 +156,27 @@ __asm__( ".hidden datadog_clone_thread\n" ".type datadog_clone_thread,@function\n" "datadog_clone_thread:\n" - /* in: rdi = fn, rsi = stack_top, edx = flags, rcx = arg */ + /* in: rdi = fn, rsi = stack_top, edx = flags, rcx = arg, + * r8b = terminate_process, r9 = tid */ " andq $-16, %rsi\n" /* align the child stack */ - " subq $16, %rsi\n" /* hand fn and arg over on it */ + " subq $32, %rsi\n" /* hand fn, arg and flag over on it */ " movq %rdi, 0(%rsi)\n" " movq %rcx, 8(%rsi)\n" + " movb %r8b, 16(%rsi)\n" /* syscall: rdi = flags, rsi = newsp, rdx = parent_tid, r10 = child_tid, r8 = tls */ " movl %edx, %edi\n" - " xorl %edx, %edx\n" - " xorl %r10d, %r10d\n" + " movq %r9, %rdx\n" /* independent parent_tid / clear_child_tid word */ + " movq %r9, %r10\n" " xorl %r8d, %r8d\n" " movl $56, %eax\n" /* SYS_clone */ " syscall\n" " testq %rax, %rax\n" /* parent: tid or -errno; child: 0 */ " jnz 1f\n" " xorl %ebp, %ebp\n" /* end the frame pointer chain */ - " popq %rax\n" /* fn */ - " popq %rdi\n" /* arg */ + " movq 0(%rsp), %rax\n" /* fn */ + " movq 8(%rsp), %rdi\n" /* arg */ + " movzbl 16(%rsp), %esi\n" /* terminate_process */ + " addq $32, %rsp\n" /* restore alignment before call */ " callq *%rax\n" " movl %eax, %edi\n" /* fn's return value is the thread's exit status */ " movl $60, %eax\n" /* SYS_exit -- this thread only, not exit_group */ @@ -180,6 +184,7 @@ __asm__( " hlt\n" /* unreachable */ "1: ret\n" ".size datadog_clone_thread,.-datadog_clone_thread\n"); + #elif defined(__aarch64__) __asm__( ".text\n" @@ -187,30 +192,43 @@ __asm__( ".hidden datadog_clone_thread\n" ".type datadog_clone_thread,%function\n" "datadog_clone_thread:\n" - /* in: x0 = fn, x1 = stack_top, w2 = flags, x3 = arg */ + /* in: x0 = fn, x1 = stack_top, w2 = flags, x3 = arg, + * w4 = terminate_process, x5 = tid */ " and x1, x1, #-16\n" /* align the child stack */ - " stp x0, x3, [x1, #-16]!\n" /* hand fn and arg over on it; x1 becomes newsp */ + " sub x1, x1, #32\n" + " stp x0, x3, [x1]\n" /* hand fn and arg over on it; x1 is newsp */ + " strb w4, [x1, #16]\n" /* terminate_process */ /* syscall: x0 = flags, x1 = newsp, x2 = parent_tid, x3 = tls, x4 = child_tid */ " mov w0, w2\n" - " mov x2, #0\n" + " mov x2, x5\n" /* same independent parent_tid / clear_child_tid word */ " mov x3, #0\n" - " mov x4, #0\n" + " mov x4, x5\n" " mov x8, #220\n" /* SYS_clone */ " svc #0\n" " cbz x0, 1f\n" /* parent: tid or -errno; child: 0 */ " ret\n" - "1: ldp x1, x0, [sp], #16\n" /* x1 = fn, x0 = arg */ + "1: ldp x16, x0, [sp]\n" /* x16 = fn, x0 = arg */ + " ldrb w1, [sp, #16]\n" /* terminate_process */ + " add sp, sp, #32\n" " mov x29, #0\n" /* end the frame pointer chain */ - " blr x1\n" /* fn's return value is left in w0 */ + " blr x16\n" /* fn's return value is left in w0 */ " mov w8, #93\n" /* SYS_exit -- this thread only, not exit_group */ " svc #0\n" " brk #0\n" /* unreachable */ ".size datadog_clone_thread,.-datadog_clone_thread\n"); + #else -#include -int datadog_clone_thread(int (*fn)(void *), void *stack_top, int flags, void *arg) { - return clone(fn, stack_top, flags, arg); +int datadog_clone_thread(datadog_raw_clone_fn fn, void *stack_top, int flags, + const struct ddog_SignalFlush *arg, bool terminate_process, _Atomic(int) *tid) { + (void)fn; + (void)stack_top; + (void)flags; + (void)arg; + (void)terminate_process; + (void)tid; + return -1; } + #endif #endif diff --git a/ext/threads.h b/ext/threads.h index a7678b05c1..740b6b6c73 100644 --- a/ext/threads.h +++ b/ext/threads.h @@ -1,6 +1,8 @@ #ifndef DATADOG_THREADS_H #define DATADOG_THREADS_H +#include +#include #include #include @@ -27,7 +29,13 @@ TSRM_API int tsrm_mutex_unlock(MUTEX_T mutexp); #endif #ifdef __linux__ -int datadog_clone_thread(int (*fn)(void *), void *stack_top, int flags, void *arg); +#include + +struct ddog_SignalFlush; +typedef int32_t (*datadog_raw_clone_fn)(const struct ddog_SignalFlush *, bool); + +int datadog_clone_thread(datadog_raw_clone_fn fn, void *stack_top, int flags, + const struct ddog_SignalFlush *arg, bool terminate_process, _Atomic(int) *tid); #endif #endif // DATADOG_THREADS_H diff --git a/libdatadog b/libdatadog index 836ff60ac4..89ee2a8b04 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit 836ff60ac46244268c6b62b5246132a08cf09512 +Subproject commit 89ee2a8b042db71e99017d5156facf5dd897c36b