2828#include < chrono>
2929#include < cmath>
3030#include < coroutine>
31+ #include < cstdlib>
3132#include < exception>
3233#include < filesystem>
3334#include < fstream>
@@ -4523,6 +4524,64 @@ unsigned smp::adjust_max_networking_aio_io_control_blocks(unsigned network_iocbs
45234524 return network_iocbs;
45244525}
45254526
4527+ // Series 6+ kernels saw the new EEVDF scheduler introduced and heavy code churn
4528+ // in that
4529+ //
4530+ // [6.12, 7.1) has one or many bugs that cause the scheduler to completely
4531+ // deadlock the system if there is a busy spinning thread (like seastar).
4532+ // The fixes have been backported to the LTS version 6.12 and 6.18
4533+ // so later series are exempted again.
4534+ // The root trigger for this is having a (root) cgroup with raised cpu weight
4535+ // which makes them directly compete with kernel threads. We have seen issues
4536+ // because of this repeatedly in the past.
4537+ // This is reliably observed when running in kubernetes which bumps its
4538+ // root/top-level cgroup cpu weight by some (weird) formula.
4539+ // While this scenario could theoretically also happen otherwise we in practice
4540+ // only see it in kubernetes (even changed systemd services don't touch the root
4541+ // slice) and hence we limit our detection to kubernetes. An alternative would
4542+ // be to just check the cpu weight of our top most cgroup slice. However, this
4543+ // fails on k8s (and elsewhere) as they use private cgroup namespaces which
4544+ // makes the pod only see its own weight.
4545+ static bool k8s_eevdf_deadlock () {
4546+ const auto ku = internal::kernel_uname ();
4547+ if (!ku.whitelisted ({" 6.12" }) || ku.whitelisted ({" 7.1" })) {
4548+ return false ;
4549+ }
4550+ // LTS series with the fixes backported. Note a three-component version only
4551+ // matches the same version.patchlevel series, so these exemptions don't
4552+ // apply to any other series.
4553+ if (ku.whitelisted ({" 6.12.94" , " 6.18.38" })) {
4554+ return false ;
4555+ }
4556+ return std::getenv (" KUBERNETES_SERVICE_HOST" ) != nullptr ;
4557+ }
4558+
4559+ // We have repeatedly seen issues where seastar's shard-pinning and busy
4560+ // spinning has caused scheduling issues with other kernel threads (most often
4561+ // the DIO thread).
4562+ // Usually this often requires an additional ingredient such as kubernetes
4563+ // or scheduling tuning (e.g.: from tuned - handled in a different place).
4564+ // If we detect these conditions and affected kernel versions, we automatically
4565+ // enable overdirectly provisioning to avoid the issue.
4566+ static bool should_auto_overprovision (const reactor_options& reactor_opts) {
4567+ // Treat --poll-aio as a cop-out. Alternative would be to make
4568+ // --overprovisioned a proper tri-state but there is a risk people have just
4569+ // set it to false by default explicitly.
4570+ // --poll-aio is safer from the POV as you can't set it with a special flag
4571+ // via rpk (like you can do with overprovisioned)
4572+ if (!reactor_opts.poll_aio .defaulted () && reactor_opts.poll_aio .get_value ()) {
4573+ return false ;
4574+ }
4575+
4576+ if (k8s_eevdf_deadlock ()) {
4577+ seastar_logger.info (" Automatically enabling --overprovisioned: running in k8s on "
4578+ " kernel versions with known scheduling issues" );
4579+ return true ;
4580+ }
4581+
4582+ return false ;
4583+ }
4584+
45264585void smp::configure (const smp_options& smp_opts, const reactor_options& reactor_opts)
45274586{
45284587#ifdef SEASTAR_TLS_DUAL_BACKEND
@@ -4535,7 +4594,12 @@ void smp::configure(const smp_options& smp_opts, const reactor_options& reactor_
45354594 internal::crypto::set_provider (reactor_opts.crypto_provider .get_selected_candidate ()());
45364595#endif
45374596
4538- bool use_transparent_hugepages = !reactor_opts.overprovisioned ;
4597+ bool overprovisioned = bool (reactor_opts.overprovisioned );
4598+ if (!overprovisioned && should_auto_overprovision (reactor_opts)) {
4599+ overprovisioned = true ;
4600+ }
4601+
4602+ bool use_transparent_hugepages = !overprovisioned;
45394603
45404604#ifndef SEASTAR_NO_EXCEPTION_HACK
45414605 if (smp_opts.enable_glibc_exception_scaling_workaround .get_value ()) {
@@ -4569,7 +4633,7 @@ void smp::configure(const smp_options& smp_opts, const reactor_options& reactor_
45694633 _using_dpdk = native_stack && native_stack->dpdk_pmd ;
45704634#endif
45714635 auto thread_affinity = smp_opts.thread_affinity .get_value ();
4572- if (reactor_opts. overprovisioned
4636+ if (overprovisioned
45734637 && smp_opts.thread_affinity .defaulted ()) {
45744638 thread_affinity = false ;
45754639 }
@@ -4583,7 +4647,7 @@ void smp::configure(const smp_options& smp_opts, const reactor_options& reactor_
45834647
45844648 resource::configuration rc;
45854649
4586- rc.overcommit = reactor_opts. overprovisioned ;
4650+ rc.overcommit = overprovisioned;
45874651
45884652 smp::_tmain = std::this_thread::get_id ();
45894653 resource::cpuset cpu_set = get_current_cpuset ();
@@ -4743,10 +4807,10 @@ void smp::configure(const smp_options& smp_opts, const reactor_options& reactor_
47434807
47444808 reactor_config reactor_cfg = {
47454809 .task_quota = std::chrono::duration_cast<sched_clock::duration>(reactor_opts.task_quota_ms .get_value () * 1ms),
4746- .max_poll_time = [&reactor_opts ] () -> std::chrono::nanoseconds {
4810+ .max_poll_time = [&] () -> std::chrono::nanoseconds {
47474811 if (reactor_opts.poll_mode ) {
47484812 return std::chrono::nanoseconds::max ();
4749- } else if (reactor_opts. overprovisioned && reactor_opts.idle_poll_time_us .defaulted ()) {
4813+ } else if (overprovisioned && reactor_opts.idle_poll_time_us .defaulted ()) {
47504814 return 0us;
47514815 } else {
47524816 return reactor_opts.idle_poll_time_us .get_value () * 1us;
@@ -4761,7 +4825,7 @@ void smp::configure(const smp_options& smp_opts, const reactor_options& reactor_
47614825 .max_task_backlog = reactor_opts.max_task_backlog .get_value (),
47624826 .strict_o_direct = !reactor_opts.relaxed_dma ,
47634827 .bypass_fsync = reactor_opts.unsafe_bypass_fsync .get_value (),
4764- .no_poll_aio = !reactor_opts.poll_aio .get_value () || (reactor_opts.poll_aio .defaulted () && reactor_opts. overprovisioned ),
4828+ .no_poll_aio = !reactor_opts.poll_aio .get_value () || (reactor_opts.poll_aio .defaulted () && overprovisioned),
47654829 .aio_nowait_works = reactor_opts.linux_aio_nowait .defaulted () ? std::optional<bool >(std::nullopt ) : std::optional<bool >(reactor_opts.linux_aio_nowait .get_value ()), // Mixed in with filesystem-provided values later
47664830 .abort_on_too_long_task_queue = reactor_opts.abort_on_too_long_task_queue .get_value (),
47674831 };
0 commit comments