// // detail/impl/signal_set_service.ipp // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef ASIO_DETAIL_IMPL_SIGNAL_SET_SERVICE_IPP #define ASIO_DETAIL_IMPL_SIGNAL_SET_SERVICE_IPP #if defined(_MSC_VER) && (_MSC_VER >= 1200) # pragma once #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) #include "asio/detail/config.hpp" #include #include #include "asio/detail/signal_blocker.hpp" #include "asio/detail/signal_set_service.hpp" #include "asio/detail/static_mutex.hpp" #include "asio/detail/throw_exception.hpp" #if defined(ASIO_HAS_IO_URING_AS_DEFAULT) # include "asio/detail/io_uring_service.hpp" #else // defined(ASIO_HAS_IO_URING_AS_DEFAULT) # include "asio/detail/reactor.hpp" #endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT) #include "asio/detail/push_options.hpp" namespace asio { namespace detail { struct signal_state { // Mutex used for protecting global state. static_mutex mutex_; // The read end of the pipe used for signal notifications. int read_descriptor_; // The write end of the pipe used for signal notifications. int write_descriptor_; // Whether the signal state has been prepared for a fork. bool fork_prepared_; // The head of a linked list of all signal_set_service instances. class signal_set_service* service_list_; // A count of the number of objects that are registered for each signal. std::size_t registration_count_[max_signal_number]; }; signal_state* get_signal_state() { static signal_state state = { ASIO_STATIC_MUTEX_INIT, -1, -1, false, 0, { 0 } }; return &state; } void asio_signal_handler(int signal_number) { #if defined(ASIO_WINDOWS) \ || defined(ASIO_WINDOWS_RUNTIME) \ || defined(__CYGWIN__) signal_set_service::deliver_signal(signal_number); #else // defined(ASIO_WINDOWS) // || defined(ASIO_WINDOWS_RUNTIME) // || defined(__CYGWIN__) int saved_errno = errno; signal_state* state = get_signal_state(); signed_size_type result = ::write(state->write_descriptor_, &signal_number, sizeof(signal_number)); (void)result; errno = saved_errno; #endif // defined(ASIO_WINDOWS) // || defined(ASIO_WINDOWS_RUNTIME) // || defined(__CYGWIN__) #if defined(ASIO_HAS_SIGNAL) && !defined(ASIO_HAS_SIGACTION) ::signal(signal_number, asio_signal_handler); #endif // defined(ASIO_HAS_SIGNAL) && !defined(ASIO_HAS_SIGACTION) } #if !defined(ASIO_WINDOWS) \ && !defined(ASIO_WINDOWS_RUNTIME) \ && !defined(__CYGWIN__) class signal_set_service::pipe_read_op : # if defined(ASIO_HAS_IO_URING_AS_DEFAULT) public io_uring_operation # else // defined(ASIO_HAS_IO_URING_AS_DEFAULT) public reactor_op # endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT) { public: # if defined(ASIO_HAS_IO_URING_AS_DEFAULT) pipe_read_op() : io_uring_operation(asio::error_code(), &pipe_read_op::do_prepare, &pipe_read_op::do_perform, pipe_read_op::do_complete) { } static void do_prepare(io_uring_operation*, ::io_uring_sqe* sqe) { signal_state* state = get_signal_state(); int fd = state->read_descriptor_; ::io_uring_prep_poll_add(sqe, fd, POLLIN); } static bool do_perform(io_uring_operation*, bool) { signal_state* state = get_signal_state(); int fd = state->read_descriptor_; int signal_number = 0; while (::read(fd, &signal_number, sizeof(int)) == sizeof(int)) if (signal_number >= 0 && signal_number < max_signal_number) signal_set_service::deliver_signal(signal_number); return false; } # else // defined(ASIO_HAS_IO_URING_AS_DEFAULT) pipe_read_op() : reactor_op(asio::error_code(), &pipe_read_op::do_perform, pipe_read_op::do_complete) { } static status do_perform(reactor_op*) { signal_state* state = get_signal_state(); int fd = state->read_descriptor_; int signal_number = 0; while (::read(fd, &signal_number, sizeof(int)) == sizeof(int)) if (signal_number >= 0 && signal_number < max_signal_number) signal_set_service::deliver_signal(signal_number); return not_done; } # endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT) static void do_complete(void* /*owner*/, operation* base, const asio::error_code& /*ec*/, std::size_t /*bytes_transferred*/) { pipe_read_op* o(static_cast(base)); delete o; } }; #endif // !defined(ASIO_WINDOWS) // && !defined(ASIO_WINDOWS_RUNTIME) // && !defined(__CYGWIN__) signal_set_service::signal_set_service(execution_context& context) : execution_context_service_base(context), scheduler_(asio::use_service(context)), #if !defined(ASIO_WINDOWS) \ && !defined(ASIO_WINDOWS_RUNTIME) \ && !defined(__CYGWIN__) # if defined(ASIO_HAS_IO_URING_AS_DEFAULT) io_uring_service_(asio::use_service(context)), # else // defined(ASIO_HAS_IO_URING_AS_DEFAULT) reactor_(asio::use_service(context)), # endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT) #endif // !defined(ASIO_WINDOWS) // && !defined(ASIO_WINDOWS_RUNTIME) // && !defined(__CYGWIN__) next_(0), prev_(0) { get_signal_state()->mutex_.init(); #if !defined(ASIO_WINDOWS) \ && !defined(ASIO_WINDOWS_RUNTIME) \ && !defined(__CYGWIN__) # if defined(ASIO_HAS_IO_URING_AS_DEFAULT) io_uring_service_.init_task(); # else // defined(ASIO_HAS_IO_URING_AS_DEFAULT) reactor_.init_task(); # endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT) #endif // !defined(ASIO_WINDOWS) // && !defined(ASIO_WINDOWS_RUNTIME) // && !defined(__CYGWIN__) for (int i = 0; i < max_signal_number; ++i) registrations_[i] = 0; add_service(this); } signal_set_service::~signal_set_service() { remove_service(this); } void signal_set_service::shutdown() { remove_service(this); op_queue ops; for (int i = 0; i < max_signal_number; ++i) { registration* reg = registrations_[i]; while (reg) { ops.push(*reg->queue_); reg = reg->next_in_table_; } } scheduler_.abandon_operations(ops); } void signal_set_service::notify_fork(execution_context::fork_event fork_ev) { #if !defined(ASIO_WINDOWS) \ && !defined(ASIO_WINDOWS_RUNTIME) \ && !defined(__CYGWIN__) signal_state* state = get_signal_state(); static_mutex::scoped_lock lock(state->mutex_); switch (fork_ev) { case execution_context::fork_prepare: { int read_descriptor = state->read_descriptor_; state->fork_prepared_ = true; lock.unlock(); # if defined(ASIO_HAS_IO_URING_AS_DEFAULT) (void)read_descriptor; io_uring_service_.deregister_io_object(io_object_data_); io_uring_service_.cleanup_io_object(io_object_data_); # else // defined(ASIO_HAS_IO_URING_AS_DEFAULT) reactor_.deregister_internal_descriptor(read_descriptor, reactor_data_); reactor_.cleanup_descriptor_data(reactor_data_); # endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT) } break; case execution_context::fork_parent: if (state->fork_prepared_) { int read_descriptor = state->read_descriptor_; state->fork_prepared_ = false; lock.unlock(); # if defined(ASIO_HAS_IO_URING_AS_DEFAULT) (void)read_descriptor; io_uring_service_.register_internal_io_object(io_object_data_, io_uring_service::read_op, new pipe_read_op); # else // defined(ASIO_HAS_IO_URING_AS_DEFAULT) reactor_.register_internal_descriptor(reactor::read_op, read_descriptor, reactor_data_, new pipe_read_op); # endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT) } break; case execution_context::fork_child: if (state->fork_prepared_) { asio::detail::signal_blocker blocker; close_descriptors(); open_descriptors(); int read_descriptor = state->read_descriptor_; state->fork_prepared_ = false; lock.unlock(); # if defined(ASIO_HAS_IO_URING_AS_DEFAULT) (void)read_descriptor; io_uring_service_.register_internal_io_object(io_object_data_, io_uring_service::read_op, new pipe_read_op); # else // defined(ASIO_HAS_IO_URING_AS_DEFAULT) reactor_.register_internal_descriptor(reactor::read_op, read_descriptor, reactor_data_, new pipe_read_op); # endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT) } break; default: break; } #else // !defined(ASIO_WINDOWS) // && !defined(ASIO_WINDOWS_RUNTIME) // && !defined(__CYGWIN__) (void)fork_ev; #endif // !defined(ASIO_WINDOWS) // && !defined(ASIO_WINDOWS_RUNTIME) // && !defined(__CYGWIN__) } void signal_set_service::construct( signal_set_service::implementation_type& impl) { impl.signals_ = 0; } void signal_set_service::destroy( signal_set_service::implementation_type& impl) { asio::error_code ignored_ec; clear(impl, ignored_ec); cancel(impl, ignored_ec); } asio::error_code signal_set_service::add( signal_set_service::implementation_type& impl, int signal_number, asio::error_code& ec) { // Check that the signal number is valid. if (signal_number < 0 || signal_number >= max_signal_number) { ec = asio::error::invalid_argument; return ec; } signal_state* state = get_signal_state(); static_mutex::scoped_lock lock(state->mutex_); // Find the appropriate place to insert the registration. registration** insertion_point = &impl.signals_; registration* next = impl.signals_; while (next && next->signal_number_ < signal_number) { insertion_point = &next->next_in_set_; next = next->next_in_set_; } // Only do something if the signal is not already registered. if (next == 0 || next->signal_number_ != signal_number) { registration* new_registration = new registration; #if defined(ASIO_HAS_SIGNAL) || defined(ASIO_HAS_SIGACTION) // Register for the signal if we're the first. if (state->registration_count_[signal_number] == 0) { # if defined(ASIO_HAS_SIGACTION) using namespace std; // For memset. struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = asio_signal_handler; sigfillset(&sa.sa_mask); if (::sigaction(signal_number, &sa, 0) == -1) # else // defined(ASIO_HAS_SIGACTION) if (::signal(signal_number, asio_signal_handler) == SIG_ERR) # endif // defined(ASIO_HAS_SIGACTION) { # if defined(ASIO_WINDOWS) || defined(__CYGWIN__) ec = asio::error::invalid_argument; # else // defined(ASIO_WINDOWS) || defined(__CYGWIN__) ec = asio::error_code(errno, asio::error::get_system_category()); # endif // defined(ASIO_WINDOWS) || defined(__CYGWIN__) delete new_registration; return ec; } } #endif // defined(ASIO_HAS_SIGNAL) || defined(ASIO_HAS_SIGACTION) // Record the new registration in the set. new_registration->signal_number_ = signal_number; new_registration->queue_ = &impl.queue_; new_registration->next_in_set_ = next; *insertion_point = new_registration; // Insert registration into the registration table. new_registration->next_in_table_ = registrations_[signal_number]; if (registrations_[signal_number]) registrations_[signal_number]->prev_in_table_ = new_registration; registrations_[signal_number] = new_registration; ++state->registration_count_[signal_number]; } ec = asio::error_code(); return ec; } asio::error_code signal_set_service::remove( signal_set_service::implementation_type& impl, int signal_number, asio::error_code& ec) { // Check that the signal number is valid. if (signal_number < 0 || signal_number >= max_signal_number) { ec = asio::error::invalid_argument; return ec; } signal_state* state = get_signal_state(); static_mutex::scoped_lock lock(state->mutex_); // Find the signal number in the list of registrations. registration** deletion_point = &impl.signals_; registration* reg = impl.signals_; while (reg && reg->signal_number_ < signal_number) { deletion_point = ®->next_in_set_; reg = reg->next_in_set_; } if (reg != 0 && reg->signal_number_ == signal_number) { #if defined(ASIO_HAS_SIGNAL) || defined(ASIO_HAS_SIGACTION) // Set signal handler back to the default if we're the last. if (state->registration_count_[signal_number] == 1) { # if defined(ASIO_HAS_SIGACTION) using namespace std; // For memset. struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; if (::sigaction(signal_number, &sa, 0) == -1) # else // defined(ASIO_HAS_SIGACTION) if (::signal(signal_number, SIG_DFL) == SIG_ERR) # endif // defined(ASIO_HAS_SIGACTION) { # if defined(ASIO_WINDOWS) || defined(__CYGWIN__) ec = asio::error::invalid_argument; # else // defined(ASIO_WINDOWS) || defined(__CYGWIN__) ec = asio::error_code(errno, asio::error::get_system_category()); # endif // defined(ASIO_WINDOWS) || defined(__CYGWIN__) return ec; } } #endif // defined(ASIO_HAS_SIGNAL) || defined(ASIO_HAS_SIGACTION) // Remove the registration from the set. *deletion_point = reg->next_in_set_; // Remove the registration from the registration table. if (registrations_[signal_number] == reg) registrations_[signal_number] = reg->next_in_table_; if (reg->prev_in_table_) reg->prev_in_table_->next_in_table_ = reg->next_in_table_; if (reg->next_in_table_) reg->next_in_table_->prev_in_table_ = reg->prev_in_table_; --state->registration_count_[signal_number]; delete reg; } ec = asio::error_code(); return ec; } asio::error_code signal_set_service::clear( signal_set_service::implementation_type& impl, asio::error_code& ec) { signal_state* state = get_signal_state(); static_mutex::scoped_lock lock(state->mutex_); while (registration* reg = impl.signals_) { #if defined(ASIO_HAS_SIGNAL) || defined(ASIO_HAS_SIGACTION) // Set signal handler back to the default if we're the last. if (state->registration_count_[reg->signal_number_] == 1) { # if defined(ASIO_HAS_SIGACTION) using namespace std; // For memset. struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; if (::sigaction(reg->signal_number_, &sa, 0) == -1) # else // defined(ASIO_HAS_SIGACTION) if (::signal(reg->signal_number_, SIG_DFL) == SIG_ERR) # endif // defined(ASIO_HAS_SIGACTION) { # if defined(ASIO_WINDOWS) || defined(__CYGWIN__) ec = asio::error::invalid_argument; # else // defined(ASIO_WINDOWS) || defined(__CYGWIN__) ec = asio::error_code(errno, asio::error::get_system_category()); # endif // defined(ASIO_WINDOWS) || defined(__CYGWIN__) return ec; } } #endif // defined(ASIO_HAS_SIGNAL) || defined(ASIO_HAS_SIGACTION) // Remove the registration from the registration table. if (registrations_[reg->signal_number_] == reg) registrations_[reg->signal_number_] = reg->next_in_table_; if (reg->prev_in_table_) reg->prev_in_table_->next_in_table_ = reg->next_in_table_; if (reg->next_in_table_) reg->next_in_table_->prev_in_table_ = reg->prev_in_table_; --state->registration_count_[reg->signal_number_]; impl.signals_ = reg->next_in_set_; delete reg; } ec = asio::error_code(); return ec; } asio::error_code signal_set_service::cancel( signal_set_service::implementation_type& impl, asio::error_code& ec) { ASIO_HANDLER_OPERATION((scheduler_.context(), "signal_set", &impl, 0, "cancel")); op_queue ops; { signal_state* state = get_signal_state(); static_mutex::scoped_lock lock(state->mutex_); while (signal_op* op = impl.queue_.front()) { op->ec_ = asio::error::operation_aborted; impl.queue_.pop(); ops.push(op); } } scheduler_.post_deferred_completions(ops); ec = asio::error_code(); return ec; } void signal_set_service::cancel_ops_by_key( signal_set_service::implementation_type& impl, void* cancellation_key) { op_queue ops; { op_queue other_ops; signal_state* state = get_signal_state(); static_mutex::scoped_lock lock(state->mutex_); while (signal_op* op = impl.queue_.front()) { impl.queue_.pop(); if (op->cancellation_key_ == cancellation_key) { op->ec_ = asio::error::operation_aborted; ops.push(op); } else other_ops.push(op); } impl.queue_.push(other_ops); } scheduler_.post_deferred_completions(ops); } void signal_set_service::deliver_signal(int signal_number) { signal_state* state = get_signal_state(); static_mutex::scoped_lock lock(state->mutex_); signal_set_service* service = state->service_list_; while (service) { op_queue ops; registration* reg = service->registrations_[signal_number]; while (reg) { if (reg->queue_->empty()) { ++reg->undelivered_; } else { while (signal_op* op = reg->queue_->front()) { op->signal_number_ = signal_number; reg->queue_->pop(); ops.push(op); } } reg = reg->next_in_table_; } service->scheduler_.post_deferred_completions(ops); service = service->next_; } } void signal_set_service::add_service(signal_set_service* service) { signal_state* state = get_signal_state(); static_mutex::scoped_lock lock(state->mutex_); #if !defined(ASIO_WINDOWS) && !defined(__CYGWIN__) // If this is the first service to be created, open a new pipe. if (state->service_list_ == 0) open_descriptors(); #endif // !defined(ASIO_WINDOWS) && !defined(__CYGWIN__) // If a scheduler_ object is thread-unsafe then it must be the only // scheduler used to create signal_set objects. if (state->service_list_ != 0) { if (!ASIO_CONCURRENCY_HINT_IS_LOCKING(SCHEDULER, service->scheduler_.concurrency_hint()) || !ASIO_CONCURRENCY_HINT_IS_LOCKING(SCHEDULER, state->service_list_->scheduler_.concurrency_hint())) { std::logic_error ex( "Thread-unsafe execution context objects require " "exclusive access to signal handling."); asio::detail::throw_exception(ex); } } // Insert service into linked list of all services. service->next_ = state->service_list_; service->prev_ = 0; if (state->service_list_) state->service_list_->prev_ = service; state->service_list_ = service; #if !defined(ASIO_WINDOWS) \ && !defined(ASIO_WINDOWS_RUNTIME) \ && !defined(__CYGWIN__) // Register for pipe readiness notifications. int read_descriptor = state->read_descriptor_; lock.unlock(); # if defined(ASIO_HAS_IO_URING_AS_DEFAULT) (void)read_descriptor; service->io_uring_service_.register_internal_io_object( service->io_object_data_, io_uring_service::read_op, new pipe_read_op); # else // defined(ASIO_HAS_IO_URING_AS_DEFAULT) service->reactor_.register_internal_descriptor(reactor::read_op, read_descriptor, service->reactor_data_, new pipe_read_op); # endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT) #endif // !defined(ASIO_WINDOWS) // && !defined(ASIO_WINDOWS_RUNTIME) // && !defined(__CYGWIN__) } void signal_set_service::remove_service(signal_set_service* service) { signal_state* state = get_signal_state(); static_mutex::scoped_lock lock(state->mutex_); if (service->next_ || service->prev_ || state->service_list_ == service) { #if !defined(ASIO_WINDOWS) \ && !defined(ASIO_WINDOWS_RUNTIME) \ && !defined(__CYGWIN__) // Disable the pipe readiness notifications. int read_descriptor = state->read_descriptor_; lock.unlock(); # if defined(ASIO_HAS_IO_URING_AS_DEFAULT) (void)read_descriptor; service->io_uring_service_.deregister_io_object(service->io_object_data_); service->io_uring_service_.cleanup_io_object(service->io_object_data_); lock.lock(); # else // defined(ASIO_HAS_IO_URING_AS_DEFAULT) service->reactor_.deregister_internal_descriptor( read_descriptor, service->reactor_data_); service->reactor_.cleanup_descriptor_data(service->reactor_data_); lock.lock(); # endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT) #endif // !defined(ASIO_WINDOWS) // && !defined(ASIO_WINDOWS_RUNTIME) // && !defined(__CYGWIN__) // Remove service from linked list of all services. if (state->service_list_ == service) state->service_list_ = service->next_; if (service->prev_) service->prev_->next_ = service->next_; if (service->next_) service->next_->prev_= service->prev_; service->next_ = 0; service->prev_ = 0; #if !defined(ASIO_WINDOWS) && !defined(__CYGWIN__) // If this is the last service to be removed, close the pipe. if (state->service_list_ == 0) close_descriptors(); #endif // !defined(ASIO_WINDOWS) && !defined(__CYGWIN__) } } void signal_set_service::open_descriptors() { #if !defined(ASIO_WINDOWS) \ && !defined(ASIO_WINDOWS_RUNTIME) \ && !defined(__CYGWIN__) signal_state* state = get_signal_state(); int pipe_fds[2]; if (::pipe(pipe_fds) == 0) { state->read_descriptor_ = pipe_fds[0]; ::fcntl(state->read_descriptor_, F_SETFL, O_NONBLOCK); state->write_descriptor_ = pipe_fds[1]; ::fcntl(state->write_descriptor_, F_SETFL, O_NONBLOCK); #if defined(FD_CLOEXEC) ::fcntl(state->read_descriptor_, F_SETFD, FD_CLOEXEC); ::fcntl(state->write_descriptor_, F_SETFD, FD_CLOEXEC); #endif // defined(FD_CLOEXEC) } else { asio::error_code ec(errno, asio::error::get_system_category()); asio::detail::throw_error(ec, "signal_set_service pipe"); } #endif // !defined(ASIO_WINDOWS) // && !defined(ASIO_WINDOWS_RUNTIME) // && !defined(__CYGWIN__) } void signal_set_service::close_descriptors() { #if !defined(ASIO_WINDOWS) \ && !defined(ASIO_WINDOWS_RUNTIME) \ && !defined(__CYGWIN__) signal_state* state = get_signal_state(); if (state->read_descriptor_ != -1) ::close(state->read_descriptor_); state->read_descriptor_ = -1; if (state->write_descriptor_ != -1) ::close(state->write_descriptor_); state->write_descriptor_ = -1; #endif // !defined(ASIO_WINDOWS) // && !defined(ASIO_WINDOWS_RUNTIME) // && !defined(__CYGWIN__) } void signal_set_service::start_wait_op( signal_set_service::implementation_type& impl, signal_op* op) { scheduler_.work_started(); signal_state* state = get_signal_state(); static_mutex::scoped_lock lock(state->mutex_); registration* reg = impl.signals_; while (reg) { if (reg->undelivered_ > 0) { --reg->undelivered_; op->signal_number_ = reg->signal_number_; scheduler_.post_deferred_completion(op); return; } reg = reg->next_in_set_; } impl.queue_.push(op); } } // namespace detail } // namespace asio #include "asio/detail/pop_options.hpp" #endif // ASIO_DETAIL_IMPL_SIGNAL_SET_SERVICE_IPP