Skip to content

Commit 48c4254

Browse files
authored
Profiling: fix UB and simplify SystemSettings (#3578)
* refactor(profiling): simplify SystemSettings * fix(profiling): UB with SystemSettings in rinit * extract Profiler::is_timeline_enabled helper This also serves to document some AtomicPtr operations and orderings. * experiment with SystemSettingsState tracking
1 parent e252274 commit 48c4254

3 files changed

Lines changed: 137 additions & 126 deletions

File tree

profiling/src/config.rs

Lines changed: 98 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,36 @@ use crate::bindings::{
88
};
99
use crate::zend::zai_str_from_zstr;
1010
use core::fmt::{Display, Formatter};
11-
use core::mem::{swap, transmute, MaybeUninit};
11+
use core::mem::transmute;
1212
use core::ptr;
1313
use core::str::FromStr;
1414
use libc::{c_char, c_int};
1515
use libdd_common::tag::{parse_tags, Tag};
1616
pub use libdd_profiling::exporter::Uri;
17-
use log::{warn, LevelFilter};
17+
use log::{debug, warn, LevelFilter};
1818
use std::borrow::Cow;
1919
use std::ffi::CString;
2020
use std::path::{Path, PathBuf};
2121

22+
#[derive(Copy, Clone, Debug, Default)]
23+
pub enum SystemSettingsState {
24+
/// Indicates the system settings are not aware of the configuration at
25+
/// the moment.
26+
#[default]
27+
ConfigUnaware,
28+
29+
/// Indicates the system settings _are_ aware of configuration at the
30+
/// moment.
31+
ConfigAware,
32+
33+
/// Expressly disabled, such as a child process post-fork (forks are not
34+
/// currently profiled, except certain forks made by SAPIs).
35+
Disabled,
36+
}
37+
2238
#[derive(Clone, Debug)]
2339
pub struct SystemSettings {
40+
pub state: SystemSettingsState,
2441
pub profiling_enabled: bool,
2542
pub profiling_experimental_features_enabled: bool,
2643
pub profiling_endpoint_collection_enabled: bool,
@@ -41,16 +58,26 @@ pub struct SystemSettings {
4158
}
4259

4360
impl SystemSettings {
44-
pub fn disable_all(&mut self) {
45-
self.profiling_enabled = false;
46-
self.profiling_experimental_features_enabled = false;
47-
self.profiling_endpoint_collection_enabled = false;
48-
self.profiling_experimental_cpu_time_enabled = false;
49-
self.profiling_allocation_enabled = false;
50-
self.profiling_timeline_enabled = false;
51-
self.profiling_exception_enabled = false;
52-
self.profiling_exception_message_enabled = false;
53-
self.profiling_io_enabled = false;
61+
/// Provides "initial" settings, which are all "off"-like values.
62+
pub const fn initial() -> SystemSettings {
63+
SystemSettings {
64+
state: SystemSettingsState::ConfigUnaware,
65+
profiling_enabled: false,
66+
profiling_experimental_features_enabled: false,
67+
profiling_endpoint_collection_enabled: false,
68+
profiling_experimental_cpu_time_enabled: false,
69+
profiling_allocation_enabled: false,
70+
profiling_allocation_sampling_distance: u32::MAX,
71+
profiling_timeline_enabled: false,
72+
profiling_exception_enabled: false,
73+
profiling_exception_message_enabled: false,
74+
profiling_wall_time_enabled: false,
75+
profiling_io_enabled: false,
76+
output_pprof: None,
77+
profiling_exception_sampling_distance: u32::MAX,
78+
profiling_log_level: LevelFilter::Off,
79+
uri: AgentEndpoint::Socket(Cow::Borrowed(AgentEndpoint::DEFAULT_UNIX_SOCKET_PATH)),
80+
}
5481
}
5582

5683
/// # Safety
@@ -64,6 +91,7 @@ impl SystemSettings {
6491
let trace_agent_url = trace_agent_url();
6592
let uri = detect_uri_from_config(trace_agent_url, agent_host, trace_agent_port);
6693
Self {
94+
state: SystemSettingsState::ConfigAware,
6795
profiling_enabled: profiling_enabled(),
6896
profiling_experimental_features_enabled: profiling_experimental_features_enabled(),
6997
profiling_endpoint_collection_enabled: profiling_endpoint_collection_enabled(),
@@ -83,15 +111,16 @@ impl SystemSettings {
83111
}
84112
}
85113

86-
static mut SYSTEM_SETTINGS: MaybeUninit<SystemSettings> = MaybeUninit::uninit();
114+
static mut SYSTEM_SETTINGS: SystemSettings = SystemSettings::initial();
87115

88116
impl SystemSettings {
89-
/// # Safety
90-
/// Must be called after [first_rinit] and before [shutdown].
91-
pub unsafe fn get() -> ptr::NonNull<SystemSettings> {
92-
// SAFETY: required by this function's own safety requirements.
93-
let addr = unsafe { (*ptr::addr_of_mut!(SYSTEM_SETTINGS)).assume_init_mut() };
94-
ptr::NonNull::from(addr)
117+
/// Returns the "current" system settings, which are always memory-safe
118+
/// but may point to "initial" values rather than the configured ones,
119+
/// depending on what point in the lifecycle we're at.
120+
pub const fn get() -> ptr::NonNull<SystemSettings> {
121+
let addr = ptr::addr_of_mut!(SYSTEM_SETTINGS);
122+
// SAFETY: it's derived from a static variable, it's not null.
123+
unsafe { ptr::NonNull::new_unchecked(addr) }
95124
}
96125

97126
/// # Safety
@@ -110,10 +139,6 @@ impl SystemSettings {
110139
}
111140
}
112141

113-
// Initialize the lazy lock holding the env var for new origin
114-
// detection in a safe place.
115-
_ = std::sync::LazyLock::force(&libdd_common::entity_id::DD_EXTERNAL_ENV);
116-
117142
// Work around version-specific issues.
118143
#[cfg(not(php_zend_mm_set_custom_handlers_ex))]
119144
if allocation::allocation_le83::first_rinit_should_disable_due_to_jit() {
@@ -123,54 +148,48 @@ impl SystemSettings {
123148
if allocation::allocation_ge84::first_rinit_should_disable_due_to_jit() {
124149
system_settings.profiling_allocation_enabled = false;
125150
}
126-
swap(
127-
&mut system_settings,
128-
(*ptr::addr_of_mut!(SYSTEM_SETTINGS)).assume_init_mut(),
151+
152+
SystemSettings::log_state(
153+
(*ptr::addr_of!(SYSTEM_SETTINGS)).state,
154+
system_settings.state,
155+
"the first request was received",
129156
);
157+
ptr::addr_of_mut!(SYSTEM_SETTINGS).swap(&mut system_settings);
130158
}
131159

132-
/// # Safety
133-
/// Must be called exactly once each startup in either minit or startup,
134-
/// whether profiling is enabled or not.
135-
unsafe fn on_startup() {
136-
(*ptr::addr_of_mut!(SYSTEM_SETTINGS)).write(INITIAL_SYSTEM_SETTINGS.clone());
160+
fn log_state(from: SystemSettingsState, to: SystemSettingsState, reason: &str) {
161+
debug!("SystemSettings state transitioned from {from:?} to {to:?} because {reason}.");
137162
}
138163

139164
/// # Safety
140165
/// Must be called exactly once per shutdown in either mshutdown or
141166
/// shutdown, before zai config is shutdown.
142167
unsafe fn on_shutdown() {
143-
let system_settings = (*ptr::addr_of_mut!(SYSTEM_SETTINGS)).assume_init_mut();
168+
let system_settings = &mut *ptr::addr_of_mut!(SYSTEM_SETTINGS);
169+
let state = SystemSettingsState::ConfigUnaware;
170+
SystemSettings::log_state(
171+
system_settings.state,
172+
state,
173+
"a shutdown command was received",
174+
);
144175
*system_settings = SystemSettings {
145-
profiling_enabled: false,
146-
profiling_experimental_features_enabled: false,
147-
profiling_endpoint_collection_enabled: false,
148-
profiling_experimental_cpu_time_enabled: false,
149-
profiling_allocation_enabled: false,
150-
profiling_allocation_sampling_distance: 0,
151-
profiling_timeline_enabled: false,
152-
profiling_exception_enabled: false,
153-
profiling_exception_message_enabled: false,
154-
profiling_wall_time_enabled: false,
155-
profiling_io_enabled: false,
156-
output_pprof: None,
157-
profiling_exception_sampling_distance: 0,
158-
profiling_log_level: LevelFilter::Off,
159-
uri: Default::default(),
176+
state,
177+
..SystemSettings::initial()
160178
};
161179
}
162180

163181
unsafe fn on_fork_in_child() {
164-
let system_settings = (*ptr::addr_of_mut!(SYSTEM_SETTINGS)).assume_init_mut();
165-
system_settings.profiling_enabled = false;
166-
system_settings.profiling_experimental_features_enabled = false;
167-
system_settings.profiling_endpoint_collection_enabled = false;
168-
system_settings.profiling_experimental_cpu_time_enabled = false;
169-
system_settings.profiling_allocation_enabled = false;
170-
system_settings.profiling_timeline_enabled = false;
171-
system_settings.profiling_exception_enabled = false;
172-
system_settings.profiling_exception_message_enabled = false;
173-
system_settings.profiling_io_enabled = false;
182+
let system_settings = &mut *ptr::addr_of_mut!(SYSTEM_SETTINGS);
183+
let state = SystemSettingsState::Disabled;
184+
SystemSettings::log_state(
185+
system_settings.state,
186+
state,
187+
"the processed forked, and child processes are not profiled",
188+
);
189+
*system_settings = SystemSettings {
190+
state,
191+
..SystemSettings::initial()
192+
};
174193
}
175194
}
176195

@@ -448,55 +467,25 @@ impl ConfigId {
448467
}
449468
}
450469

451-
lazy_static::lazy_static! {
452-
/// In some SAPIs, full configuration is not known until the first request
453-
/// is served. This is ripe for edge cases. Consider this order of events:
454-
/// 1. Worker is created.
455-
/// 2. No requests are served.
456-
/// 3. As the worker shuts down, the timeline profiler will attempt to
457-
/// add idle time to timeline.
458-
/// What state should the configuration be in?
459-
///
460-
/// Since the real configuration was never learned, assume everything is
461-
/// disabled, which should cause fewer issues for customers than assuming
462-
/// defaults.
463-
pub static ref INITIAL_SYSTEM_SETTINGS: SystemSettings = SystemSettings {
464-
profiling_enabled: false,
465-
profiling_experimental_features_enabled: false,
466-
profiling_endpoint_collection_enabled: false,
467-
profiling_experimental_cpu_time_enabled: false,
468-
profiling_allocation_enabled: false,
469-
profiling_allocation_sampling_distance: u32::MAX,
470-
profiling_timeline_enabled: false,
471-
profiling_exception_enabled: false,
472-
profiling_exception_message_enabled: false,
473-
profiling_wall_time_enabled: false,
474-
profiling_io_enabled: false,
475-
output_pprof: None,
476-
profiling_exception_sampling_distance: u32::MAX,
477-
profiling_log_level: LevelFilter::Off,
478-
uri: Default::default(),
479-
};
480-
481-
/// Keep these in sync with the INI defaults.
482-
static ref DEFAULT_SYSTEM_SETTINGS: SystemSettings = SystemSettings {
483-
profiling_enabled: true,
484-
profiling_experimental_features_enabled: false,
485-
profiling_endpoint_collection_enabled: true,
486-
profiling_experimental_cpu_time_enabled: true,
487-
profiling_allocation_enabled: true,
488-
profiling_allocation_sampling_distance: 1024 * 4096,
489-
profiling_timeline_enabled: true,
490-
profiling_exception_enabled: true,
491-
profiling_exception_message_enabled: false,
492-
profiling_wall_time_enabled: true,
493-
profiling_io_enabled: false,
494-
output_pprof: None,
495-
profiling_exception_sampling_distance: 100,
496-
profiling_log_level: LevelFilter::Off,
497-
uri: Default::default(),
498-
};
499-
}
470+
/// Keep these in sync with the INI defaults.
471+
static DEFAULT_SYSTEM_SETTINGS: SystemSettings = SystemSettings {
472+
state: SystemSettingsState::ConfigUnaware,
473+
profiling_enabled: true,
474+
profiling_experimental_features_enabled: false,
475+
profiling_endpoint_collection_enabled: true,
476+
profiling_experimental_cpu_time_enabled: true,
477+
profiling_allocation_enabled: true,
478+
profiling_allocation_sampling_distance: 1024 * 4096,
479+
profiling_timeline_enabled: true,
480+
profiling_exception_enabled: true,
481+
profiling_exception_message_enabled: false,
482+
profiling_wall_time_enabled: true,
483+
profiling_io_enabled: false,
484+
output_pprof: None,
485+
profiling_exception_sampling_distance: 100,
486+
profiling_log_level: LevelFilter::Off,
487+
uri: AgentEndpoint::Socket(Cow::Borrowed(AgentEndpoint::DEFAULT_UNIX_SOCKET_PATH)),
488+
};
500489

501490
/// # Safety
502491
/// This function must only be called after config has been initialized in
@@ -1244,7 +1233,8 @@ pub(crate) fn minit(module_number: libc::c_int) {
12441233
);
12451234
assert!(tmp); // It's literally return true in the source.
12461235

1247-
SystemSettings::on_startup();
1236+
let system_settings = &mut *ptr::addr_of_mut!(SYSTEM_SETTINGS);
1237+
*system_settings = SystemSettings::initial();
12481238
}
12491239
}
12501240

@@ -1268,6 +1258,7 @@ pub(crate) unsafe fn on_fork_in_child() {
12681258
#[cfg(test)]
12691259
mod tests {
12701260
use super::*;
1261+
use core::mem::MaybeUninit;
12711262
use libc::memcmp;
12721263

12731264
#[test]

profiling/src/lib.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ mod exception;
2222
mod timeline;
2323
mod vec_ext;
2424

25-
use crate::config::{SystemSettings, INITIAL_SYSTEM_SETTINGS};
25+
use crate::config::SystemSettings;
2626
use crate::zend::datadog_sapi_globals_request_info;
2727
use bindings::{
2828
self as zend, ddog_php_prof_php_version, ddog_php_prof_php_version_id, ZendExtension,
@@ -323,6 +323,9 @@ extern "C" fn minit(_type: c_int, module_number: c_int) -> ZendResult {
323323
let _connector = libdd_common::connector::Connector::default();
324324
}
325325

326+
// Initialize the lazy lock holding the env var for new origin detection.
327+
_ = std::sync::LazyLock::force(&libdd_common::entity_id::DD_EXTERNAL_ENV);
328+
326329
// Use a hybrid extension hack to load as a module but have the
327330
// zend_extension hooks available:
328331
// https://www.phpinternalsbook.com/php7/extensions_design/zend_extensions.html#hybrid-extensions
@@ -423,7 +426,7 @@ pub struct RequestLocals {
423426
/// conditions such as in mshutdown when there were no requests served,
424427
/// then the settings are still memory safe, but they may not have the
425428
/// real configuration. Instead, they have a best-effort values such as
426-
/// INITIAL_SYSTEM_SETTINGS, or possibly the values which were available
429+
/// the initial settings, or possibly the values which were available
427430
/// in MINIT.
428431
pub system_settings: ptr::NonNull<SystemSettings>,
429432

@@ -434,8 +437,9 @@ pub struct RequestLocals {
434437
impl RequestLocals {
435438
#[track_caller]
436439
pub fn system_settings(&self) -> &SystemSettings {
437-
// SAFETY: it should always be valid, either set to the
438-
// INITIAL_SYSTEM_SETTINGS or to the SYSTEM_SETTINGS.
440+
// SAFETY: it should always be valid, just maybe "stale", such as
441+
// having only the initial values, or only the ones available in minit,
442+
// rather than the fully configured values.
439443
unsafe { self.system_settings.as_ref() }
440444
}
441445
}
@@ -449,7 +453,7 @@ impl Default for RequestLocals {
449453
git_commit_sha: None,
450454
git_repository_url: None,
451455
tags: vec![],
452-
system_settings: ptr::NonNull::from(INITIAL_SYSTEM_SETTINGS.deref()),
456+
system_settings: SystemSettings::get(),
453457
interrupt_count: AtomicU32::new(0),
454458
vm_interrupt_addr: ptr::null_mut(),
455459
}
@@ -572,8 +576,9 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult {
572576

573577
unsafe { bindings::zai_config_rinit() };
574578

575-
// SAFETY: We are after first rinit and before config mshutdown.
576-
let mut system_settings = unsafe { SystemSettings::get() };
579+
// Needs to come after config::first_rinit, because that's what sets the
580+
// values to the ones in the configuration.
581+
let system_settings = SystemSettings::get();
577582

578583
// initialize the thread local storage and cache some items
579584
let result = REQUEST_LOCALS.try_with_borrow_mut(|locals| {
@@ -645,8 +650,10 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult {
645650
return ZendResult::Success;
646651
}
647652

648-
// SAFETY: still safe to access in rinit after first_rinit.
649-
let system_settings = unsafe { system_settings.as_mut() };
653+
// SAFETY: safe to dereference in rinit after first_rinit. It's important
654+
// that this is a non-mut reference because in ZTS there's nothing which
655+
// enforces mutual exclusion.
656+
let system_settings = unsafe { system_settings.as_ref() };
650657

651658
// SAFETY: the once control is not mutable during request.
652659
let once = unsafe { &*ptr::addr_of!(RINIT_ONCE) };

0 commit comments

Comments
 (0)