/*
 * SPDX-FileCopyrightText: 2026 Jérémie Galarneau <jeremie.galarneau@efficios.com>
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 */

#include "agent.hpp"
#include "consumer.hpp"
#include "context-configuration.hpp"
#include "counter-event-payload.hpp"
#include "event-rule-configuration.hpp"
#include "fd-limit.hpp"
#include "health-sessiond.hpp"
#include "lttng-sessiond.hpp"
#include "map-channel-configuration.hpp"
#include "notification-thread-commands.hpp"
#include "pending-memory-reclamation-request.hpp"
#include "recording-channel-configuration.hpp"
#include "session.hpp"
#include "trace-ust.hpp"
#include "ust-app-channel.hpp"
#include "ust-app-ctx.hpp"
#include "ust-app-event.hpp"
#include "ust-app.hpp"
#include "ust-consumer.hpp"
#include "ust-counter-event-attachment.hpp"
#include "ust-domain-orchestrator.hpp"
#include "ust-key-registry.hpp"
#include "ust-map-channel.hpp"
#include "ust-registry.hpp"
#include "ust-trace-class-index.hpp"

#include <common/ctl/format.hpp>
#include <common/defaults.hpp>
#include <common/error.hpp>
#include <common/exception.hpp>
#include <common/format.hpp>
#include <common/fs-utils.hpp>
#include <common/hash-combine.hpp>
#include <common/macros.hpp>
#include <common/scope-exit.hpp>
#include <common/trace-chunk.hpp>
#include <common/urcu.hpp>

#include <lttng/event-rule/event-rule-internal.hpp>
#include <lttng/event-rule/event-rule.h>
#include <lttng/event-rule/user-tracepoint.h>
#include <lttng/ust-ctl.h>
#include <lttng/ust-error.h>

#include <cstring>
#include <functional>
#include <numeric>
#include <vector>

namespace ls = lttng::sessiond;
namespace lsc = lttng::sessiond::config;

lttng_ust_context_attr ls::ust::domain_orchestrator::_make_ust_context_attr(
	const lsc::context_configuration& context_config)
{
	struct lttng_ust_context_attr ust_ctx = {};

	switch (context_config.context_type) {
	case lsc::context_configuration::type::VTID:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_VTID;
		break;
	case lsc::context_configuration::type::VPID:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_VPID;
		break;
	case lsc::context_configuration::type::PTHREAD_ID:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_PTHREAD_ID;
		break;
	case lsc::context_configuration::type::PROCNAME:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_PROCNAME;
		break;
	case lsc::context_configuration::type::IP:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_IP;
		break;
	case lsc::context_configuration::type::PERF_THREAD_COUNTER:
	{
		const auto& perf_config =
			static_cast<const lsc::perf_counter_context_configuration&>(context_config);

		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER;
		ust_ctx.u.perf_counter.type = static_cast<uint32_t>(perf_config.perf_type);
		ust_ctx.u.perf_counter.config = perf_config.perf_config;
		strncpy(ust_ctx.u.perf_counter.name,
			perf_config.name.c_str(),
			LTTNG_UST_ABI_SYM_NAME_LEN);
		ust_ctx.u.perf_counter.name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] = '\0';
		break;
	}
	case lsc::context_configuration::type::APP_CONTEXT:
	{
		const auto& app_config =
			static_cast<const lsc::app_context_configuration&>(context_config);

		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_APP_CONTEXT;
		/*
		 * The provider_name and ctx_name pointers in the ABI struct
		 * are non-owning. The caller must ensure the
		 * context_configuration outlives the returned struct.
		 */
		/* NOLINTBEGIN(cppcoreguidelines-pro-type-const-cast): UST ABI uses char *. */
		ust_ctx.u.app_ctx.provider_name =
			const_cast<char *>(app_config.provider_name.c_str());
		ust_ctx.u.app_ctx.ctx_name = const_cast<char *>(app_config.context_name.c_str());
		/* NOLINTEND(cppcoreguidelines-pro-type-const-cast) */
		break;
	}
	case lsc::context_configuration::type::CPU_ID:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_CPU_ID;
		break;
	case lsc::context_configuration::type::CGROUP_NS:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_CGROUP_NS;
		break;
	case lsc::context_configuration::type::IPC_NS:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_IPC_NS;
		break;
	case lsc::context_configuration::type::MNT_NS:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_MNT_NS;
		break;
	case lsc::context_configuration::type::NET_NS:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_NET_NS;
		break;
	case lsc::context_configuration::type::PID_NS:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_PID_NS;
		break;
	case lsc::context_configuration::type::TIME_NS:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_TIME_NS;
		break;
	case lsc::context_configuration::type::USER_NS:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_USER_NS;
		break;
	case lsc::context_configuration::type::UTS_NS:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_UTS_NS;
		break;
	case lsc::context_configuration::type::VUID:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_VUID;
		break;
	case lsc::context_configuration::type::VEUID:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_VEUID;
		break;
	case lsc::context_configuration::type::VSUID:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_VSUID;
		break;
	case lsc::context_configuration::type::VGID:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_VGID;
		break;
	case lsc::context_configuration::type::VEGID:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_VEGID;
		break;
	case lsc::context_configuration::type::VSGID:
		ust_ctx.ctx = LTTNG_UST_ABI_CONTEXT_VSGID;
		break;
	default:
		LTTNG_THROW_INVALID_ARGUMENT_ERROR(
			fmt::format("Context type is not supported by the UST domain: type={}",
				    context_config.context_type));
	}

	return ust_ctx;
}

void ls::ust::domain_orchestrator::consumer_output_deleter::operator()(
	struct consumer_output *output) const noexcept
{
	consumer_output_put(output);
}

ls::ust::domain_orchestrator::domain_orchestrator(
	const ltt_session& session,
	lsc::recording_channel_configuration::ownership_model_t default_buffer_ownership,
	consumer_output_uptr consumer_output) :
	_session(session),
	_default_buffer_ownership(default_buffer_ownership),
	_consumer_output(std::move(consumer_output)),
	_agents(lttng_ht_new(0, LTTNG_HT_TYPE_U64)),
	_root_shm_path(session.shm_path),
	_shm_path(session.shm_path[0] != '\0' ? std::string(session.shm_path) + "/ust" :
						std::string())
{
	LTTNG_ASSERT(_consumer_output);
	LTTNG_ASSERT(_agents);

	DBG_FMT("UST domain orchestrator created: session_name=`{}`, session_id={}, "
		"buffer_ownership={}, shm_path=`{}`",
		_session.name,
		_session.id,
		_default_buffer_ownership,
		_shm_path);
}

bool ls::ust::domain_orchestrator::supports_madv_remove() const noexcept
{
	return lttng::utils::fs_supports_madv_remove(!_shm_path.empty() ? _shm_path.c_str() :
									  nullptr);
}

std::uint64_t ls::ust::domain_orchestrator::_session_id() const noexcept
{
	return _session.id;
}

lttng_buffer_type ls::ust::domain_orchestrator::buffer_type() const noexcept
{
	return _default_buffer_ownership ==
			lsc::recording_channel_configuration::ownership_model_t::PER_UID ?
		LTTNG_BUFFER_PER_UID :
		LTTNG_BUFFER_PER_PID;
}

/* NOLINTNEXTLINE(bugprone-exception-escape) */
ls::ust::domain_orchestrator::~domain_orchestrator()
{
	DBG_FMT("UST domain orchestrator destroying: session_name=`{}`, session_id={}, "
		"app_sessions_count={}, per_uid_trace_classes={}, per_pid_trace_classes={}",
		_session.name,
		_session.id,
		_app_sessions.size(),
		_per_uid_trace_classes.size(),
		_per_pid_trace_classes.size());

	/*
	 * Tear down all app sessions owned by this orchestrator. Each
	 * app session holds UST handles, consumer channel registrations,
	 * counter-event rule attachments, and per-PID stream groups/trace
	 * classes that must be released while the applications are still
	 * reachable.
	 *
	 * on_app_departure() erases from _app_sessions, so iterate
	 * until the map is drained.
	 */
	{
		const lttng::urcu::read_lock_guard read_lock;

		while (!_app_sessions.empty()) {
			auto *app = _app_sessions.begin()->first;

			if (!ust_app_get(*app)) {
				/*
				 * Application is being torn down concurrently;
				 * drop the session entry without interacting
				 * with the app.
				 */
				_app_sessions.erase(_app_sessions.begin());
				continue;
			}

			const ust_app_reference app_ref(app);

			health_code_update();

			try {
				on_app_departure(*app);
			} catch (const std::exception& ex) {
				ERR_FMT("Failed to tear down app session during orchestrator destruction: session_name=`{}`, app={}, error='{}'",
					_session.name,
					*app,
					ex.what());
			}

			health_code_update();

			try {
				app->command_socket.lock().wait_quiescent();
			} catch (const ust::app_communication_error&) {
			} catch (const lttng::runtime_error&) {
			}
		}
	}

	/*
	 * Close per-UID metadata channels on the consumer before
	 * destroying trace classes. Per-PID metadata is closed when
	 * each application disconnects; per-UID metadata outlives the
	 * applications and must be closed explicitly.
	 */
	try {
		_close_per_uid_metadata_on_consumer();
	} catch (const std::exception& ex) {
		ERR_FMT("Failed to close per-UID metadata on consumer during orchestrator destruction: session_name=`{}`, error='{}'",
			_session.name,
			ex.what());
	}

	/* Unregister all per-UID trace classes from the global index. */
	for (const auto& entry : _per_uid_trace_classes) {
		try {
			the_trace_class_index->remove_per_uid(
				_session.id,
				static_cast<std::uint32_t>(entry.first.abi),
				entry.first.uid);
		} catch (const std::exception& ex) {
			ERR_FMT("Failed to remove per-UID trace class from the global index during orchestrator destruction: session_name=`{}`, uid={}, error='{}'",
				_session.name,
				entry.first.uid,
				ex.what());
		}
	}

	/* Unregister any remaining per-PID trace classes from the global index. */
	for (const auto& entry : _per_pid_app_session_ids) {
		try {
			the_trace_class_index->remove_per_pid(entry.second);
		} catch (const std::exception& ex) {
			ERR_FMT("Failed to remove per-PID trace class from the global index during orchestrator destruction: session_name=`{}`, app_session_id={}, error='{}'",
				_session.name,
				entry.second,
				ex.what());
		}
	}

	/* Destroy all agents. */
	if (_agents) {
		try {
			const lttng::urcu::read_lock_guard read_lock;

			for (auto *agt :
			     lttng::urcu::lfht_iteration_adapter<agent,
								 decltype(agent::node),
								 &agent::node>(*_agents->ht)) {
				const int ret = cds_lfht_del(_agents->ht, &agt->node.node);

				LTTNG_ASSERT(!ret);
				agent_destroy(agt);
			}

			lttng_ht_destroy(_agents);
		} catch (const std::exception& ex) {
			ERR_FMT("Unexpected exception while destroying agents during orchestrator destruction: session_name=`{}`, error='{}'",
				_session.name,
				ex.what());
		}
	}
}

struct agent *
ls::ust::domain_orchestrator::find_agent(enum lttng_domain_type domain_type) const noexcept
{
	struct lttng_ht_node_u64 *node;
	struct lttng_ht_iter iter;
	uint64_t key = domain_type;

	lttng_ht_lookup(_agents, &key, &iter);
	node = lttng_ht_iter_get_node<lttng_ht_node_u64>(&iter);
	if (!node) {
		return nullptr;
	}

	return lttng::utils::container_of(node, &agent::node);
}

struct agent& ls::ust::domain_orchestrator::find_or_create_agent(enum lttng_domain_type domain_type)
{
	auto *agt = find_agent(domain_type);
	if (agt) {
		return *agt;
	}

	agt = agent_create(domain_type);
	if (!agt) {
		LTTNG_THROW_ALLOCATION_FAILURE_ERROR("Failed to create agent");
	}

	agent_add(agt, _agents);
	return *agt;
}

std::uint64_t ls::ust::domain_orchestrator::_trace_class_stream_class_handle(
	const config::recording_channel_configuration& channel_config) const
{
	const auto it = _channel_handles.find(&channel_config);
	LTTNG_ASSERT(it != _channel_handles.end());
	return it->second;
}

ls::ust::trace_class& ls::ust::domain_orchestrator::_find_or_create_per_uid_trace_class(
	uid_t uid,
	application_abi abi,
	const lttng::sessiond::trace::abi& tracer_abi,
	std::uint32_t tracer_major,
	std::uint32_t tracer_minor,
	const char *root_shm_path,
	const char *shm_path)
{
	LTTNG_ASSERT(_default_buffer_ownership ==
		     lsc::recording_channel_configuration::ownership_model_t::PER_UID);

	const _per_uid_trace_class_key key = { uid, abi };
	const auto it = _per_uid_trace_classes.find(key);
	if (it != _per_uid_trace_classes.end()) {
		return *it->second;
	}

	const std::shared_ptr<ust::trace_class> tc(ust_trace_class_per_uid_create(
		_session.trace_format,
		tracer_abi,
		tracer_major,
		tracer_minor,
		root_shm_path,
		shm_path,
		_session.uid,
		_session.gid,
		_session.id,
		uid,
		_session.has_auto_generated_name ? DEFAULT_SESSION_NAME : _session.name,
		_session.hostname,
		_session.creation_time));
	if (!tc) {
		LTTNG_THROW_ERROR(
			fmt::format("Failed to create per-UID trace class: uid={}, abi={}",
				    uid,
				    static_cast<int>(abi)));
	}

	auto& ref = *tc;
	_per_uid_trace_classes.emplace(key, tc);

	/* Register in the global trace class index for consumer metadata lookups. */
	the_trace_class_index->add_per_uid(_session.id, static_cast<std::uint32_t>(abi), uid, tc);

	DBG_FMT("UST domain orchestrator created per-UID trace class: uid={}, abi={}",
		uid,
		static_cast<int>(abi));

	return ref;
}

ls::ust::trace_class& ls::ust::domain_orchestrator::_find_or_create_per_pid_trace_class(
	ls::ust::app& app,
	std::uint64_t app_session_id,
	const lttng::sessiond::trace::abi& tracer_abi,
	std::uint32_t tracer_major,
	std::uint32_t tracer_minor,
	const char *root_shm_path,
	const char *shm_path,
	uid_t euid,
	gid_t egid)
{
	LTTNG_ASSERT(_default_buffer_ownership ==
		     lsc::recording_channel_configuration::ownership_model_t::PER_PID);

	const auto it = _per_pid_trace_classes.find(&app);
	if (it != _per_pid_trace_classes.end()) {
		return *it->second;
	}

	const std::shared_ptr<ust::trace_class> tc(ust_trace_class_per_pid_create(
		&app,
		_session.trace_format,
		tracer_abi,
		tracer_major,
		tracer_minor,
		root_shm_path,
		shm_path,
		euid,
		egid,
		_session.id,
		_session.has_auto_generated_name ? DEFAULT_SESSION_NAME : _session.name,
		_session.hostname,
		_session.creation_time));
	if (!tc) {
		LTTNG_THROW_ERROR(fmt::format("Failed to create per-PID trace class: app={}", app));
	}

	auto& ref = *tc;
	_per_pid_trace_classes.emplace(&app, tc);
	_per_pid_app_session_ids.emplace(&app, app_session_id);

	/* Register in the global trace class index for consumer metadata lookups. */
	the_trace_class_index->add_per_pid(app_session_id, tc);

	DBG_FMT("UST domain orchestrator created per-PID trace class: app={}", app);

	return ref;
}

void ls::ust::domain_orchestrator::_release_per_pid_trace_class(ls::ust::app& app)
{
	const auto it = _per_pid_trace_classes.find(&app);
	if (it == _per_pid_trace_classes.end()) {
		return;
	}

	/* Unregister from the global trace class index. */
	const auto id_it = _per_pid_app_session_ids.find(&app);
	if (id_it != _per_pid_app_session_ids.end()) {
		the_trace_class_index->remove_per_pid(id_it->second);
		_per_pid_app_session_ids.erase(id_it);
	}

	DBG_FMT("UST domain orchestrator releasing per-PID trace class: session_name=`{}`, app={}",
		_session.name,
		app);
	_per_pid_trace_classes.erase(it);
}

ls::ust::stream_group& ls::ust::domain_orchestrator::_find_or_create_per_uid_stream_group(
	const config::recording_channel_configuration& channel_config,
	uid_t uid,
	application_abi abi,
	std::uint64_t consumer_key,
	ust::ust_object_data channel_object,
	ust::trace_class& trace_class,
	ust::stream_class& stream_class)
{
	LTTNG_ASSERT(_default_buffer_ownership ==
		     lsc::recording_channel_configuration::ownership_model_t::PER_UID);

	const _per_uid_stream_group_key key = { &channel_config, uid, abi };
	const auto it = _per_uid_stream_groups.find(key);
	if (it != _per_uid_stream_groups.end()) {
		return *it->second;
	}

	auto sg = lttng::make_unique<ust::stream_group>(
		consumer_key, std::move(channel_object), channel_config, trace_class, stream_class);

	auto& ref = *sg;
	_per_uid_stream_groups.emplace(key, std::move(sg));

	DBG_FMT("UST domain orchestrator created per-UID stream group: "
		"channel_name=`{}`, uid={}, abi={}, consumer_key={}",
		channel_config.name,
		uid,
		static_cast<int>(abi),
		consumer_key);

	return ref;
}

ls::ust::stream_group& ls::ust::domain_orchestrator::_get_per_uid_stream_group(
	const config::recording_channel_configuration& channel_config,
	uid_t uid,
	application_abi abi)
{
	const _per_uid_stream_group_key key = { &channel_config, uid, abi };
	const auto it = _per_uid_stream_groups.find(key);
	if (it == _per_uid_stream_groups.end()) {
		LTTNG_THROW_ERROR(fmt::format(
			"Per-UID stream group not found: channel_name=`{}`, uid={}, abi={}",
			channel_config.name,
			uid,
			static_cast<int>(abi)));
	}

	return *it->second;
}

bool ls::ust::domain_orchestrator::_has_per_uid_stream_group(
	const config::recording_channel_configuration& channel_config,
	uid_t uid,
	application_abi abi) const
{
	const _per_uid_stream_group_key key = { &channel_config, uid, abi };

	return _per_uid_stream_groups.find(key) != _per_uid_stream_groups.end();
}

ls::ust::stream_group& ls::ust::domain_orchestrator::_find_or_create_per_pid_stream_group(
	const config::recording_channel_configuration& channel_config,
	ls::ust::app& app,
	std::uint64_t consumer_key,
	ust::ust_object_data channel_object,
	ust::trace_class& trace_class,
	ust::stream_class& stream_class)
{
	LTTNG_ASSERT(_default_buffer_ownership ==
		     lsc::recording_channel_configuration::ownership_model_t::PER_PID);

	const _per_pid_stream_group_key key = { &channel_config, &app };
	const auto it = _per_pid_stream_groups.find(key);
	if (it != _per_pid_stream_groups.end()) {
		return *it->second;
	}

	auto sg = lttng::make_unique<ust::stream_group>(
		consumer_key, std::move(channel_object), channel_config, trace_class, stream_class);

	auto& ref = *sg;
	_per_pid_stream_groups.emplace(key, std::move(sg));

	DBG_FMT("UST domain orchestrator created per-PID stream group: "
		"channel_name=`{}`, pid={}, consumer_key={}",
		channel_config.name,
		app.pid,
		consumer_key);

	return ref;
}

void ls::ust::domain_orchestrator::_release_per_pid_stream_groups(ls::ust::app& app)
{
	auto it = _per_pid_stream_groups.begin();
	while (it != _per_pid_stream_groups.end()) {
		if (it->first.app == &app) {
			DBG_FMT("UST domain orchestrator releasing per-PID stream group: "
				"session_name=`{}`, channel_name=`{}`, app={}",
				_session.name,
				it->second->configuration().name,
				app);
			it = _per_pid_stream_groups.erase(it);
		} else {
			++it;
		}
	}
}

void ls::ust::domain_orchestrator::create_channel(
	const config::recording_channel_configuration& channel_config)
{
	_validate_channel_attributes(channel_config);

	const auto buffer_type = _default_buffer_ownership ==
			lsc::recording_channel_configuration::ownership_model_t::PER_PID ?
		LTTNG_BUFFER_PER_PID :
		LTTNG_BUFFER_PER_UID;

	const auto handle = _next_trace_class_stream_class_handle++;
	_channel_handles[&channel_config] = handle;

	/* Lock buffer type on first channel creation. */
	if (!_locked_buffer_type) {
		_locked_buffer_type = buffer_type;
	} else if (*_locked_buffer_type != buffer_type) {
		LTTNG_THROW_UST_BUFFER_TYPE_MISMATCH();
	}

	DBG_FMT("UST domain orchestrator created channel: session_name=`{}`, session_id={}, "
		"trace_class_stream_class_handle={}, channel_config={}",
		_session.name,
		_session.id,
		handle,
		channel_config);
}

void ls::ust::domain_orchestrator::_validate_channel_attributes(
	const lsc::recording_channel_configuration& channel_config)
{
	const auto subbuf_size = channel_config.subbuffer_size_bytes;
	const auto num_subbuf = channel_config.subbuffer_count;

	/* Overwrite mode requires at least 2 subbuffers. */
	if (channel_config.buffer_full_policy ==
		    lsc::channel_configuration::buffer_full_policy_t::OVERWRITE_OLDEST_PACKET &&
	    num_subbuf < 2) {
		LTTNG_THROW_INVALID_ARGUMENT_ERROR("Overwrite mode requires at least 2 subbuffers");
	}

	/* Subbuffer size must be a nonzero power of 2. */
	if (!subbuf_size || (subbuf_size & (subbuf_size - 1))) {
		LTTNG_THROW_INVALID_ARGUMENT_ERROR("Subbuffer size must be a nonzero power of 2");
	}

	/* Subbuffer size must be at least the page size. */
	if (subbuf_size < static_cast<std::uint64_t>(the_page_size)) {
		LTTNG_THROW_INVALID_ARGUMENT_ERROR("Subbuffer size must be at least the page size");
	}

	/* Number of subbuffers must be a nonzero power of 2. */
	if (!num_subbuf || (num_subbuf & (num_subbuf - 1))) {
		LTTNG_THROW_INVALID_ARGUMENT_ERROR(
			"Number of subbuffers must be a nonzero power of 2");
	}

	/* UST only supports MMAP output. */
	if (channel_config.buffer_consumption_backend !=
	    lsc::channel_configuration::buffer_consumption_backend_t::MMAP) {
		LTTNG_THROW_UNSUPPORTED_ERROR("UST only supports MMAP output");
	}

	/* Tracefile size must be >= subbuffer size when set. */
	if (channel_config.trace_file_size_limit_bytes &&
	    *channel_config.trace_file_size_limit_bytes > 0 &&
	    *channel_config.trace_file_size_limit_bytes < subbuf_size) {
		LTTNG_THROW_INVALID_ARGUMENT_ERROR(
			"Tracefile size must be at least the subbuffer size");
	}
}

void ls::ust::domain_orchestrator::_enable_channel_on_apps(lttng::c_string_view channel_name)
{
	const lttng::urcu::read_lock_guard read_lock;

	for (const auto& app_session_pair : _app_sessions) {
		auto *app = app_session_pair.first;
		auto *ua_sess = app_session_pair.second.get();

		if (!ust_app_get(*app)) {
			continue;
		}

		const ust_app_reference app_ref(app);

		if (!app->compatible) {
			continue;
		}

		auto *ua_chan = ua_sess->find_channel(channel_name.data());
		if (ua_chan == nullptr) {
			DBG_FMT("UST domain orchestrator channel not found while enabling on app: "
				"session_name=`{}`, channel_name=`{}`, app={}",
				_session.name,
				channel_name.data(),
				*app);
			continue;
		}

		try {
			ua_chan->enable();
		} catch (const lttng::runtime_error& ex) {
			DBG_FMT("Failed to enable UST channel on app: channel_name=`{}`, app={}, error='{}'",
				channel_name.data(),
				*app,
				ex.what());
			continue;
		}
	}
}

void ls::ust::domain_orchestrator::_disable_channel_on_apps(lttng::c_string_view channel_name)
{
	const lttng::urcu::read_lock_guard read_lock;

	for (const auto& app_session_pair : _app_sessions) {
		auto *app = app_session_pair.first;
		auto *ua_sess = app_session_pair.second.get();

		if (!ust_app_get(*app)) {
			continue;
		}

		const ust_app_reference app_ref(app);

		if (!app->compatible) {
			continue;
		}

		auto *ua_chan = ua_sess->find_channel(channel_name.data());
		if (!ua_chan) {
			DBG_FMT("UST domain orchestrator channel not found while disabling channel on app, "
				"skipping: session_name=`{}`, channel_name=`{}`, app={}",
				_session.name,
				channel_name.data(),
				*app);
			continue;
		}

		if (!ua_chan->enabled) {
			DBG_FMT("UST domain orchestrator channel already disabled on app, "
				"skipping: session_name=`{}`, channel_name=`{}`, app={}",
				_session.name,
				channel_name.data(),
				*app);
			continue;
		}

		try {
			ua_chan->disable();
		} catch (const lttng::runtime_error& ex) {
			DBG_FMT("Failed to disable UST channel on app: channel_name=`{}`, app={}, error='{}'",
				channel_name.data(),
				*app,
				ex.what());
			continue;
		}
	}
}

void ls::ust::domain_orchestrator::_create_event_on_apps(
	lttng::c_string_view channel_name,
	const config::event_rule_configuration& event_rule_config)
{
	const lttng::urcu::read_lock_guard read_lock;

	for (const auto& app_session_pair : _app_sessions) {
		auto *app = app_session_pair.first;
		auto *ua_sess = app_session_pair.second.get();

		if (!ust_app_get(*app)) {
			continue;
		}

		const ust_app_reference app_ref(app);

		if (!app->compatible) {
			continue;
		}

		if (ua_sess->deleted) {
			continue;
		}

		auto *ua_chan = ua_sess->find_channel(channel_name.data());
		if (!ua_chan) {
			DBG_FMT("UST domain orchestrator channel not found while enabling event on app, "
				"skipping: session_name=`{}`, channel_name=`{}`, app={}",
				_session.name,
				channel_name.data(),
				*app);
			continue;
		}

		try {
			app_event::create(*ua_chan, event_rule_config);
		} catch (const ls::ust::app_communication_error&) {
			continue;
		} catch (const lttng::runtime_error&) {
			LTTNG_THROW_UST_EVENT_ENABLE_FAILURE("Failed to create UST event");
		}
	}
}

void ls::ust::domain_orchestrator::_enable_event_on_apps(
	lttng::c_string_view channel_name,
	const config::event_rule_configuration& event_rule_config)
{
	const lttng::urcu::read_lock_guard read_lock;

	for (const auto& app_session_pair : _app_sessions) {
		auto *app = app_session_pair.first;
		auto *ua_sess = app_session_pair.second.get();

		if (!ust_app_get(*app)) {
			continue;
		}

		const ust_app_reference app_ref(app);

		if (!app->compatible) {
			continue;
		}

		if (ua_sess->deleted) {
			continue;
		}

		auto *ua_chan = ua_sess->find_channel(channel_name.data());
		if (ua_chan == nullptr) {
			continue;
		}

		auto *ua_event = app_event::find_by_config(ua_chan->events, event_rule_config);
		if (ua_event == nullptr) {
			DBG_FMT("UST domain orchestrator event not found while enabling on app, "
				"skipping: session_name=`{}`, channel_name=`{}`, app={}",
				_session.name,
				channel_name.data(),
				*app);
			continue;
		}

		try {
			ua_event->enable();
		} catch (const ls::ust::app_communication_error& ex) {
			DBG_FMT("Failed to enable UST event on app: app={}, error='{}'",
				*app,
				ex.what());
			continue;
		} catch (const lttng::runtime_error&) {
			LTTNG_THROW_UST_EVENT_ENABLE_FAILURE("Failed to enable UST event");
		}
	}
}

void ls::ust::domain_orchestrator::_disable_event_on_apps(
	lttng::c_string_view channel_name,
	const config::event_rule_configuration& event_rule_config)
{
	const lttng::urcu::read_lock_guard read_lock;

	for (const auto& app_session_pair : _app_sessions) {
		auto *app = app_session_pair.first;
		auto *ua_sess = app_session_pair.second.get();

		if (!ust_app_get(*app)) {
			continue;
		}

		const ust_app_reference app_ref(app);

		if (!app->compatible) {
			continue;
		}

		auto *ua_chan = ua_sess->find_channel(channel_name.data());
		if (ua_chan == nullptr) {
			DBG_FMT("UST domain orchestrator channel not found while disabling event on app, "
				"skipping: session_name=`{}`, channel_name=`{}`, app={}",
				_session.name,
				channel_name.data(),
				*app);
			continue;
		}

		auto *ua_event = app_event::find_by_config(ua_chan->events, event_rule_config);
		if (ua_event == nullptr) {
			DBG_FMT("UST domain orchestrator event not found while disabling on app, "
				"skipping: session_name=`{}`, channel_name=`{}`, app={}",
				_session.name,
				channel_name.data(),
				*app);
			continue;
		}

		try {
			ua_event->disable();
		} catch (const ls::ust::app_communication_error& ex) {
			DBG_FMT("Failed to disable UST event on app: app={}, error='{}'",
				*app,
				ex.what());
			continue;
		} catch (const lttng::runtime_error&) {
			LTTNG_THROW_UST_EVENT_DISABLE_FAILURE("Failed to disable UST event");
		}
	}
}

void ls::ust::domain_orchestrator::_add_context_on_apps(
	lttng::c_string_view channel_name, const config::context_configuration& ctx_config)
{
	const lttng::urcu::read_lock_guard read_lock;

	for (const auto& app_session_pair : _app_sessions) {
		auto *app = app_session_pair.first;
		auto *ua_sess = app_session_pair.second.get();

		if (!ust_app_get(*app)) {
			continue;
		}

		const ust_app_reference app_ref(app);

		if (!app->compatible) {
			continue;
		}

		if (ua_sess->deleted) {
			continue;
		}

		auto *ua_chan = ua_sess->find_channel(channel_name.data());
		if (ua_chan == nullptr) {
			continue;
		}

		try {
			auto ust_ctx_attr = _make_ust_context_attr(ctx_config);
			ua_chan->create_context(ctx_config, &ust_ctx_attr);
		} catch (const ls::ust::app_communication_error& ex) {
			DBG_FMT("Failed to add context on app: channel_name=`{}`, app={}, error='{}'",
				channel_name.data(),
				*app,
				ex.what());
			continue;
		} catch (const lttng::runtime_error& ex) {
			WARN_FMT(
				"Failed to add context on app: channel_name=`{}`, app={}, error='{}'",
				channel_name.data(),
				*app,
				ex.what());
			continue;
		}
	}
}

void ls::ust::domain_orchestrator::enable_channel(
	const config::recording_channel_configuration& channel_config)
{
	DBG_FMT("UST domain orchestrator enabling channel: session_name=`{}`, session_id={}, "
		"session_is_active={}, channel_config={}",
		_session.name,
		_session.id,
		_active,
		channel_config);

	if (!_active) {
		/*
		 * The channel will be enabled on all applications when the
		 * session is started as part of the synchronization.
		 */
		return;
	}

	/*
	 * Enable channel for UST global domain on all applications. Ignore return
	 * value here since whatever error we got, it means that the channel was
	 * not enabled on one or many registered applications and we can not report
	 * this to the user yet. However, at this stage, the channel was
	 * successfully enabled on the session daemon side so the enable-channel
	 * command is a success.
	 */
	_enable_channel_on_apps(channel_config.name);
}

void ls::ust::domain_orchestrator::disable_channel(
	const config::recording_channel_configuration& channel_config)
{
	DBG_FMT("UST domain orchestrator disabling channel: session_name=`{}`, session_id={}, "
		"session_is_active={}, channel_config={}",
		_session.name,
		_session.id,
		_active,
		channel_config);

	if (!_active) {
		/*
		 * If the session is inactive, the tracers are not notified
		 * right away. The disabled state will be picked up on the
		 * next synchronization.
		 */
		return;
	}

	_disable_channel_on_apps(channel_config.name);
}

void ls::ust::domain_orchestrator::disable_event(
	const config::recording_channel_configuration& channel_config,
	const config::event_rule_configuration& event_rule_config)
{
	DBG_FMT("UST domain orchestrator disabling event: session_name=`{}`, session_id={}, "
		"channel_name=`{}`, session_is_active={}, event_rule_config={}",
		_session.name,
		_session.id,
		channel_config.name,
		_active,
		event_rule_config);

	if (!_active) {
		return;
	}

	_disable_event_on_apps(channel_config.name, event_rule_config);
}

void ls::ust::domain_orchestrator::add_context(
	const config::recording_channel_configuration& channel_config,
	const config::context_configuration& ctx_config)
{
	DBG_FMT("UST domain orchestrator adding context: session_name=`{}`, session_id={}, "
		"channel_name=`{}`, session_is_active={}, context_config={}",
		_session.name,
		_session.id,
		channel_config.name,
		_active,
		ctx_config);

	if (!_active) {
		/*
		 * The config layer already recorded the context in
		 * the recording_channel_configuration. The per-app sync
		 * path reads contexts from the config directly when
		 * applications register while the session is inactive.
		 */
		return;
	}

	_add_context_on_apps(channel_config.name, ctx_config);
}

void ls::ust::domain_orchestrator::enable_event(
	const config::recording_channel_configuration& channel_config,
	const config::event_rule_configuration& event_rule_config)
{
	DBG_FMT("UST domain orchestrator enabling event: session_name=`{}`, session_id={}, "
		"channel_name=`{}`, session_is_active={}, event_rule_config={}",
		_session.name,
		_session.id,
		channel_config.name,
		_active,
		event_rule_config);

	if (!_active) {
		_created_event_rules.insert(&event_rule_config);
		return;
	}

	const auto already_created = _created_event_rules.count(&event_rule_config) > 0;

	if (already_created) {
		DBG_FMT("UST domain orchestrator re-enabling existing event on apps: "
			"session_name=`{}`, channel_name=`{}`, event_rule_config={}",
			_session.name,
			channel_config.name,
			event_rule_config);
		_enable_event_on_apps(channel_config.name, event_rule_config);
	} else {
		DBG_FMT("UST domain orchestrator creating new event on apps: "
			"session_name=`{}`, channel_name=`{}`, event_rule_config={}",
			_session.name,
			channel_config.name,
			event_rule_config);
		_create_event_on_apps(channel_config.name, event_rule_config);
		_created_event_rules.insert(&event_rule_config);
	}
}

void ls::ust::domain_orchestrator::set_tracking_policy(
	config::process_attribute_type attribute_type, config::tracking_policy policy)
{
	DBG_FMT("UST domain orchestrator setting tracking policy: session_name=`{}`, session_id={}, "
		"attribute_type={}, policy={}, session_is_active={}",
		_session.name,
		_session.id,
		attribute_type,
		policy,
		_active);

	/*
	 * The config has already been updated by the command layer. Push the
	 * updated configuration to all running applications if tracing is active.
	 */
	if (_active) {
		_synchronize_all_apps();
	}
}

void ls::ust::domain_orchestrator::track_process_attribute(
	config::process_attribute_type attribute_type, std::uint64_t value)
{
	DBG_FMT("UST domain orchestrator tracking process attribute: session_name=`{}`, session_id={}, "
		"attribute_type={}, value={}, session_is_active={}",
		_session.name,
		_session.id,
		attribute_type,
		value,
		_active);

	if (_active) {
		_synchronize_all_apps();
	}
}

void ls::ust::domain_orchestrator::untrack_process_attribute(
	config::process_attribute_type attribute_type, std::uint64_t value)
{
	DBG_FMT("UST domain orchestrator untracking process attribute: session_name=`{}`, session_id={}, "
		"attribute_type={}, value={}, session_is_active={}",
		_session.name,
		_session.id,
		attribute_type,
		value,
		_active);

	if (_active) {
		_synchronize_all_apps();
	}
}

namespace {

/*
 * Wrapper that acquires shared ownership of a trace class and locks it.
 * The lock is released when the wrapper is destroyed.
 *
 * Callers that need a `const locked_ref&` (e.g. push_metadata) should
 * use the locked_ref() accessor.
 */
struct owned_locked_trace_class {
	std::shared_ptr<ls::ust::trace_class> _ownership;
	ls::ust::trace_class::locked_ref _lock;

	explicit operator bool() const noexcept
	{
		return _ownership != nullptr;
	}

	ls::ust::trace_class *operator->() const noexcept
	{
		return _ownership.get();
	}

	ls::ust::trace_class& operator*() const noexcept
	{
		return *_ownership;
	}

	ls::ust::trace_class::locked_ref& locked_ref() noexcept
	{
		return _lock;
	}

	const ls::ust::trace_class::locked_ref& locked_ref() const noexcept
	{
		return _lock;
	}

	void reset() noexcept
	{
		_lock.reset();
		_ownership.reset();
	}
};

owned_locked_trace_class get_locked_trace_class(const ls::ust::app_session::identifier& identifier)
{
	auto session = ls::ust::app_session::get_trace_class(identifier);
	ls::ust::trace_class::locked_ref lock;

	if (session) {
		pthread_mutex_lock(&session->_lock);
		lock = ls::ust::trace_class::locked_ref{ session.get() };
	}

	return { std::move(session), std::move(lock) };
}

} /* anonymous namespace */

ls::ust::app_session& ls::ust::domain_orchestrator::_find_or_create_app_session(ust::app& app)
{
	ASSERT_LOCKED(_session._lock);

	health_code_update();
	const auto update_health_code_on_exit =
		lttng::make_scope_exit([]() noexcept { health_code_update(); });

	{
		const auto it = _app_sessions.find(&app);
		if (it != _app_sessions.end()) {
			return *it->second;
		}
	}

	DBG_FMT("UST domain orchestrator creating app session: session_name=`{}`, session_id={}, "
		"app={}, buffer_type={}",
		_session.name,
		_session_id(),
		app,
		buffer_type());

	/*
	 * Build the trace output path and shared-memory path for this
	 * app session. Per-PID traces use "pid/<name>-<pid>-<datetime>";
	 * per-UID traces use "uid/<uid>/<bits>-bit".
	 */
	struct tm timeinfo_buf = {};
	const auto *timeinfo = localtime_r(&app.registration_time, &timeinfo_buf);
	char datetime[16];
	strftime(datetime, sizeof(datetime), "%Y%m%d-%H%M%S", timeinfo);

	lttng_credentials real_creds = {};
	LTTNG_OPTIONAL_SET(&real_creds.uid, app.uid);
	LTTNG_OPTIONAL_SET(&real_creds.gid, app.gid);

	lttng_credentials effective_creds = {};
	LTTNG_OPTIONAL_SET(&effective_creds.uid, _session.uid);
	LTTNG_OPTIONAL_SET(&effective_creds.gid, _session.gid);

	const auto buf_type = buffer_type();

	auto trace_path = [&]() -> std::string {
		switch (buf_type) {
		case LTTNG_BUFFER_PER_PID:
			return fmt::format("pid/{}-{}-{}", app.name, app.pid, datetime);
		case LTTNG_BUFFER_PER_UID:
			return fmt::format("uid/{}/{}-bit",
					   lttng_credentials_get_uid(&real_creds),
					   app.abi_desc.bits_per_long);
		default:
			abort();
		}
	}();

	auto session_shm_path = shm_path();
	if (!session_shm_path.empty()) {
		switch (buf_type) {
		case LTTNG_BUFFER_PER_PID:
			session_shm_path +=
				fmt::format("/pid/{}-{}-{}", app.name, app.pid, datetime);
			break;
		case LTTNG_BUFFER_PER_UID:
			session_shm_path +=
				fmt::format("/uid/{}/{}-bit", app.uid, app.abi_desc.bits_per_long);
			break;
		default:
			abort();
		}
	}

	/*
	 * Acquire a consumer output reference for the new session.
	 * ~app_session() releases it on all error/teardown paths.
	 */
	auto *const consumer_output = get_consumer_output_ptr();
	consumer_output_get(consumer_output);

	/*
	 * Release the temporary reference only if app_session construction
	 * fails before ownership of the reference is transferred.
	 */
	auto put_consumer_output_on_constructor_error = lttng::make_scope_exit(
		[consumer_output]() noexcept { consumer_output_put(consumer_output); });

	auto new_session = lttng::make_unique<ust::app_session>(app,
								_session_id(),
								ust::app_session::next_id(),
								real_creds,
								effective_creds,
								buf_type,
								app.abi_desc.bits_per_long,
								std::move(trace_path),
								_root_shm_path,
								std::move(session_shm_path),
								consumer_output);

	/* app_session now owns the acquired consumer_output reference. */
	put_consumer_output_on_constructor_error.disarm();

	switch (buffer_type()) {
	case LTTNG_BUFFER_PER_PID:
	{
		_find_or_create_per_pid_trace_class(
			app,
			new_session->app_session_id,
			app.abi_desc,
			app.version.major,
			app.version.minor,
			new_session->root_shm_path.c_str(),
			new_session->shm_path.c_str(),
			lttng_credentials_get_uid(&new_session->effective_credentials),
			lttng_credentials_get_gid(&new_session->effective_credentials));
		break;
	}
	case LTTNG_BUFFER_PER_UID:
	{
		const auto app_abi = app.abi_desc.bits_per_long == 32 ? application_abi::ABI_32 :
									application_abi::ABI_64;

		_find_or_create_per_uid_trace_class(app.uid,
						    app_abi,
						    app.abi_desc,
						    app.version.major,
						    app.version.minor,
						    new_session->root_shm_path.c_str(),
						    new_session->shm_path.c_str());
		break;
	}
	default:
		abort();
	}

	health_code_update();

	if (new_session->handle == -1) {
		try {
			new_session->handle = app.command_socket.lock().create_session();
		} catch (const ls::ust::app_communication_error&) {
			LTTNG_THROW_COMMUNICATION_ERROR("Failed to create UST app session");
		} catch (const lttng::runtime_error&) {
			LTTNG_THROW_ERROR("Failed to create UST app session");
		}

		/* Register the session's objd in the app's registry. */
		new_session->objd_token = app.objd_registry.register_session_objd(
			new_session->handle, new_session->get_identifier());

		DBG_FMT("UST domain orchestrator created app session: session_name=`{}`, "
			"session_id={}, app={}, handle={}",
			_session.name,
			_session_id(),
			app,
			new_session->handle);
	}

	auto *ua_sess = new_session.get();
	_app_sessions[&app] = std::move(new_session);
	return *ua_sess;
}

ls::ust::app_channel& ls::ust::domain_orchestrator::_allocate_app_channel(
	ust::app_session& ua_sess,
	const lsc::recording_channel_configuration& channel_config,
	std::uint64_t trace_class_stream_class_handle)
{
	ASSERT_RCU_READ_LOCKED();

	/* Lookup channel in the ust app session */
	{
		const auto it = ua_sess.channels.find(channel_config.name);
		if (it != ua_sess.channels.end()) {
			return *it->second;
		}
	}

	auto *ua_chan = new app_channel(ua_sess, channel_config);
	ua_chan->trace_class_stream_class_handle = trace_class_stream_class_handle;
	return *ua_chan;
}

void ls::ust::domain_orchestrator::_create_channel_per_uid(ust::app *app,
							   ust::app_session *ua_sess,
							   ust::app_channel *ua_chan)
{
	LTTNG_ASSERT(app);
	LTTNG_ASSERT(ua_sess);
	LTTNG_ASSERT(ua_chan);
	ASSERT_RCU_READ_LOCKED();

	auto *consumer = get_consumer_output_ptr();
	/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) */
	auto& session = const_cast<ltt_session&>(_session);

	DBG_FMT("UST domain orchestrator creating channel with per-UID buffers: "
		"session_name=`{}`, session_id={}, channel_name=`{}`, app={}",
		_session.name,
		_session_id(),
		ua_chan->channel_config.name,
		*app);

	const auto& recording_config =
		static_cast<const lsc::recording_channel_configuration&>(ua_chan->channel_config);
	const auto app_abi = app->abi_desc.bits_per_long == 32 ? ust::application_abi::ABI_32 :
								 ust::application_abi::ABI_64;

	/*
	 * Check if the per-UID stream group already exists for this
	 * channel. If so, the channel has already been created on the
	 * consumer and we only need to send it to this application.
	 */
	if (!_has_per_uid_stream_group(recording_config, app->uid, app_abi)) {
		/*
		 * Look up the per-UID trace class. It must exist: it was created
		 * during app-session setup (find_or_create_ust_app_session).
		 */
		auto trace_class_ptr = the_trace_class_index->find_per_uid(
			_session_id(), static_cast<std::uint32_t>(app_abi), app->uid);
		LTTNG_ASSERT(trace_class_ptr);

		/* Register the stream class (CTF channel) in the trace class. */
		try {
			trace_class_ptr->add_channel(ua_chan->trace_class_stream_class_handle,
						     recording_config.buffer_allocation_policy);
		} catch (const std::exception& ex) {
			LTTNG_THROW_ERROR(fmt::format(
				"Failed to add UST channel to per-UID trace class: channel_name=`{}`, "
				"session_id={}, uid={}, abi={}, error={}",
				ua_chan->channel_config.name,
				_session_id(),
				app->uid,
				static_cast<unsigned int>(app_abi),
				ex.what()));
		}

		/*
		 * Create the buffers on the consumer side. This call populates the
		 * ust app channel object with all streams and data object.
		 */
		if (do_consumer_create_channel(consumer,
					       ua_sess,
					       ua_chan,
					       app->abi_desc.bits_per_long,
					       trace_class_ptr.get(),
					       session.current_trace_chunk,
					       session.trace_format,
					       _session.output_traces,
					       _session.live_timer) < 0) {
			auto locked_trace_class = trace_class_ptr->lock();
			try {
				locked_trace_class->remove_channel(
					ua_chan->trace_class_stream_class_handle, false);
			} catch (const std::exception& ex) {
				DBG("Could not find channel for removal: %s", ex.what());
			}
			LTTNG_THROW_ERROR(fmt::format(
				"Failed to create UST channel on consumer: channel_name=`{}`, "
				"session_id={}, uid={}, abi={}, consumer_output_type={}",
				ua_chan->channel_config.name,
				_session_id(),
				app->uid,
				static_cast<unsigned int>(app_abi),
				static_cast<int>(_consumer_output->type)));
		}

		/* Set the consumer key on the stream class. */
		{
			auto locked_trace_class = trace_class_ptr->lock();
			auto& trace_class_channel = locked_trace_class->channel(
				ua_chan->trace_class_stream_class_handle);

			trace_class_channel._consumer_key = ua_chan->key;
		}

		/*
		 * Transfer channel and stream objects directly to the
		 * orchestrator's stream group.
		 */
		{
			auto locked_trace_class = trace_class_ptr->lock();
			auto& stream_class_ref = locked_trace_class->channel(
				ua_chan->trace_class_stream_class_handle);

			auto& stream_group =
				_find_or_create_per_uid_stream_group(recording_config,
								     app->uid,
								     app_abi,
								     ua_chan->key,
								     std::move(ua_chan->obj),
								     *trace_class_ptr,
								     stream_class_ref);

			unsigned int cpu_idx = 0;
			for (auto& stream_ptr : ua_chan->streams) {
				stream_group.add_stream(cpu_idx, stream_ptr->release_obj());
				cpu_idx++;
			}
		}

		/* Notify the notification subsystem of the channel's creation. */
		const auto notification_ret = notification_thread_command_add_channel(
			the_notification_thread_handle,
			_session.id,
			ua_chan->channel_config.name.c_str(),
			ua_chan->key,
			LTTNG_DOMAIN_UST,
			ua_chan->channel_config.subbuffer_size_bytes *
				ua_chan->channel_config.subbuffer_count);
		if (notification_ret != LTTNG_OK) {
			LTTNG_THROW_ERROR(fmt::format(
				"Failed to notify UST channel creation: channel_name=`{}`, "
				"session_id={}, uid={}, abi={}, channel_key={}, notification_ret={}",
				ua_chan->channel_config.name,
				_session_id(),
				app->uid,
				static_cast<unsigned int>(app_abi),
				ua_chan->key,
				static_cast<int>(notification_ret)));
		}
	}

	/* Send buffers to the application. */
	{
		auto& sg = _get_per_uid_stream_group(recording_config, app->uid, app_abi);

		ua_chan->send_to_app_per_uid(sg);
	}
}

void ls::ust::domain_orchestrator::_create_channel_per_pid(ust::app *app,
							   ust::app_session& ua_sess,
							   ust::app_channel *ua_chan)
{
	bool trace_class_channel_added = false;

	LTTNG_ASSERT(app);
	LTTNG_ASSERT(ua_chan);

	auto *consumer = get_consumer_output_ptr();
	/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) */
	auto& session = const_cast<ltt_session&>(_session);

	DBG_FMT("UST domain orchestrator creating channel with per-PID buffers: "
		"session_name=`{}`, session_id={}, channel_name=`{}`, app={}",
		_session.name,
		_session_id(),
		ua_chan->channel_config.name,
		*app);

	const lttng::urcu::read_lock_guard read_lock;

	auto trace_class = ust::app_session::get_trace_class(ua_sess.get_identifier());
	/* The UST app session lock is held, trace class shall not be null. */
	LTTNG_ASSERT(trace_class);

	ASSERT_LOCKED(session._lock);

	/* Create and add a new stream class to the trace class. */
	try {
		const auto& per_pid_recording_config =
			static_cast<const lsc::recording_channel_configuration&>(
				ua_chan->channel_config);

		trace_class->add_channel(ua_chan->key,
					 per_pid_recording_config.buffer_allocation_policy);
		trace_class_channel_added = true;
	} catch (const std::exception& ex) {
		LTTNG_THROW_ERROR(fmt::format(
			"Failed to create per-PID UST stream class: channel_name=`{}`, "
			"session_name=`{}`, session_id={}, app={}, channel_key={}, error={}",
			ua_chan->channel_config.name,
			_session.name,
			_session_id(),
			*app,
			ua_chan->key,
			ex.what()));
	}

	/* Create and get channel on the consumer side. */
	try {
		if (do_consumer_create_channel(consumer,
					       &ua_sess,
					       ua_chan,
					       app->abi_desc.bits_per_long,
					       trace_class.get(),
					       session.current_trace_chunk,
					       session.trace_format,
					       _session.output_traces,
					       _session.live_timer) < 0) {
			LTTNG_THROW_ERROR(fmt::format(
				"Failed to create per-PID UST channel on consumer: channel_name=`{}`, "
				"session_name=`{}`, session_id={}, app={}",
				ua_chan->channel_config.name,
				_session.name,
				_session_id(),
				*app));
		}

		ua_chan->send_to_app_per_pid();

		const auto trace_class_channel_key = ua_chan->key;
		{
			auto locked_trace_class = trace_class->lock();

			auto& trace_class_channel =
				locked_trace_class->channel(trace_class_channel_key);
			trace_class_channel._consumer_key = ua_chan->key;
		}

		/*
		 * Populate the orchestrator's per-PID stream group map.
		 * During the dual-write transition, the per-app channel
		 * retains the authoritative channel and stream objects. The
		 * stream group's channel object is null during this period;
		 * ownership will be transferred when the per-app channel
		 * objects are managed by the orchestrator.
		 */
		{
			const auto& recording_config =
				static_cast<const lsc::recording_channel_configuration&>(
					ua_chan->channel_config);
			auto& trace_class_ref = *trace_class;
			auto locked_trace_class = trace_class_ref.lock();
			auto& stream_class_ref =
				locked_trace_class->channel(trace_class_channel_key);

			_find_or_create_per_pid_stream_group(recording_config,
							     *app,
							     ua_chan->key,
							     ust::ust_object_data(nullptr),
							     trace_class_ref,
							     stream_class_ref);
		}

		const auto cmd_ret = notification_thread_command_add_channel(
			the_notification_thread_handle,
			_session.id,
			ua_chan->channel_config.name.c_str(),
			ua_chan->key,
			LTTNG_DOMAIN_UST,
			ua_chan->channel_config.subbuffer_size_bytes *
				ua_chan->channel_config.subbuffer_count);
		if (cmd_ret != LTTNG_OK) {
			LTTNG_THROW_ERROR(fmt::format(
				"Failed to notify per-PID UST channel creation: channel_name=`{}`, "
				"session_name=`{}`, session_id={}, app={}, channel_key={}, notification_ret={}",
				ua_chan->channel_config.name,
				_session.name,
				_session_id(),
				*app,
				ua_chan->key,
				static_cast<int>(cmd_ret)));
		}
	} catch (...) {
		if (trace_class_channel_added) {
			try {
				auto locked_trace_class = trace_class->lock();
				locked_trace_class->remove_channel(ua_chan->key, false);
			} catch (const std::exception& ex) {
				DBG("Could not find channel for removal: %s", ex.what());
			}
		}

		throw;
	}
}

void ls::ust::domain_orchestrator::_send_app_channel(ust::app *app,
						     ust::app_session& ua_sess,
						     ust::app_channel *ua_chan)
{
	LTTNG_ASSERT(app);
	LTTNG_ASSERT(ua_chan);
	ASSERT_RCU_READ_LOCKED();

	/* Handle buffer type before sending the channel to the application. */
	switch (buffer_type()) {
	case LTTNG_BUFFER_PER_UID:
		_create_channel_per_uid(app, &ua_sess, ua_chan);
		break;
	case LTTNG_BUFFER_PER_PID:
		_create_channel_per_pid(app, ua_sess, ua_chan);
		break;
	default:
		abort();
	}

	/* Register the channel's objd in the app's registry. */
	{
		const auto trace_class_channel_key = (buffer_type() == LTTNG_BUFFER_PER_UID) ?
			ua_chan->trace_class_stream_class_handle :
			ua_chan->key;

		ua_chan->objd_token = app->objd_registry.register_channel_objd(
			ua_chan->handle, ua_sess.get_identifier(), trace_class_channel_key);
	}

	/* If channel is not enabled, disable it on the tracer */
	if (!ua_chan->enabled) {
		ua_chan->disable();
	}
}

ls::ust::app_channel& ls::ust::domain_orchestrator::_create_app_channel(
	ust::app_session& ua_sess,
	ust::app *app,
	const lsc::recording_channel_configuration& channel_config,
	std::uint64_t trace_class_stream_class_handle)
{
	auto& ua_chan =
		_allocate_app_channel(ua_sess, channel_config, trace_class_stream_class_handle);
	bool channel_published = false;

	try {
		/*
		 * Create channel onto application and synchronize its
		 * configuration.
		 */
		_send_app_channel(app, ua_sess, &ua_chan);

		/* Only publish the channel if successfully created on the tracer/consumer. */
		ua_sess.channels.emplace(ua_chan.channel_config.name,
					 std::unique_ptr<app_channel>(&ua_chan));
		channel_published = true;

		/* Add contexts. */
		for (const auto& ctx_uptr : channel_config.get_contexts()) {
			const auto& ctx_config = *ctx_uptr;

			if (is_context_redundant(channel_config, ctx_config)) {
				continue;
			}

			auto ust_ctx_attr = _make_ust_context_attr(ctx_config);
			ua_chan.create_context(ctx_config, &ust_ctx_attr);
		}
	} catch (...) {
		/*
		 * Remove from trace class before destroying. Pass false to
		 * avoid notifying the consumer on this error path.
		 */
		if (ua_sess.buffer_type == LTTNG_BUFFER_PER_PID) {
			const auto trace_class =
				ust::app_session::get_trace_class(ua_sess.get_identifier());

			if (trace_class) {
				const auto locked_trace_class = trace_class->lock();

				try {
					locked_trace_class->remove_channel(ua_chan.key, false);
				} catch (const std::exception& ex) {
					DBG("Could not find channel for removal: %s", ex.what());
				}
			}
		}

		if (channel_published) {
			ua_sess.channels.erase(ua_chan.channel_config.name);
		} else {
			delete &ua_chan;
		}

		throw;
	}

	return ua_chan;
}

ls::ust::app_channel& ls::ust::domain_orchestrator::_find_or_create_app_channel(
	ust::app_session& ua_sess,
	ust::app *app,
	const lsc::recording_channel_configuration& channel_config,
	std::uint64_t trace_class_stream_class_handle)
{
	{
		const auto it = ua_sess.channels.find(channel_config.name);
		if (it != ua_sess.channels.end()) {
			return *it->second;
		}
	}

	return _create_app_channel(ua_sess, app, channel_config, trace_class_stream_class_handle);
}

void ls::ust::domain_orchestrator::_synchronize_channel_event(
	ust::app_channel *ua_chan, const lsc::event_rule_configuration& event_config)
{
	auto *ua_event = app_event::find_by_config(ua_chan->events, event_config);
	if (!ua_event) {
		app_event::create(*ua_chan, event_config);
	} else {
		if (ua_event->enabled != event_config.is_enabled) {
			if (event_config.is_enabled) {
				ua_event->enable();
			} else {
				ua_event->disable();
			}
		}
	}
}

void ls::ust::domain_orchestrator::_synchronize_all_channels(ust::app_session& ua_sess,
							     ust::app *app)
{
	LTTNG_ASSERT(app);
	ASSERT_RCU_READ_LOCKED();

	const auto& config_domain = _session.user_space_domain;

	for (const auto& chan_config : config_domain.recording_channels()) {
		const auto handle = _trace_class_stream_class_handle(chan_config);

		/*
		 * Search for a matching app_channel. If none is found,
		 * create it. Creating the channel will cause the ua_chan
		 * structure to be allocated, the channel buffers to be
		 * allocated (if necessary) and sent to the application, and
		 * all enabled contexts will be added to the channel.
		 */
		ust::app_channel *ua_chan = nullptr;

		try {
			ua_chan = &_find_or_create_app_channel(ua_sess, app, chan_config, handle);
		} catch (const lttng::runtime_error&) {
			/* Tracer is probably gone or ENOMEM. */
			return;
		}

		if (!ua_chan) {
			/* ua_chan will be NULL for the metadata channel */
			continue;
		}

		for (const auto& event_rule_entry : chan_config.event_rules) {
			try {
				_synchronize_channel_event(ua_chan, *event_rule_entry.second);
			} catch (const lttng::runtime_error&) {
				return;
			}
		}

		if (ua_chan->enabled != chan_config.is_enabled) {
			try {
				if (chan_config.is_enabled) {
					ua_chan->enable();
				} else {
					ua_chan->disable();
				}
			} catch (const lttng::runtime_error&) {
				return;
			}
		}
	}
}

/*
 * Create UST metadata and open it on the tracer side.
 *
 * Called with UST app session lock held and RCU read side lock.
 */
void ls::ust::domain_orchestrator::_create_app_metadata(ust::app_session& ua_sess, ust::app *app)
{
	ust::app_channel *metadata;
	struct consumer_socket *socket;

	LTTNG_ASSERT(app);
	LTTNG_ASSERT(get_consumer_output_ptr());
	ASSERT_RCU_READ_LOCKED();

	auto locked_trace_class = get_locked_trace_class(ua_sess.get_identifier());
	/* The UST app session is held, trace class shall not be null. */
	LTTNG_ASSERT(locked_trace_class);

	ASSERT_LOCKED(_session._lock);

	const auto& metadata_config =
		_session.get_domain(lttng::domain_class::USER_SPACE).metadata_channel();

	/* Metadata already exists for this trace class or it was closed previously */
	if (locked_trace_class->_metadata_key || locked_trace_class->_metadata_closed) {
		return;
	}

	/* Allocate UST metadata */
	metadata = new app_channel(ua_sess, metadata_config);
	const auto delete_metadata_on_exit =
		lttng::make_scope_exit([metadata]() noexcept { delete metadata; });

	/* Need one fd for the channel. */
	if (lttng_fd_get(LTTNG_FD_APPS, 1) < 0) {
		LTTNG_THROW_ERROR(fmt::format(
			"Failed to allocate metadata file descriptor: session_name=`{}`, "
			"session_id={}, app={}",
			_session.name,
			_session_id(),
			*app));
	}
	const auto put_fd_on_exit =
		lttng::make_scope_exit([]() noexcept { lttng_fd_put(LTTNG_FD_APPS, 1); });

	/* Get the right consumer socket for the application. */
	socket = consumer_find_socket_by_bitness(app->abi_desc.bits_per_long,
						 get_consumer_output_ptr());
	if (!socket) {
		LTTNG_THROW_INVALID_ARGUMENT_ERROR("Failed to find consumer socket for metadata");
	}

	/*
	 * Keep metadata key so we can identify it on the consumer side. Assign it
	 * to the trace class *before* we ask the consumer so we avoid the race of the
	 * consumer requesting the metadata and the ask_channel call on our side
	 * did not returned yet.
	 */
	locked_trace_class->_metadata_key = metadata->key;

	/*
	 * Ask the metadata channel creation to the consumer. The metadata object
	 * will be created by the consumer and kept their. However, the stream is
	 * never added or monitored until we do a first push metadata to the
	 * consumer.
	 */
	if (ust_consumer_ask_channel(&ua_sess,
				     metadata,
				     get_consumer_output_ptr(),
				     socket,
				     locked_trace_class.locked_ref().get(),
				     _session.current_trace_chunk,
				     _session.trace_format,
				     _session.output_traces,
				     _session.live_timer) < 0) {
		/* Nullify the metadata key so we don't try to close it later on. */
		locked_trace_class->_metadata_key = 0;
		LTTNG_THROW_ERROR("Failed to create metadata channel on consumer");
	}

	/*
	 * The setup command will make the metadata stream be sent to the relayd,
	 * if applicable, and the thread managing the metadatas. This is important
	 * because after this point, if an error occurs, the only way the stream
	 * can be deleted is to be monitored in the consumer.
	 */
	if (consumer_setup_metadata(socket, metadata->key) < 0) {
		/* Nullify the metadata key so we don't try to close it later on. */
		locked_trace_class->_metadata_key = 0;
		LTTNG_THROW_ERROR("Failed to setup metadata channel on consumer");
	}

	DBG_FMT("UST domain orchestrator created metadata: session_name=`{}`, session_id={}, "
		"metadata_key={}, app={}",
		_session.name,
		_session_id(),
		metadata->key,
		*app);
}

void ls::ust::domain_orchestrator::_synchronize_app_map_channels(ust::app_session& ua_sess,
								 ust::app& app)
{
	for (auto& entry : _map_channels) {
		auto& channel = *entry.second;

		if (ua_sess.map_channel_attachments.count(&channel) != 0) {
			/* Already attached, skip. */
			continue;
		}

		try {
			auto attachment = channel.attach_to_app(app, ua_sess.handle);
			if (!attachment) {
				/*
				 * Not served to this app (e.g. value type doesn't
				 * fit its ABI): no master counter to host rules.
				 */
				continue;
			}

			ua_sess.map_channel_attachments.emplace(&channel, std::move(*attachment));
		} catch (const ust::app_communication_error& ex) {
			DBG_FMT("Application unreachable while attaching map channel: app={}, map_name=`{}`, error=`{}`",
				app,
				channel.configuration().name,
				ex.what());
			continue;
		} catch (const std::exception& ex) {
			ERR_FMT("Failed to attach map channel to application: app={}, map_name=`{}`, error=`{}`",
				app,
				channel.configuration().name,
				ex.what());
			continue;
		}

		/*
		 * The channel is now attached to this app: install every rule
		 * already registered against it, so an app that synchronizes
		 * after the rules were registered still hosts them.
		 *
		 * The rules are keyed by user token, hence installed in
		 * registration order: the tracer resolves each rule's key in
		 * the order the rules reach it, so when the channel runs out of
		 * key slots the denied key is deterministically that of the
		 * most recently registered rule.
		 */
		for (const auto& rule_entry : channel._rules) {
			const auto user_token = rule_entry.first;
			const auto *const rule_event_rule = rule_entry.second.key.first;
			const auto *const rule_action = rule_entry.second.key.second;

			try {
				_install_rule_on_app(app,
						     ua_sess,
						     channel,
						     *rule_event_rule,
						     *rule_action,
						     user_token);
			} catch (const ust::app_communication_error& ex) {
				DBG_FMT("Application unreachable while installing existing map rule on new attachment: "
					"app={}, map_name=`{}`, error=`{}`",
					app,
					channel.configuration().name,
					ex.what());
			} catch (const std::exception& ex) {
				ERR_FMT("Failed to install existing map rule on new app attachment: "
					"app={}, map_name=`{}`, error=`{}`",
					app,
					channel.configuration().name,
					ex.what());
			}
		}
	}
}

void ls::ust::domain_orchestrator::_install_rule_on_app(ust::app& app,
							ust::app_session& ua_sess,
							const ust::map_channel& channel,
							const lttng_event_rule& event_rule,
							const lttng_action& incr_map_value_action,
							std::uint64_t user_token)
{
	DBG_FMT("Installing map channel rule on app: map_name=`{}`, user_token={}",
		channel.configuration().name,
		user_token);

	/*
	 * The channel must already be attached to this app: the rule installs
	 * against the app's copy of the channel's master counter.
	 */
	const auto attachment_it = ua_sess.map_channel_attachments.find(&channel);
	LTTNG_ASSERT(attachment_it != ua_sess.map_channel_attachments.end());

	auto payload = sessiond::map::details::serialize_for_ust(
		event_rule, incr_map_value_action, user_token);

	lttng_ust_abi_object_data *event_handle = nullptr;
	{
		auto guard = app.command_socket.lock();
		guard.counter_create_event(
			reinterpret_cast<lttng_ust_abi_counter_event *>(payload.data()),
			payload.size(),
			attachment_it->second.master_object_data(),
			&event_handle);
	}

	/*
	 * Take ownership of the returned handle now so it is released if a
	 * later step throws.
	 */
	auto attachment = ust::counter_event_attachment(app, event_handle);

	/*
	 * The counter-event payload does not carry the filter, so apply it to
	 * the new handle separately, the way the event-notifier path does.
	 */
	const auto *const filter_bytecode = lttng_event_rule_get_filter_bytecode(&event_rule);
	if (filter_bytecode && set_ust_object_filter(&app, filter_bytecode, event_handle) < 0) {
		LTTNG_THROW_ERROR(
			fmt::format("Failed to set filter on map counter-event rule: app={}", app));
	}

	/*
	 * The counter-event enabler is created disabled on the tracer side, like
	 * every event enabler. Enable it now, after the filter is attached, so
	 * the tracer instantiates the event-counter for each matching probe and
	 * requests a key index from the session daemon; a disabled enabler never
	 * reaches that path and the map records nothing.
	 */
	app.command_socket.lock().enable(event_handle);

	const counter_event_attachment_key key{ &channel, &event_rule, &incr_map_value_action };
	const auto insertion =
		ua_sess.counter_event_attachments.emplace(key, std::move(attachment));
	LTTNG_ASSERT(insertion.second);
}

void ls::ust::domain_orchestrator::_uninstall_rule_from_app(
	ust::app_session& ua_sess,
	const ust::map_channel& channel,
	const lttng_event_rule& event_rule,
	const lttng_action& incr_map_value_action) noexcept
{
	const counter_event_attachment_key key{ &channel, &event_rule, &incr_map_value_action };

	/* RAII releases the handle; a missing entry is a normal no-op. */
	ua_sess.counter_event_attachments.erase(key);
}

void ls::ust::domain_orchestrator::synchronize_app(ust::app& app)
{
	namespace lsc = lttng::sessiond::config;

	DBG_FMT("UST domain orchestrator synchronizing app: session_name=`{}`, session_id={}, "
		"app={}, session_is_active={}",
		_session.name,
		_session.id,
		app,
		_active);

	if (!app.compatible) {
		DBG_FMT("UST domain orchestrator skipping incompatible app: session_name=`{}`, app={}",
			_session.name,
			app);
		return;
	}

	const auto& domain = _session.user_space_domain;

	if (domain.virtual_process_id_tracker().is_tracked(app.pid) &&
	    domain.virtual_user_id_tracker().is_tracked(
		    lsc::resolved_process_attr_value<uid_t>(app.uid)) &&
	    domain.virtual_group_id_tracker().is_tracked(
		    lsc::resolved_process_attr_value<gid_t>(app.gid))) {
		ust::app_session *ua_sess = nullptr;

		try {
			ua_sess = &_find_or_create_app_session(app);
		} catch (const lttng::runtime_error&) {
			DBG_FMT("UST domain orchestrator failed to find or create app session: "
				"session_name=`{}`, app={}",
				_session.name,
				app);
			/* Tracer is probably gone or ENOMEM. */
			return;
		}

		LTTNG_ASSERT(ua_sess);

		if (!ua_sess->deleted) {
			const lttng::urcu::read_lock_guard read_lock;
			_synchronize_all_channels(*ua_sess, &app);

			/*
			 * Create the metadata for the application. This returns
			 * gracefully if a metadata was already set for the session.
			 *
			 * The metadata channel must be created after the data
			 * channels as the consumer daemon assumes this ordering.
			 * When interacting with a relay daemon, the consumer will
			 * use this assumption to send the "STREAMS_SENT" message
			 * to the relay daemon.
			 *
			 * A session that only contains map channels has no ring
			 * buffer to describe, and no consumer daemon to describe it
			 * to: it is not spawned for such a session. The metadata is
			 * created on the first synchronization that follows the
			 * creation of a recording channel, which does spawn one.
			 */
			if (_session.user_space_domain.recording_channel_count() > 0) {
				try {
					_create_app_metadata(*ua_sess, &app);
				} catch (const lttng::runtime_error&) {
					ERR("Metadata creation failed for app sock %d for session id %" PRIu64,
					    app.command_socket.fd(),
					    _session_id());
				}
			}

			_synchronize_app_map_channels(*ua_sess, app);
		}

		if (_active) {
			try {
				_start_app_trace(&app);
			} catch (const lttng::runtime_error&) {
				DBG_FMT("UST domain orchestrator failed to start app trace during "
					"synchronization: session_name=`{}`, app={}",
					_session.name,
					app);
				/* The app may have departed; synchronize on next interaction. */
			}
		}
	} else {
		DBG_FMT("UST domain orchestrator app is not tracked, tearing down app session: "
			"session_name=`{}`, app={}",
			_session.name,
			app);
		if (_find_app_session(app)) {
			on_app_departure(app);
		}
	}
}

unsigned int ls::ust::domain_orchestrator::on_app_departure(
	ust::app& app, const nonstd::optional<uint32_t>& owner_id_to_reclaim)
{
	DBG_FMT("UST domain orchestrator handling app departure: session_name=`{}`, session_id={}, "
		"app={}, owner_id_to_reclaim={}",
		_session.name,
		_session.id,
		app,
		owner_id_to_reclaim ? std::to_string(*owner_id_to_reclaim) : "none");

	const auto it = _app_sessions.find(&app);
	if (it == _app_sessions.end()) {
		DBG_FMT("UST domain orchestrator has no app session for departing app: "
			"session_name=`{}`, app={}",
			_session.name,
			app);
		return 0;
	}

	/*
	 * Destroy the app_session. For per-PID buffers, flush all
	 * application streams before teardown to ensure proper
	 * data_pending behavior.
	 *
	 * The unique_ptr destruction runs the app_session's RAII token
	 * destructor, which deregisters the session objd from
	 * app.objd_registry. Channel tokens are destroyed when their
	 * channels are destroyed during ~app_session().
	 */
	auto owned_session = std::move(it->second);
	_app_sessions.erase(it);

	if (owned_session->buffer_type == LTTNG_BUFFER_PER_PID) {
		/*
		 * A destroyed session's trace chunk is closed: a flush would
		 * produce packets that have no output.
		 */
		if (!_session.destroyed) {
			_flush_app_session(*owned_session);
		}

		_save_per_pid_stats_on_departure(*owned_session);
	}

	/*
	 * Drain any per-PID map groups this app owned, applying each
	 * channel's dead_group_policy through ust::map_channel::
	 * remove_app_group. Per-UID map channels share groups across
	 * apps and remain untouched here; the channel-side method skips
	 * channels that aren't per-PID-configured. Read the sessiond-
	 * side counter mmap while the group is still in the channel —
	 * remove_app_group drains, then erases.
	 *
	 * Map-channel buffer ownership is decided independently of the
	 * session's recording-channel buffer_type, so the loop runs for
	 * every app departure regardless of `owned_session->buffer_type`.
	 *
	 * Per-channel failures are logged and skipped so a single
	 * channel can't block the rest of departure.
	 */
	for (auto& entry : _map_channels) {
		auto& channel = *entry.second;

		if (channel.configuration().buffer_ownership != lsc::ownership_model_t::PER_PID) {
			continue;
		}

		try {
			channel.remove_app_group(app);
		} catch (const std::exception& ex) {
			ERR_FMT("Failed to drain per-PID map group on app departure: "
				"map_name=`{}`, app={}, error=`{}`",
				channel.configuration().name,
				app,
				ex.what());
		}
	}

	/*
	 * When reclaiming an owner ID, tell the consumer daemon before
	 * destroying the app session: the consumer needs the channels
	 * to still exist when processing the reclamation command.
	 *
	 * A session that only contains map channels owns no consumer-side
	 * buffer, and no consumer daemon is spawned for it: it has nothing to
	 * reclaim and nobody to ask.
	 */
	unsigned int pending_reclamations = 0;
	if (owner_id_to_reclaim && _session.user_space_domain.recording_channel_count() > 0) {
		pending_reclamations =
			consumer_reclaim_session_owner_id(*owned_session, *owner_id_to_reclaim);
	}

	/* Destructor handles channel/context/stream cleanup and consumer output release. */
	owned_session.reset();

	if (buffer_type() == LTTNG_BUFFER_PER_PID) {
		_release_per_pid_stream_groups(app);
		_release_per_pid_trace_class(app);
	}

	return pending_reclamations;
}

void ls::ust::domain_orchestrator::_synchronize_all_apps()
{
	DBG_FMT("UST domain orchestrator synchronizing all apps: session_name=`{}`, session_id={}",
		_session.name,
		_session.id);

	for (auto *app :
	     lttng::urcu::lfht_iteration_adapter<ust::app,
						 decltype(ust::app::sock_n),
						 &ust::app::sock_n>(*ust_app_ht_by_sock->ht)) {
		if (!ust_app_get(*app)) {
			DBG_FMT("UST domain orchestrator skipping app being torn down during "
				"synchronization: session_name=`{}`",
				_session.name);
			continue;
		}

		const ust_app_reference app_ref(app);

		synchronize_app(*app);
	}
}

void ls::ust::domain_orchestrator::_start_app_trace(ust::app *app)
{
	ust::app_session *ua_sess;

	ASSERT_LOCKED(_session._lock);

	DBG_FMT("UST domain orchestrator starting app trace: session_name=`{}`, session_id={}, app={}",
		_session.name,
		_session.id,
		*app);

	const lttng::urcu::read_lock_guard read_lock;
	const auto update_health_code_on_exit =
		lttng::make_scope_exit([]() noexcept { health_code_update(); });

	if (!app->compatible) {
		return;
	}

	ua_sess = _find_app_session(*app);
	if (ua_sess == nullptr) {
		/* The session is in teardown process. Ignore and continue. */
		return;
	}

	if (ua_sess->deleted) {
		return;
	}

	if (ua_sess->enabled) {
		return;
	}

	/* This starts the UST tracing */
	try {
		app->command_socket.lock().start_session(ua_sess->handle);
	} catch (const ls::ust::app_communication_error&) {
		DBG_FMT("UST domain orchestrator failed to start app trace (communication error): "
			"session_name=`{}`, app={}",
			_session.name,
			*app);
		return;
	} catch (const lttng::runtime_error&) {
		LTTNG_THROW_ERROR(
			lttng::format("Failed to start UST app trace: session_name=`{}`, app={}",
				      _session.name,
				      *app));
	}

	/* Indicate that the session has been started once */
	ua_sess->started = true;
	ua_sess->enabled = true;

	health_code_update();

	/* Quiescent wait after starting trace */
	try {
		app->command_socket.lock().wait_quiescent();
	} catch (const ls::ust::app_communication_error&) {
	} catch (const lttng::runtime_error&) {
	}
}

void ls::ust::domain_orchestrator::_stop_app_trace(ust::app *app)
{
	ust::app_session *ua_sess;

	ASSERT_LOCKED(_session._lock);

	DBG_FMT("UST domain orchestrator stopping app trace: session_name=`{}`, session_id={}, app={}",
		_session.name,
		_session.id,
		*app);

	const lttng::urcu::read_lock_guard read_lock;
	const auto update_health_code_on_exit =
		lttng::make_scope_exit([]() noexcept { health_code_update(); });

	if (!app->compatible) {
		return;
	}

	ua_sess = _find_app_session(*app);
	if (ua_sess == nullptr) {
		return;
	}

	if (ua_sess->deleted) {
		return;
	}

	/*
	 * If started = 0, it means that stop trace has been called for a session
	 * that was never started. It's possible since we can have a fail start
	 * from either the application manager thread or the command thread. Simply
	 * indicate that this is a stop error.
	 */
	if (!ua_sess->started) {
		LTTNG_THROW_ERROR(lttng::format(
			"Failed to stop UST app trace (never started): session_name=`{}`, app={}",
			_session.name,
			*app));
	}

	health_code_update();

	/* This inhibits UST tracing */
	try {
		app->command_socket.lock().stop_session(ua_sess->handle);
	} catch (const ls::ust::app_communication_error&) {
		DBG_FMT("UST domain orchestrator failed to stop app trace (communication error): "
			"session_name=`{}`, app={}",
			_session.name,
			*app);
		return;
	} catch (const lttng::runtime_error&) {
		LTTNG_THROW_ERROR(
			lttng::format("Failed to stop UST app trace: session_name=`{}`, app={}",
				      _session.name,
				      *app));
	}

	health_code_update();
	ua_sess->enabled = false;

	/* Quiescent wait after stopping trace */
	try {
		app->command_socket.lock().wait_quiescent();
	} catch (const ls::ust::app_communication_error&) {
	} catch (const lttng::runtime_error&) {
	}

	health_code_update();

	{
		auto trace_class = ust::app_session::get_trace_class(ua_sess->get_identifier());
		LTTNG_ASSERT(trace_class);
		auto locked_trace_class = trace_class->lock();
		if (!locked_trace_class->_metadata_closed) {
			const auto socket = consumer_find_socket_by_bitness(
				locked_trace_class->abi.bits_per_long, ua_sess->consumer);
			if (socket) {
				(void) ust_app_push_metadata(locked_trace_class, socket, 0);
			}
		}
	}
}

void ls::ust::domain_orchestrator::_flush_app_session(ust::app_session& ua_sess)
{
	ua_sess.flush();
}

void ls::ust::domain_orchestrator::_clear_quiescent_app_session(ust::app *app,
								ust::app_session *ua_sess)
{
	struct consumer_socket *socket;

	DBG_FMT("UST domain orchestrator clearing quiescent state: session_name=`{}`, session_id={}, app={}",
		_session.name,
		_session.id,
		*app);

	const lttng::urcu::read_lock_guard read_lock;
	const auto update_health_code_on_exit =
		lttng::make_scope_exit([]() noexcept { health_code_update(); });

	if (!app->compatible) {
		return;
	}

	if (ua_sess->deleted) {
		return;
	}

	health_code_update();

	socket = consumer_find_socket_by_bitness(app->abi_desc.bits_per_long, ua_sess->consumer);
	if (!socket) {
		ERR("Failed to find consumer (%" PRIu32 ") socket", app->abi_desc.bits_per_long);
		return;
	}

	/* Clear quiescent state. */
	switch (ua_sess->buffer_type) {
	case LTTNG_BUFFER_PER_PID:
		for (auto& chan_pair : ua_sess->channels) {
			health_code_update();
			if (consumer_clear_quiescent_channel(socket, chan_pair.second->key)) {
				ERR("Error clearing quiescent state for consumer channel");
				continue;
			}
		}
		break;
	case LTTNG_BUFFER_PER_UID:
	default:
		abort();
		break;
	}
}

void ls::ust::domain_orchestrator::start()
{
	DBG_FMT("UST domain orchestrator starting: session_name=`{}`, session_id={}, "
		"buffer_type={}, app_sessions_count={}",
		_session.name,
		_session.id,
		buffer_type(),
		_app_sessions.size());

	if (_active) {
		DBG_FMT("UST domain orchestrator already active, skipping start: "
			"session_name=`{}`, session_id={}",
			_session.name,
			_session.id);
		return;
	}

	/*
	 * Set _active before iterating applications so that newly
	 * registering apps (via dispatch thread) see the session as
	 * active and are started by default.
	 */
	_active = true;

	/*
	 * In a start-stop-start use-case, clear the quiescent state of
	 * each channel so that a following stop or destroy grabs a
	 * timestamp_end near those operations, even if the packet is
	 * empty.
	 *
	 * Per-UID channels are handled by the orchestrator directly;
	 * per-PID channels are handled via the per-app iteration.
	 */
	if (buffer_type() == LTTNG_BUFFER_PER_UID) {
		_clear_quiescent_per_uid_channels();
	}

	if (buffer_type() == LTTNG_BUFFER_PER_PID) {
		const lttng::urcu::read_lock_guard read_lock;

		for (const auto& app_session_pair : _app_sessions) {
			auto *app = app_session_pair.first;

			if (!ust_app_get(*app)) {
				continue;
			}

			const ust_app_reference app_ref(app);
			_clear_quiescent_app_session(app, app_session_pair.second.get());
		}
	}

	_synchronize_all_apps();
}

void ls::ust::domain_orchestrator::stop()
{
	DBG_FMT("UST domain orchestrator stopping: session_name=`{}`, session_id={}, "
		"buffer_type={}, app_sessions_count={}",
		_session.name,
		_session.id,
		buffer_type(),
		_app_sessions.size());

	if (!_active) {
		DBG_FMT("UST domain orchestrator already inactive, skipping stop: "
			"session_name=`{}`, session_id={}",
			_session.name,
			_session.id);
		return;
	}

	/*
	 * Set _active before iterating applications so that newly
	 * registering apps (via dispatch thread) see the session as
	 * inactive and are not started.
	 */
	_active = false;

	/*
	 * Stop every running application. _app_sessions is complete at
	 * this point: start() synchronized all pre-existing apps, and
	 * the dispatch thread added any apps that registered since then.
	 */
	{
		const lttng::urcu::read_lock_guard read_lock;

		for (const auto& app_session_pair : _app_sessions) {
			auto *app = app_session_pair.first;

			if (!ust_app_get(*app)) {
				continue;
			}

			const ust_app_reference app_ref(app);

			try {
				_stop_app_trace(app);
			} catch (const lttng::runtime_error&) {
				/* The app may have departed; continue with remaining apps. */
			}
		}
	}

	if (buffer_type() == LTTNG_BUFFER_PER_PID) {
		const lttng::urcu::read_lock_guard read_lock;

		for (const auto& app_session_pair : _app_sessions) {
			_flush_app_session(*app_session_pair.second);
		}
	} else if (buffer_type() == LTTNG_BUFFER_PER_UID) {
		_flush_per_uid_buffers();
	} else {
		std::abort();
	}
}

void ls::ust::domain_orchestrator::_push_metadata(
	const ls::ust::trace_class::locked_ref& locked_trace_class) const
{
	if (locked_trace_class->_metadata_closed) {
		return;
	}

	const auto socket = consumer_find_socket_by_bitness(locked_trace_class->abi.bits_per_long,
							    get_consumer_output_ptr());
	if (!socket) {
		return;
	}

	(void) ust_app_push_metadata(locked_trace_class, socket, 0);
}

void ls::ust::domain_orchestrator::_flush_per_uid_buffers() const
{
	auto *consumer = get_consumer_output_ptr();

	_for_each_consumer_stream_group([this,
					 consumer](const _consumer_stream_group_descriptor& desc) {
		const lttng::urcu::read_lock_guard read_lock;

		const auto socket =
			consumer_find_socket_by_bitness(static_cast<int>(desc.abi), consumer);
		if (!socket) {
			return;
		}

		if (desc.is_metadata) {
			_push_metadata(desc.trace_class.lock());
		} else {
			(void) consumer_flush_channel(socket, desc.consumer_key);
		}
	});
}

void ls::ust::domain_orchestrator::_clear_quiescent_per_uid_channels() const
{
	_for_each_consumer_stream_group([this](const _consumer_stream_group_descriptor& desc) {
		if (desc.is_metadata) {
			return;
		}

		const lttng::urcu::read_lock_guard read_lock;

		const auto socket = consumer_find_socket_by_bitness(static_cast<int>(desc.abi),
								    get_consumer_output_ptr());
		if (!socket) {
			return;
		}

		(void) consumer_clear_quiescent_channel(socket, desc.consumer_key);
	});
}

void ls::ust::domain_orchestrator::rotate()
{
	DBG_FMT("UST domain orchestrator rotating: session_name=`{}`, session_id={}, "
		"buffer_type={}",
		_session.name,
		_session.id,
		buffer_type());

	auto *consumer = get_consumer_output_ptr();

	_for_each_consumer_stream_group([this,
					 consumer](const _consumer_stream_group_descriptor& desc) {
		const lttng::urcu::read_lock_guard read_lock;

		const auto socket =
			consumer_find_socket_by_bitness(static_cast<int>(desc.abi), consumer);
		if (!socket) {
			LTTNG_THROW_ROTATION_FAILURE(fmt::format(
				"Failed to rotate UST stream group: no consumer socket: session_name=`{}`, session_id={}, "
				"consumer_key={}, abi={}, is_metadata={}",
				_session.name,
				_session.id,
				desc.consumer_key,
				static_cast<unsigned int>(desc.abi),
				desc.is_metadata));
		}

		if (desc.is_metadata) {
			_push_metadata(desc.trace_class.lock());
		}

		const auto rotate_ret = consumer_rotate_channel(
			socket, desc.consumer_key, consumer, desc.is_metadata);
		if (rotate_ret < 0) {
			LTTNG_THROW_ROTATION_FAILURE(fmt::format(
				"Failed to rotate UST stream group on consumer: session_name=`{}`, "
				"session_id={}, consumer_key={}, abi={}, is_metadata={}, status={}",
				_session.name,
				_session.id,
				desc.consumer_key,
				static_cast<unsigned int>(desc.abi),
				desc.is_metadata,
				rotate_ret));
		}
	});
}

void ls::ust::domain_orchestrator::clear()
{
	DBG_FMT("UST domain orchestrator clearing: session_name=`{}`, session_id={}, "
		"buffer_type={}, session_is_active={}",
		_session.name,
		_session.id,
		buffer_type(),
		_active);

	if (_active) {
		LTTNG_THROW_CLEAR_FAILURE(fmt::format(
			"Failed to clear UST session while active: session_name=`{}`, session_id={}",
			_session.name,
			_session.id));
	}

	/*
	 * Reset the counters of every map channel
	 * (from lttng_clear_session()).
	 *
	 * Done before the recording buffer clearing below so that it
	 * always runs.
	 */
	for (auto& entry : _map_channels) {
		entry.second->clear();
	}

	auto *consumer = get_consumer_output_ptr();
	const auto buf_type = buffer_type();

	_for_each_consumer_stream_group([this, consumer, buf_type](
						const _consumer_stream_group_descriptor& desc) {
		/* Protect looked-up consumer socket. */
		const lttng::urcu::read_lock_guard read_lock;

		const auto consumer_socket =
			consumer_find_socket_by_bitness(static_cast<int>(desc.abi), consumer);
		if (!consumer_socket) {
			LTTNG_THROW_CLEAR_FAILURE(fmt::format(
				"Failed to clear UST stream group: no consumer socket: "
				"session_name=`{}`, session_id={}, consumer_key={}, abi={}, "
				"is_metadata={}, buffer_type={}",
				_session.name,
				_session.id,
				desc.consumer_key,
				static_cast<unsigned int>(desc.abi),
				desc.is_metadata,
				static_cast<int>(buf_type)));
		}

		if (desc.is_metadata) {
			_push_metadata(desc.trace_class.lock());
		}

		const auto clean_ret = consumer_clear_channel(consumer_socket, desc.consumer_key);
		if (clean_ret < 0) {
			if (clean_ret == -LTTCOMM_CONSUMERD_CHAN_NOT_FOUND &&
			    buf_type == LTTNG_BUFFER_PER_PID) {
				return;
			}

			if (clean_ret == -LTTCOMM_CONSUMERD_RELAYD_CLEAR_DISALLOWED) {
				LTTNG_THROW_CLEAR_RELAY_DISALLOWED(fmt::format(
					"Failed to clear UST stream group: relayd clear disallowed: "
					"session_name=`{}`, session_id={}, consumer_key={}, abi={}, "
					"is_metadata={}, status={}",
					_session.name,
					_session.id,
					desc.consumer_key,
					static_cast<unsigned int>(desc.abi),
					desc.is_metadata,
					clean_ret));
			}

			LTTNG_THROW_CLEAR_FAILURE(fmt::format(
				"Failed to clear UST stream group on consumer: session_name=`{}`, "
				"session_id={}, consumer_key={}, abi={}, is_metadata={}, status={}",
				_session.name,
				_session.id,
				desc.consumer_key,
				static_cast<unsigned int>(desc.abi),
				desc.is_metadata,
				clean_ret));
		}
	});
}

/*
 * Open data channel packets for all consumer stream groups.
 *
 * Metadata channels are skipped: the begin/end timestamps of a metadata
 * packet are useless. Moreover, opening a packet after a "clear" would
 * introduce padding that was not part of the first trace chunk. The
 * relay daemon expects the content of the metadata stream of successive
 * metadata trace chunks to be strict supersets of one another.
 */
void ls::ust::domain_orchestrator::open_packets()
{
	DBG_FMT("UST domain orchestrator opening packets: session_name=`{}`, session_id={}, "
		"buffer_type={}",
		_session.name,
		_session.id,
		buffer_type());

	auto *consumer = get_consumer_output_ptr();
	const auto buf_type = buffer_type();

	_for_each_consumer_stream_group([this, consumer, buf_type](
						const _consumer_stream_group_descriptor& desc) {
		if (desc.is_metadata) {
			return;
		}

		/* Protect looked-up consumer socket. */
		const lttng::urcu::read_lock_guard read_lock;

		const auto socket =
			consumer_find_socket_by_bitness(static_cast<int>(desc.abi), consumer);
		if (!socket) {
			LTTNG_THROW_OPEN_PACKETS_FAILURE(fmt::format(
				"Failed to open packets for UST stream group: no consumer socket: "
				"session_name=`{}`, session_id={}, consumer_key={}, abi={}, "
				"buffer_type={}",
				_session.name,
				_session.id,
				desc.consumer_key,
				static_cast<unsigned int>(desc.abi),
				static_cast<int>(buf_type)));
		}

		const auto open_ret = consumer_open_channel_packets(socket, desc.consumer_key);
		if (open_ret < 0) {
			/* Per-PID buffer and application going away. */
			if (open_ret == -LTTCOMM_CONSUMERD_CHAN_NOT_FOUND &&
			    buf_type == LTTNG_BUFFER_PER_PID) {
				return;
			}

			LTTNG_THROW_OPEN_PACKETS_FAILURE(fmt::format(
				"Failed to open packets for UST stream group on consumer: "
				"session_name=`{}`, session_id={}, consumer_key={}, abi={}, "
				"status={}",
				_session.name,
				_session.id,
				desc.consumer_key,
				static_cast<unsigned int>(desc.abi),
				open_ret));
		}
	});
}

void ls::ust::domain_orchestrator::_close_per_uid_metadata_on_consumer() const
{
	const lttng::urcu::read_lock_guard read_lock;

	for (const auto& tc_entry : _per_uid_trace_classes) {
		const auto& abi = tc_entry.first.abi;
		const auto& trace_class = *tc_entry.second;

		if (!trace_class._metadata_key) {
			continue;
		}

		auto *socket = consumer_find_socket_by_bitness(static_cast<int>(abi),
							       _consumer_output.get());
		if (!socket) {
			continue;
		}

		(void) consumer_close_metadata(socket, trace_class._metadata_key);
	}
}

void ls::ust::domain_orchestrator::record_snapshot(const struct consumer_output& snapshot_consumer,
						   std::uint64_t nb_packets_per_stream)
{
	DBG_FMT("UST domain orchestrator recording snapshot: session_name=`{}`, session_id={}, "
		"buffer_ownership={}, nb_packets_per_stream={}",
		_session.name,
		_session.id,
		_default_buffer_ownership,
		nb_packets_per_stream);

	if (_default_buffer_ownership ==
	    lsc::recording_channel_configuration::ownership_model_t::PER_UID) {
		_record_snapshot_per_uid(snapshot_consumer, nb_packets_per_stream);
	} else {
		_record_snapshot_per_pid(snapshot_consumer, nb_packets_per_stream);
	}
}

void ls::ust::domain_orchestrator::_record_snapshot_per_uid(
	const struct consumer_output& snapshot_consumer, std::uint64_t nb_packets_per_stream) const
{
	for (const auto& tc_entry : _per_uid_trace_classes) {
		const auto& tc_key = tc_entry.first;
		auto& trace_class = *tc_entry.second;

		if (!trace_class._metadata_key) {
			/* Skip since no metadata is present. */
			continue;
		}

		const lttng::urcu::read_lock_guard read_lock;
		auto *socket = consumer_find_socket_by_bitness(static_cast<int>(tc_key.abi),
							       _consumer_output.get());
		if (!socket) {
			LTTNG_THROW_INVALID_ARGUMENT_ERROR(
				"Failed to find consumer socket for snapshot");
		}

		const auto uid_path = fmt::format(
			"uid/{}/{}-bit", tc_key.uid, static_cast<unsigned int>(tc_key.abi));

		std::size_t consumer_path_offset = 0;
		const auto trace_path_raw = setup_channel_trace_path(
			_consumer_output.get(), uid_path.c_str(), &consumer_path_offset);
		if (!trace_path_raw) {
			LTTNG_THROW_INVALID_ARGUMENT_ERROR(
				"Failed to setup channel trace path for snapshot");
		}

		const auto free_trace_path = lttng::make_scope_exit(
			[trace_path_raw]() noexcept { free(trace_path_raw); });
		const auto *trace_path = &trace_path_raw[consumer_path_offset];

		/* Snapshot data channels. */
		for (const auto& sg_entry : _per_uid_stream_groups) {
			if (sg_entry.first.uid != tc_key.uid || sg_entry.first.abi != tc_key.abi) {
				continue;
			}

			const auto status =
				consumer_snapshot_channel(socket,
							  sg_entry.second->consumer_key(),
							  &snapshot_consumer,
							  0,
							  trace_path,
							  nb_packets_per_stream);
			if (status != LTTNG_OK) {
				LTTNG_THROW_SNAPSHOT_FAILURE("Failed to snapshot UST data channel");
			}
		}

		/* Snapshot metadata channel. */
		const auto status = consumer_snapshot_channel(
			socket, trace_class._metadata_key, &snapshot_consumer, 1, trace_path, 0);
		if (status != LTTNG_OK) {
			LTTNG_THROW_SNAPSHOT_FAILURE("Failed to snapshot UST metadata channel");
		}
	}
}

void ls::ust::domain_orchestrator::_record_snapshot_per_pid(
	const struct consumer_output& snapshot_consumer, std::uint64_t nb_packets_per_stream) const
{
	for (const auto& tc_entry : _per_pid_trace_classes) {
		const auto *app = tc_entry.first;
		auto& trace_class = *tc_entry.second;

		if (!trace_class._metadata_key) {
			/* Skip since no metadata is present. */
			continue;
		}

		/*
		 * Skip entries whose trace class has been marked as
		 * closed. This happens when the application departs:
		 * ust_app_unregister() sets _metadata_closed and closes
		 * the channels on the consumer before the orchestrator
		 * maps are cleaned up.
		 */
		{
			const auto locked_tc = trace_class.lock();

			if (locked_tc->_metadata_closed) {
				continue;
			}
		}

		const lttng::urcu::read_lock_guard read_lock;
		auto *socket = consumer_find_socket_by_bitness(
			static_cast<int>(app->abi_desc.bits_per_long), _consumer_output.get());
		if (!socket) {
			LTTNG_THROW_INVALID_ARGUMENT_ERROR(
				"Failed to find consumer socket for snapshot");
		}

		/* Build per-PID trace path: pid/<name>-<pid>-<datetime>. */
		struct tm timeinfo_buf = {};
		const auto *timeinfo = localtime_r(&app->registration_time, &timeinfo_buf);
		char datetime[16];
		strftime(datetime, sizeof(datetime), "%Y%m%d-%H%M%S", timeinfo);

		const auto pid_path = fmt::format("pid/{}-{}-{}", app->name, app->pid, datetime);

		std::size_t consumer_path_offset = 0;
		const auto trace_path_raw = setup_channel_trace_path(
			_consumer_output.get(), pid_path.c_str(), &consumer_path_offset);
		if (!trace_path_raw) {
			LTTNG_THROW_INVALID_ARGUMENT_ERROR(
				"Failed to setup channel trace path for snapshot");
		}

		const auto free_trace_path = lttng::make_scope_exit(
			[trace_path_raw]() noexcept { free(trace_path_raw); });
		const auto *trace_path = &trace_path_raw[consumer_path_offset];

		/* Snapshot data channels. */
		for (const auto& sg_entry : _per_pid_stream_groups) {
			if (sg_entry.first.app != app) {
				continue;
			}

			const auto status =
				consumer_snapshot_channel(socket,
							  sg_entry.second->consumer_key(),
							  &snapshot_consumer,
							  0,
							  trace_path,
							  nb_packets_per_stream);
			switch (status) {
			case LTTNG_OK:
				break;
			case LTTNG_ERR_CHAN_NOT_FOUND:
				continue;
			default:
				LTTNG_THROW_SNAPSHOT_FAILURE(
					"Failed to snapshot UST per-PID data channel");
			}
		}

		/* Snapshot metadata channel. */
		const auto status = consumer_snapshot_channel(
			socket, trace_class._metadata_key, &snapshot_consumer, 1, trace_path, 0);
		switch (status) {
		case LTTNG_OK:
			break;
		case LTTNG_ERR_CHAN_NOT_FOUND:
			continue;
		default:
			LTTNG_THROW_SNAPSHOT_FAILURE(
				"Failed to snapshot UST per-PID metadata channel");
		}
	}
}

void ls::ust::domain_orchestrator::regenerate_metadata()
{
	DBG_FMT("UST domain orchestrator regenerating metadata: session_name=`{}`, session_id={}, "
		"buffer_type={}",
		_session.name,
		_session.id,
		buffer_type());

	if (buffer_type() == LTTNG_BUFFER_PER_PID) {
		LTTNG_THROW_UST_METADATA_REGENERATION_UNSUPPORTED();
	}

	for (const auto& tc_entry : _per_uid_trace_classes) {
		tc_entry.second->regenerate_metadata();
	}
}

void ls::ust::domain_orchestrator::regenerate_statedump()
{
	DBG_FMT("UST domain orchestrator regenerating statedump: session_name=`{}`, session_id={}, "
		"app_sessions_count={}",
		_session.name,
		_session.id,
		_app_sessions.size());

	const lttng::urcu::read_lock_guard read_lock;

	for (const auto& app_session_pair : _app_sessions) {
		auto *app = app_session_pair.first;
		auto *ua_sess = app_session_pair.second.get();

		if (!ust_app_get(*app)) {
			continue;
		}

		const ust_app_reference app_ref(app);

		if (!app->compatible) {
			continue;
		}

		if (ua_sess->deleted) {
			continue;
		}

		try {
			app->command_socket.lock().regenerate_statedump(ua_sess->handle);
		} catch (const ls::ust::app_communication_error&) {
		} catch (const lttng::runtime_error&) {
		}
	}
}

void ls::ust::domain_orchestrator::create_channel_subdirectories(
	lttng_trace_chunk& trace_chunk) const
{
	DBG_FMT("UST domain orchestrator creating channel subdirectories: session_name=`{}`, "
		"session_id={}, buffer_ownership={}",
		_session.name,
		_session.id,
		_default_buffer_ownership);

	if (_default_buffer_ownership ==
	    lsc::recording_channel_configuration::ownership_model_t::PER_UID) {
		for (const auto& tc_entry : _per_uid_trace_classes) {
			const auto uid = tc_entry.first.uid;
			const auto bits_per_long = static_cast<unsigned int>(tc_entry.first.abi);

			const auto pathname = fmt::format(DEFAULT_UST_TRACE_DIR
							  "/uid/{}/{}-bit/" DEFAULT_INDEX_DIR,
							  uid,
							  bits_per_long);

			const auto chunk_status = lttng_trace_chunk_create_subdirectory(
				&trace_chunk, pathname.c_str());
			if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
				LTTNG_THROW_UST_DIRECTORY_CREATION_FAILURE(
					"Failed to create UST channel subdirectory");
			}
		}
	} else {
		/*
		 * Create the toplevel ust/ directory in case no apps are running.
		 */
		auto chunk_status =
			lttng_trace_chunk_create_subdirectory(&trace_chunk, DEFAULT_UST_TRACE_DIR);
		if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
			LTTNG_THROW_UST_DIRECTORY_CREATION_FAILURE(
				"Failed to create UST trace directory");
		}

		/*
		 * Iterate the orchestrator's per-PID trace class map so that
		 * subdirectories are created for exactly the same set of
		 * applications that for_each_consumer_stream_group() will visit
		 * during rotation. Both maps are only mutated under the session
		 * lock, so there is no TOCTOU race between subdirectory creation
		 * and the subsequent rotation.
		 *
		 * Any app present in the map is guaranteed to still have its
		 * ua_sess accessible: the orchestrator entry is removed (under
		 * session lock) before the app session is torn down.
		 */
		for (const auto& tc_entry : _per_pid_trace_classes) {
			const auto *ua_sess = _find_app_session(*tc_entry.first);
			if (!ua_sess) {
				continue;
			}

			const auto pathname = fmt::format(
				DEFAULT_UST_TRACE_DIR "/{}/" DEFAULT_INDEX_DIR, ua_sess->path);

			chunk_status = lttng_trace_chunk_create_subdirectory(&trace_chunk,
									     pathname.c_str());
			if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) {
				LTTNG_THROW_UST_DIRECTORY_CREATION_FAILURE(
					"Failed to create UST channel subdirectory");
			}
		}
	}
}

namespace {
/*
 * Issue a memory reclamation request for the specified consumer channel keys
 * (effectively stream groups).
 *
 * The results are matched back to the stream group owners provided to
 * populate the result vector providing proper stream group ownership
 * information along with the reclaimed memory sizes (completed and pending).
 */
void issue_consumer_reclaim_channel_memory(
	consumer_socket& consumer_socket,
	const std::vector<ls::commands::stream_group_owner>& stream_group_owners,
	bool is_per_cpu_stream,
	const std::vector<std::uint64_t>& target_consumer_channel_keys,
	const nonstd::optional<std::chrono::microseconds>& reclaim_older_than_age,
	bool only_reclaim_consumed_data,
	std::uint64_t memory_reclaim_request_token,
	std::vector<ls::commands::stream_memory_reclamation_result_group>& result)
{
	if (target_consumer_channel_keys.empty()) {
		return;
	}

	std::size_t current_channel_index = 0;
	const auto channels_reclaimed_memory =
		ls::consumer::reclaim_channels_memory(consumer_socket,
						      target_consumer_channel_keys,
						      reclaim_older_than_age,
						      only_reclaim_consumed_data,
						      memory_reclaim_request_token);

	for (const auto& channel_reclaimed_memory : channels_reclaimed_memory) {
		const auto& group_owner = stream_group_owners.at(current_channel_index);

		std::uint64_t cpu_id = 0;
		std::vector<ls::commands::stream_memory_reclamation_result> streams_reclaimed_memory;
		for (const auto& stream_reclaimed_memory :
		     channel_reclaimed_memory.streams_reclaimed_memory) {
			const ls::commands::stream_identifier stream_identifier{
				is_per_cpu_stream ?
					decltype(ls::commands::stream_identifier::cpu_id)(
						cpu_id++) :
					nonstd::nullopt
			};

			streams_reclaimed_memory.emplace_back(
				stream_identifier,
				stream_reclaimed_memory.subbuffers_reclaimed,
				stream_reclaimed_memory.pending_subbuffers_to_reclaim);
		}

		result.emplace_back(group_owner, std::move(streams_reclaimed_memory));

		current_channel_index++;
	}
}

void append_consumer_channel_memory_usage(
	std::vector<ls::commands::stream_memory_usage_group>& result,
	const std::vector<std::uint64_t>& consumer_channel_keys,
	const std::vector<ls::commands::stream_group_owner>& stream_group_owners,
	bool is_per_cpu_stream,
	consumer_socket& consumer_socket)
{
	if (consumer_channel_keys.empty()) {
		return;
	}

	std::size_t current_channel_index = 0;
	const auto channels_memory_usage =
		ls::consumer::get_channels_memory_usage(consumer_socket, consumer_channel_keys);

	for (const auto& channel_usage : channels_memory_usage) {
		const auto& group_owner = stream_group_owners.at(current_channel_index);

		std::uint64_t cpu_id = 0;
		std::vector<ls::commands::stream_memory_usage> streams_memory_usage;
		for (const auto& stream_usage : channel_usage.streams_memory_usage) {
			const ls::commands::stream_identifier stream_identifier{
				is_per_cpu_stream ?
					decltype(ls::commands::stream_identifier::cpu_id)(
						cpu_id++) :
					nonstd::nullopt
			};

			streams_memory_usage.emplace_back(stream_identifier,
							  stream_usage.size_bytes.logical,
							  stream_usage.size_bytes.physical);
		}

		result.emplace_back(group_owner, std::move(streams_memory_usage));

		current_channel_index++;
	}
}

} /* namespace */

void ls::ust::domain_orchestrator::_collect_stream_group_keys_by_bitness(
	const lsc::recording_channel_configuration& target_channel_config,
	std::vector<std::uint64_t>& consumer32_channel_keys,
	std::vector<std::uint64_t>& consumer64_channel_keys,
	std::vector<ls::commands::stream_group_owner>& consumer32_owners,
	std::vector<ls::commands::stream_group_owner>& consumer64_owners) const
{
	_for_each_consumer_stream_group([&target_channel_config,
					 &consumer32_channel_keys,
					 &consumer64_channel_keys,
					 &consumer32_owners,
					 &consumer64_owners](
						const _consumer_stream_group_descriptor& desc) {
		/* Only consider data channels. */
		if (desc.is_metadata) {
			return;
		}

		/*
		 * Filter by channel configuration pointer identity: the
		 * orchestrator passes the actual recording_channel_configuration
		 * reference from the stream group key.
		 */
		if (&desc.channel_config != &target_channel_config) {
			return;
		}

		const auto owner = [&desc]() {
			if (desc.owner_uid) {
				return ls::commands::stream_group_owner(desc.abi, *desc.owner_uid);
			}

			return ls::commands::stream_group_owner(desc.abi, *desc.owner_pid);
		}();

		if (desc.abi == ls::ust::application_abi::ABI_32) {
			consumer32_channel_keys.emplace_back(desc.consumer_key);
			consumer32_owners.emplace_back(owner);
		} else {
			consumer64_channel_keys.emplace_back(desc.consumer_key);
			consumer64_owners.emplace_back(owner);
		}
	});
}

ls::commands::reclaim_channel_memory_result ls::ust::domain_orchestrator::reclaim_channel_memory(
	const config::recording_channel_configuration& target_channel,
	const nonstd::optional<std::chrono::microseconds>& reclaim_older_than_age,
	bool require_consumed,
	ls::commands::completion_callback_t on_complete,
	ls::commands::cancellation_callback_t on_cancel)
{
	const auto is_per_cpu_stream = target_channel.buffer_allocation_policy ==
		lsc::recording_channel_configuration::buffer_allocation_policy_t::PER_CPU;

	std::vector<std::uint64_t> consumer32_channel_keys, consumer64_channel_keys;
	std::vector<ls::commands::stream_group_owner> consumer32_owners, consumer64_owners;

	_collect_stream_group_keys_by_bitness(target_channel,
					      consumer32_channel_keys,
					      consumer64_channel_keys,
					      consumer32_owners,
					      consumer64_owners);

	const unsigned int consumer_count = (!consumer32_channel_keys.empty() ? 1 : 0) +
		(!consumer64_channel_keys.empty() ? 1 : 0);

	/*
	 * Create the completion tracking request before issuing reclaim operations.
	 * The consumers will signal completion on their own when they're done.
	 *
	 * The const_cast is needed because the pending reclamation registry
	 * mutates the session's pending reclamation state. The session lock
	 * is held by the caller.
	 */
	DBG_FMT("Creating completion tracking request: consumer_count={}", consumer_count);

	/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) */
	auto& mutable_session = const_cast<ltt_session&>(_session);
	const auto token =
		ls::the_pending_memory_reclamation_registry.create_request(mutable_session,
									   target_channel.name,
									   consumer_count,
									   std::move(on_complete),
									   std::move(on_cancel));

	std::vector<ls::commands::stream_memory_reclamation_result_group> result;

	/* Handle 32-bit ABI stream groups. */
	if (!consumer32_channel_keys.empty()) {
		const lttng::urcu::read_lock_guard read_lock;

		try {
			issue_consumer_reclaim_channel_memory(
				*consumer_find_socket_by_bitness(32, _consumer_output.get()),
				consumer32_owners,
				is_per_cpu_stream,
				consumer32_channel_keys,
				reclaim_older_than_age,
				require_consumed,
				token,
				result);
		} catch (const std::exception& e) {
			ls::the_pending_memory_reclamation_registry.cancel_request(token);
			throw;
		}
	}

	/* Handle 64-bit ABI stream groups. */
	if (!consumer64_channel_keys.empty()) {
		const lttng::urcu::read_lock_guard read_lock;

		try {
			issue_consumer_reclaim_channel_memory(
				*consumer_find_socket_by_bitness(64, _consumer_output.get()),
				consumer64_owners,
				is_per_cpu_stream,
				consumer64_channel_keys,
				reclaim_older_than_age,
				require_consumed,
				token,
				result);
		} catch (const std::exception& e) {
			ls::the_pending_memory_reclamation_registry.cancel_request(token);
			throw;
		}
	}

	/* Log results. */
	for (const auto& stream_group : result) {
		const auto total_reclaimed = std::accumulate(
			stream_group.reclaimed_streams_memory.begin(),
			stream_group.reclaimed_streams_memory.end(),
			0ULL,
			[](std::uint64_t sum,
			   const ls::commands::stream_memory_reclamation_result& stream_result) {
				return sum + stream_result.subbuffers_reclaimed;
			});
		const auto total_pending = std::accumulate(
			stream_group.reclaimed_streams_memory.begin(),
			stream_group.reclaimed_streams_memory.end(),
			0ULL,
			[](std::uint64_t sum,
			   const ls::commands::stream_memory_reclamation_result& stream_result) {
				return sum + stream_result.pending_subbuffers_to_reclaim;
			});

		DBG_FMT("Reclaimed sub-buffers for streams in group: session_name=`{}`, channel_name=`{}`, "
			"owner_type={}, bitness={}, streams_count={}, total_reclaimed={}, total_pending={}",
			_session.name,
			target_channel.name,
			stream_group.owner.owner_type,
			stream_group.owner.bitness,
			stream_group.reclaimed_streams_memory.size(),
			total_reclaimed,
			total_pending);

		for (const auto& stream_result : stream_group.reclaimed_streams_memory) {
			DBG_FMT("Reclaimed stream sub-buffers: id={}, subbuffers_reclaimed={}, pending_subbuffers={}",
				stream_result.id,
				stream_result.subbuffers_reclaimed,
				stream_result.pending_subbuffers_to_reclaim);
		}
	}

	return { std::move(result), token };
}

std::vector<ls::commands::stream_memory_usage_group>
ls::ust::domain_orchestrator::get_channel_memory_usage(
	const config::recording_channel_configuration& target_channel) const
{
	const auto is_per_cpu_stream = target_channel.buffer_allocation_policy ==
		lsc::recording_channel_configuration::buffer_allocation_policy_t::PER_CPU;

	std::vector<std::uint64_t> consumer32_channel_keys, consumer64_channel_keys;
	std::vector<ls::commands::stream_group_owner> consumer32_owners, consumer64_owners;

	_collect_stream_group_keys_by_bitness(target_channel,
					      consumer32_channel_keys,
					      consumer64_channel_keys,
					      consumer32_owners,
					      consumer64_owners);

	std::vector<ls::commands::stream_memory_usage_group> result;

	if (!consumer32_channel_keys.empty()) {
		const lttng::urcu::read_lock_guard read_lock;

		append_consumer_channel_memory_usage(
			result,
			consumer32_channel_keys,
			consumer32_owners,
			is_per_cpu_stream,
			*consumer_find_socket_by_bitness(32, _consumer_output.get()));
	}

	if (!consumer64_channel_keys.empty()) {
		const lttng::urcu::read_lock_guard read_lock;

		append_consumer_channel_memory_usage(
			result,
			consumer64_channel_keys,
			consumer64_owners,
			is_per_cpu_stream,
			*consumer_find_socket_by_bitness(64, _consumer_output.get()));
	}

	/* Log results. */
	for (const auto& stream_group : result) {
		DBG_FMT("Stream group memory usage: session_name=`{}`, channel_name=`{}`, "
			"owner_type={}, bitness={}, streams_count={}, usage_ratio={:.3f}%",
			_session.name,
			target_channel.name,
			stream_group.owner.owner_type,
			stream_group.owner.bitness,
			stream_group.streams_memory_usage.size(),
			[&stream_group]() {
				if (stream_group.streams_memory_usage.empty()) {
					return 0.0;
				}

				std::uint64_t logical_size = 0, physical_size = 0;
				for (const auto& usage : stream_group.streams_memory_usage) {
					logical_size += usage.size_bytes.logical;
					physical_size += usage.size_bytes.physical;
				}

				if (logical_size == 0) {
					return 0.0;
				}

				return (static_cast<double>(physical_size) / logical_size) * 100.0;
			}());

		for (const auto& stream_usage : stream_group.streams_memory_usage) {
			DBG_FMT("Stream memory usage: id='{}', logical_size_bytes={}, "
				"physical_size_bytes={}",
				stream_usage.id,
				stream_usage.size_bytes.logical,
				stream_usage.size_bytes.physical);
		}
	}

	return result;
}

void ls::ust::domain_orchestrator::_save_per_pid_stats_on_departure(const ust::app_session& ua_sess)
{
	for (const auto& chan_pair : ua_sess.channels) {
		const auto *ua_chan = chan_pair.second.get();

		/* Metadata channels do not have discarded/lost counters. */
		if (ua_chan->channel_config.channel_type() ==
		    lsc::channel_configuration::channel_type_t::METADATA) {
			continue;
		}

		const auto& recording_config =
			static_cast<const lsc::recording_channel_configuration&>(
				ua_chan->channel_config);

		std::uint64_t discarded = 0, lost = 0;

		if (recording_config.buffer_full_policy ==
		    lsc::channel_configuration::buffer_full_policy_t::OVERWRITE_OLDEST_PACKET) {
			consumer_get_lost_packets(ua_sess.recording_session_id,
						  ua_chan->key,
						  get_consumer_output_ptr(),
						  &lost);
		} else {
			consumer_get_discarded_events(ua_sess.recording_session_id,
						      ua_chan->key,
						      get_consumer_output_ptr(),
						      &discarded);
		}

		_accumulate_per_pid_closed_app_stats(recording_config, discarded, lost);
	}
}

void ls::ust::domain_orchestrator::_accumulate_per_pid_closed_app_stats(
	const config::recording_channel_configuration& channel_config,
	std::uint64_t discarded_events,
	std::uint64_t lost_packets)
{
	auto& counters = _per_pid_closed_app_stats[&channel_config];

	counters.discarded_events += discarded_events;
	counters.lost_packets += lost_packets;
}

ls::recording_channel_runtime_stats
ls::ust::domain_orchestrator::get_recording_channel_runtime_stats(
	const config::recording_channel_configuration& channel_config) const
{
	recording_channel_runtime_stats stats = {};
	const auto is_overwrite = channel_config.buffer_full_policy ==
		lsc::channel_configuration::buffer_full_policy_t::OVERWRITE_OLDEST_PACKET;

	if (_default_buffer_ownership ==
	    lsc::recording_channel_configuration::ownership_model_t::PER_UID) {
		/*
		 * Find the first per-UID stream group matching this channel
		 * configuration and query the consumer daemon for its stats.
		 */
		uint64_t consumer_chan_key = 0;
		bool found = false;

		for (const auto& sg_entry : _per_uid_stream_groups) {
			if (sg_entry.first.channel_config == &channel_config) {
				consumer_chan_key = sg_entry.second->consumer_key();
				found = true;
				break;
			}
		}

		if (found) {
			if (is_overwrite) {
				const auto ret_lost =
					consumer_get_lost_packets(_session.id,
								  consumer_chan_key,
								  _consumer_output.get(),
								  &stats.lost_packets);
				if (ret_lost < 0) {
					LTTNG_THROW_ERROR(fmt::format(
						"Failed to get lost packets from consumer: channel_name=`{}`",
						channel_config.name));
				}
			} else {
				const auto ret_disc =
					consumer_get_discarded_events(_session.id,
								      consumer_chan_key,
								      _consumer_output.get(),
								      &stats.discarded_events);
				if (ret_disc < 0) {
					LTTNG_THROW_ERROR(fmt::format(
						"Failed to get discarded events from consumer: channel_name=`{}`",
						channel_config.name));
				}
			}
		}
	} else {
		/*
		 * Per-PID: iterate all per-PID stream groups matching this
		 * channel configuration and query the consumer daemon for
		 * each app's stats.
		 */
		for (const auto& sg_entry : _per_pid_stream_groups) {
			if (sg_entry.first.channel_config != &channel_config) {
				continue;
			}

			const auto consumer_chan_key = sg_entry.second->consumer_key();

			if (is_overwrite) {
				uint64_t lost = 0;

				const auto ret = consumer_get_lost_packets(_session.id,
									   consumer_chan_key,
									   _consumer_output.get(),
									   &lost);
				if (ret < 0) {
					LTTNG_THROW_ERROR(fmt::format(
						"Failed to get per-PID lost packets from consumer: channel_name=`{}`",
						channel_config.name));
				}

				stats.lost_packets += lost;
			} else {
				uint64_t discarded = 0;

				const auto ret =
					consumer_get_discarded_events(_session.id,
								      consumer_chan_key,
								      _consumer_output.get(),
								      &discarded);
				if (ret < 0) {
					LTTNG_THROW_ERROR(fmt::format(
						"Failed to get per-PID discarded events from consumer: channel_name=`{}`",
						channel_config.name));
				}

				stats.discarded_events += discarded;
			}
		}
	}
	/* Add accumulated stats from applications that have already exited. */
	{
		const auto closed_it = _per_pid_closed_app_stats.find(&channel_config);
		if (closed_it != _per_pid_closed_app_stats.end()) {
			stats.discarded_events += closed_it->second.discarded_events;
			stats.lost_packets += closed_it->second.lost_packets;
		}
	}

	return stats;
}

void ls::ust::domain_orchestrator::add_map_channel(const lsc::map_channel_configuration& config)
{
	DBG_FMT("UST domain orchestrator adding map channel: session_name=`{}`, config={}",
		_session.name,
		config);

	auto registry = lttng::make_unique<ust::key_registry>(config.max_entry_count);
	auto channel_uptr = lttng::make_unique<ust::map_channel>(config, std::move(registry));
	auto& channel = *channel_uptr;

	_map_channels.emplace(&config, std::move(channel_uptr));

	/*
	 * Late-arriving channel: attach to every application that has
	 * already established an app_session for this recording session.
	 * Apps registered later get the channel via synchronize_app.
	 */
	for (auto& app_session_pair : _app_sessions) {
		auto& app = *app_session_pair.first;
		auto& ua_sess = *app_session_pair.second;

		if (ua_sess.deleted || ua_sess.handle < 0) {
			continue;
		}

		try {
			auto attachment = channel.attach_to_app(app, ua_sess.handle);
			if (attachment) {
				ua_sess.map_channel_attachments.emplace(&channel,
									std::move(*attachment));
			}
		} catch (const ust::app_communication_error& ex) {
			DBG_FMT("Application unreachable while attaching map channel: app={}, map_name=`{}`, error=`{}`",
				app,
				config.name,
				ex.what());
		} catch (const std::exception& ex) {
			ERR_FMT("Failed to attach map channel to application: app={}, map_name=`{}`, error=`{}`",
				app,
				config.name,
				ex.what());
		}
	}
}

void ls::ust::domain_orchestrator::remove_map_channel(const lsc::map_channel_configuration& config)
{
	const auto erased = _map_channels.erase(&config);
	LTTNG_ASSERT(erased == 1);
}

void ls::ust::domain_orchestrator::add_map_channel_event_rule(
	const lsc::map_channel_configuration& target_map,
	const lttng_event_rule& event_rule,
	const lttng_action& incr_map_value_action)
{
	DBG_FMT("UST orchestrator adding map channel event rule: map={}", target_map);

	const auto channel_it = _map_channels.find(&target_map);
	LTTNG_ASSERT(channel_it != _map_channels.end());
	auto& channel = *channel_it->second;

	const auto user_token = channel.allocate_user_token();

	/*
	 * Record the rule on the channel before fanning out: if installation
	 * fails on some apps, the rule still exists at the channel level so
	 * app churn (a late-attaching app picks it up in
	 * _synchronize_app_map_channels) and rule removal stay consistent.
	 * Failed apps simply lack the per-app attachment.
	 */
	const sessiond::map::event_rule_action_key key{ &event_rule, &incr_map_value_action };
	const auto inserted =
		channel._rules.emplace(user_token, ust::map_channel::rule_record{ key });
	LTTNG_ASSERT(inserted.second);

	for (auto& app_session_entry : _app_sessions) {
		auto& app = *app_session_entry.first;
		auto& ua_sess = *app_session_entry.second;

		if (ua_sess.deleted || ua_sess.handle < 0) {
			continue;
		}

		/*
		 * Only apps that received the channel can host a rule against
		 * it; the channel attachment may have failed for some apps.
		 */
		if (ua_sess.map_channel_attachments.count(&channel) == 0) {
			continue;
		}

		try {
			_install_rule_on_app(app,
					     ua_sess,
					     channel,
					     event_rule,
					     incr_map_value_action,
					     user_token);
		} catch (const ust::app_communication_error& ex) {
			DBG_FMT("Application unreachable while installing map channel event rule: "
				"app={}, map_name=`{}`, error=`{}`",
				app,
				target_map.name,
				ex.what());
		} catch (const std::exception& ex) {
			ERR_FMT("Failed to install map channel event rule on application: "
				"app={}, map_name=`{}`, error=`{}`",
				app,
				target_map.name,
				ex.what());
		}
	}
}

void ls::ust::domain_orchestrator::remove_map_channel_event_rule(
	const lsc::map_channel_configuration& target_map,
	const lttng_event_rule& event_rule,
	const lttng_action& incr_map_value_action)
{
	DBG_FMT("UST orchestrator removing map channel event rule: map={}", target_map);

	const auto channel_it = _map_channels.find(&target_map);
	LTTNG_ASSERT(channel_it != _map_channels.end());
	auto& channel = *channel_it->second;

	/* Drop every per-app attachment first, then the channel-level record. */
	for (auto& app_session_entry : _app_sessions) {
		_uninstall_rule_from_app(
			*app_session_entry.second, channel, event_rule, incr_map_value_action);
	}

	const auto rule_it =
		sessiond::map::find_rule(channel._rules, event_rule, incr_map_value_action);

	LTTNG_ASSERT(rule_it != channel._rules.end());
	channel._rules.erase(rule_it);
}

void ls::ust::domain_orchestrator::increment_map_value(
	const lsc::map_channel_configuration& target_map,
	const std::string& key,
	std::int64_t delta)
{
	DBG_FMT("UST orchestrator incrementing shared map value: map={}, key=`{}`, delta={}",
		target_map,
		key,
		delta);

	const auto it = _map_channels.find(&target_map);
	LTTNG_ASSERT(it != _map_channels.end());

	it->second->increment_shared_value(key, delta);
}

void ls::ust::domain_orchestrator::for_each_map_channel(
	const std::function<void(const ls::map::map_channel&)>& visitor) const
{
	for (const auto& entry : _map_channels) {
		visitor(*entry.second);
	}
}

const ls::map::map_channel&
ls::ust::domain_orchestrator::map_channel_for(const lsc::map_channel_configuration& config) const
{
	const auto it = _map_channels.find(&config);

	LTTNG_ASSERT(it != _map_channels.end());
	return *it->second;
}

std::uint64_t ls::ust::domain_orchestrator::get_size_one_more_packet_per_stream(
	std::uint64_t cur_nr_packets) const
{
	std::uint64_t tot_size = 0;

	if (_default_buffer_ownership ==
	    lsc::recording_channel_configuration::ownership_model_t::PER_UID) {
		for (const auto& sg_entry : _per_uid_stream_groups) {
			const auto& config = sg_entry.second->configuration();

			if (cur_nr_packets >= config.subbuffer_count) {
				continue;
			}

			tot_size += config.subbuffer_size_bytes * sg_entry.second->stream_count();
		}
	} else {
		for (const auto& sg_entry : _per_pid_stream_groups) {
			const auto& config = sg_entry.second->configuration();

			if (cur_nr_packets >= config.subbuffer_count) {
				continue;
			}

			tot_size += config.subbuffer_size_bytes * sg_entry.second->stream_count();
		}
	}

	return tot_size;
}

void ls::ust::domain_orchestrator::_for_each_consumer_stream_group(
	const consumer_stream_group_visitor& visitor) const
{
	const auto& metadata_config = _session.user_space_domain.metadata_channel();

	if (_default_buffer_ownership ==
	    lsc::recording_channel_configuration::ownership_model_t::PER_UID) {
		for (const auto& sg_entry : _per_uid_stream_groups) {
			visitor(_consumer_stream_group_descriptor{
				sg_entry.first.abi,
				sg_entry.second->consumer_key(),
				false,
				sg_entry.second->get_trace_class(),
				*sg_entry.first.channel_config,
				nonstd::optional<uid_t>(sg_entry.first.uid),
				nonstd::nullopt });
		}

		for (const auto& tc_entry : _per_uid_trace_classes) {
			if (!tc_entry.second->_metadata_key) {
				continue;
			}

			visitor(_consumer_stream_group_descriptor{
				tc_entry.first.abi,
				tc_entry.second->_metadata_key,
				true,
				*tc_entry.second,
				metadata_config,
				nonstd::optional<uid_t>(tc_entry.first.uid),
				nonstd::nullopt });
		}
	} else {
		for (const auto& sg_entry : _per_pid_stream_groups) {
			const auto app_abi = sg_entry.first.app->abi_desc.bits_per_long == 32 ?
				application_abi::ABI_32 :
				application_abi::ABI_64;

			visitor(_consumer_stream_group_descriptor{
				app_abi,
				sg_entry.second->consumer_key(),
				false,
				sg_entry.second->get_trace_class(),
				*sg_entry.first.channel_config,
				nonstd::nullopt,
				nonstd::optional<pid_t>(sg_entry.first.app->pid) });
		}

		for (const auto& tc_entry : _per_pid_trace_classes) {
			if (!tc_entry.second->_metadata_key) {
				continue;
			}

			const auto app_abi = tc_entry.first->abi_desc.bits_per_long == 32 ?
				application_abi::ABI_32 :
				application_abi::ABI_64;

			visitor(_consumer_stream_group_descriptor{
				app_abi,
				tc_entry.second->_metadata_key,
				true,
				*tc_entry.second,
				metadata_config,
				nonstd::nullopt,
				nonstd::optional<pid_t>(tc_entry.first->pid) });
		}
	}
}

/* Key comparison and hash implementations. */

bool ls::ust::domain_orchestrator::_per_uid_trace_class_key::operator==(
	const _per_uid_trace_class_key& other) const noexcept
{
	return uid == other.uid && abi == other.abi;
}

std::size_t ls::ust::domain_orchestrator::_per_uid_trace_class_key::hash() const noexcept
{
	return lttng::utils::hash_combine(
		std::hash<uid_t>{}(uid), std::hash<std::uint8_t>{}(static_cast<std::uint8_t>(abi)));
}

bool ls::ust::domain_orchestrator::_per_uid_stream_group_key::operator==(
	const _per_uid_stream_group_key& other) const noexcept
{
	return channel_config == other.channel_config && uid == other.uid && abi == other.abi;
}

std::size_t ls::ust::domain_orchestrator::_per_uid_stream_group_key::hash() const noexcept
{
	auto h = std::hash<const void *>{}(channel_config);
	h = lttng::utils::hash_combine(h, std::hash<uid_t>{}(uid));
	h = lttng::utils::hash_combine(h,
				       std::hash<std::uint8_t>{}(static_cast<std::uint8_t>(abi)));
	return h;
}

bool ls::ust::domain_orchestrator::_per_pid_stream_group_key::operator==(
	const _per_pid_stream_group_key& other) const noexcept
{
	return channel_config == other.channel_config && app == other.app;
}

std::size_t ls::ust::domain_orchestrator::_per_pid_stream_group_key::hash() const noexcept
{
	return lttng::utils::hash_combine(std::hash<const void *>{}(channel_config),
					  std::hash<const void *>{}(app));
}
