blob: 7c90eeb3d702e4444775e8eabca0e97a7cbb081f [file]
/** @file
A brief file description
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "P_UnixNetProcessor.h"
#include "P_Net.h"
#include "P_UnixNet.h"
#include "iocore/net/AsyncSignalEventIO.h"
#include "tscore/ink_hrtime.h"
#include "ts/ats_probe.h"
#if TS_USE_LINUX_IO_URING
#include "iocore/io_uring/IO_URING.h"
#endif
#include <limits>
ink_hrtime last_throttle_warning;
ink_hrtime last_shedding_warning;
int net_connections_throttle;
std::atomic<bool> net_memory_throttle = false;
int fds_throttle;
ink_hrtime last_transient_accept_error;
namespace
{
/// Config members that @c NetHandler::configure_per_thread_values reads.
constexpr unsigned long long PER_THREAD_DEPENDENT_CONFIG{0x3};
// std::bitset silently discards bits at or above its width, which would drop a
// member from the set without any diagnostic if Config ever shrinks. The first
// assertion keeps the shift in the second one well defined.
static_assert(NetHandler::CONFIG_ITEM_COUNT < std::numeric_limits<unsigned long long>::digits);
static_assert(PER_THREAD_DEPENDENT_CONFIG < (1ULL << NetHandler::CONFIG_ITEM_COUNT));
} // end anonymous namespace
NetHandler::Config NetHandler::global_config;
std::bitset<std::numeric_limits<unsigned int>::digits> NetHandler::active_thread_types;
const std::bitset<NetHandler::CONFIG_ITEM_COUNT> NetHandler::config_value_affects_per_thread_value{PER_THREAD_DEPENDENT_CONFIG};
namespace
{
DbgCtl dbg_ctl_inactivity_cop{"inactivity_cop"};
DbgCtl dbg_ctl_inactivity_cop_check{"inactivity_cop_check"};
DbgCtl dbg_ctl_inactivity_cop_verbose{"inactivity_cop_verbose"};
} // end anonymous namespace
NetHandler *
get_NetHandler(EThread *t)
{
return static_cast<NetHandler *>(ETHREAD_GET_PTR(t, unix_netProcessor.netHandler_offset));
}
PollCont *
get_PollCont(EThread *t)
{
return static_cast<PollCont *>(ETHREAD_GET_PTR(t, unix_netProcessor.pollCont_offset));
}
PollDescriptor *
get_PollDescriptor(EThread *t)
{
PollCont *p = get_PollCont(t);
return p->pollDescriptor;
}
// INKqa10496
// One Inactivity cop runs on each thread once every second and
// loops through the list of NetEvents and calls the timeouts
class InactivityCop : public Continuation
{
public:
explicit InactivityCop(Ptr<ProxyMutex> &m) : Continuation(m.get()) { SET_HANDLER(&InactivityCop::check_inactivity); }
int
check_inactivity(int event, Event *e)
{
(void)event;
ink_hrtime now = ink_get_hrtime();
NetHandler &nh = *get_NetHandler(this_ethread());
Dbg(dbg_ctl_inactivity_cop_check, "Checking inactivity on Thread-ID #%d", this_ethread()->id);
// The rest NetEvents in cop_list which are not triggered between InactivityCop runs.
// Use pop() to catch any closes caused by callbacks.
while (NetEvent *ne = nh.cop_list.pop()) {
// If we cannot get the lock don't stop just keep cleaning
MUTEX_TRY_LOCK(lock, ne->get_mutex(), this_ethread());
if (!lock.is_locked()) {
Metrics::Counter::increment(net_rsb.inactivity_cop_lock_acquire_failure);
continue;
}
if (ne->closed) {
nh.free_netevent(ne);
continue;
}
if (ne->default_inactivity_timeout_in == -1) {
// If no context-specific default inactivity timeout has been set by an
// override plugin, then use the global default.
Dbg(dbg_ctl_inactivity_cop,
"vc: %p setting the global default inactivity timeout of %d, next_inactivity_timeout_at: %" PRId64, ne,
nh.config.default_inactivity_timeout, ne->next_inactivity_timeout_at);
ne->set_default_inactivity_timeout(HRTIME_SECONDS(nh.config.default_inactivity_timeout));
}
// set a default inactivity timeout if one is not set
// The event `EVENT_INACTIVITY_TIMEOUT` only be triggered if a read
// or write I/O operation was set by `do_io_read()` or `do_io_write()`.
if (ne->next_inactivity_timeout_at == 0 && ne->default_inactivity_timeout_in > 0 && (ne->read.enabled || ne->write.enabled)) {
Dbg(dbg_ctl_inactivity_cop, "vc: %p inactivity timeout not set, setting a default of %d", ne,
nh.config.default_inactivity_timeout);
ne->use_default_inactivity_timeout = true;
ne->next_inactivity_timeout_at = ink_get_hrtime() + ne->default_inactivity_timeout_in;
ne->inactivity_timeout_in = 0;
Metrics::Counter::increment(net_rsb.default_inactivity_timeout_applied);
}
if (ne->next_inactivity_timeout_at && ne->next_inactivity_timeout_at < now) {
if (ne->is_default_inactivity_timeout()) {
// track the connections that timed out due to default inactivity
Dbg(dbg_ctl_inactivity_cop, "vc: %p timed out due to default inactivity timeout", ne);
Metrics::Counter::increment(net_rsb.default_inactivity_timeout_count);
}
if (nh.keep_alive_queue.in(ne)) {
// only stat if the connection is in keep-alive, there can be other inactivity timeouts
ink_hrtime diff = (now - (ne->next_inactivity_timeout_at - ne->inactivity_timeout_in)) / HRTIME_SECOND;
Metrics::Counter::increment(net_rsb.keep_alive_queue_timeout_total, diff);
Metrics::Counter::increment(net_rsb.keep_alive_queue_timeout_count);
}
Dbg(dbg_ctl_inactivity_cop_verbose, "ne: %p now: %" PRId64 " timeout at: %" PRId64 " timeout in: %" PRId64, ne,
ink_hrtime_to_sec(now), ne->next_inactivity_timeout_at, ne->inactivity_timeout_in);
ATS_PROBE6(net_inactivity_timeout, ne->get_fd(), now, ne->next_inactivity_timeout_at, ne->inactivity_timeout_in,
ne->is_default_inactivity_timeout() ? 1 : 0, ne->default_inactivity_timeout_in.load());
ne->callback(VC_EVENT_INACTIVITY_TIMEOUT, e);
} else if (ne->next_activity_timeout_at && ne->next_activity_timeout_at < now) {
Dbg(dbg_ctl_inactivity_cop_verbose, "active ne: %p now: %" PRId64 " timeout at: %" PRId64 " timeout in: %" PRId64, ne,
ink_hrtime_to_sec(now), ne->next_activity_timeout_at, ne->active_timeout_in);
ne->callback(VC_EVENT_ACTIVE_TIMEOUT, e);
}
}
// The cop_list is empty now.
// Let's reload the cop_list from open_list again.
forl_LL(NetEvent, ne, nh.open_list)
{
if (ne->get_thread() == this_ethread()) {
nh.cop_list.push(ne);
}
}
// NetHandler will remove NetEvent from cop_list if it is triggered.
// As the NetHandler runs, the number of NetEvents in the cop_list is decreasing.
// NetHandler runs 100 times maximum between InactivityCop runs.
// Therefore we don't have to check all the NetEvents as much as open_list.
// Cleanup the active and keep-alive queues periodically
nh.manage_active_queue(nullptr, true); // close any connections over the active timeout
nh.manage_keep_alive_queue();
return 0;
}
};
void
initialize_thread_for_net(EThread *thread)
{
NetHandler *nh = get_NetHandler(thread);
new (reinterpret_cast<ink_dummy_for_new *>(nh)) NetHandler();
new (reinterpret_cast<ink_dummy_for_new *>(get_PollCont(thread))) PollCont(thread->mutex, nh);
nh->mutex = new_ProxyMutex();
nh->thread = thread;
PollCont *pc = get_PollCont(thread);
PollDescriptor *pd = pc->pollDescriptor;
InactivityCop *inactivityCop = new InactivityCop(get_NetHandler(thread)->mutex);
int cop_freq = 1;
cop_freq = RecGetRecordInt("proxy.config.net.inactivity_check_frequency").value_or(0);
memcpy(&nh->config, &NetHandler::global_config, sizeof(NetHandler::global_config));
nh->configure_per_thread_values();
thread->schedule_every(inactivityCop, HRTIME_SECONDS(cop_freq));
thread->set_tail_handler(nh);
#if HAVE_EVENTFD
#if TS_USE_LINUX_IO_URING
auto ep = new IOUringEventIO();
ep->start(pd, IOUringContext::local_context());
#else
auto ep = new AsyncSignalEventIO();
ep->start(pd, thread->evfd, EVENTIO_READ);
#endif
#else
auto ep = new AsyncSignalEventIO();
ep->start(pd, thread->evpipe[0], EVENTIO_READ);
#endif
thread->ep = ep;
}