/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed 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. */ #pragma once #include <atomic> #include <limits> #include <folly/GLog.h> #include <folly/IntrusiveList.h> #include <folly/Optional.h> #include <folly/lang/Align.h> #include <folly/synchronization/DistributedMutex.h> #include <folly/synchronization/SaturatingSemaphore.h> #include <folly/synchronization/WaitOptions.h> #include <folly/synchronization/detail/Spin.h> namespace folly { class ThrottledLifoSemTestHelper; /** * ThrottledLifoSem is a semaphore that can wait up to a configurable * wakeUpInterval before waking up a sleeping waiter. This gives an opportunity * to new waiters to consume the posted values, avoiding the overhead of waking * up a thread when the already active threads can consume values fast enough, * effectively allowing to batch the work. The semaphore is "throttled" because * sleeping waiters can be awoken at most once every wakeUpInterval. * * The motivating example is the task queue of a thread pool, where if a burst * of very small tasks (order of hundreds of nanoseconds each) is enqueued, it * may be beneficial to have a single thread process all the tasks sequentially, * rather than pay for the cost of waking up all the idle threads. However, if * nothing consumes the posted value, more waiters are awoken, each after a * wakeUpInterval delay. Sleeping waiters are awoken in LIFO order, consistently * with LifoSem. * * This is realized by having at most one sleeping waiter being in a "waking" * state: when such waiter is awoken, it immediately goes to sleep until last * wakeup time + wakeUpInterval to allow more value to accumulate and other * threads to consume it. If at the end of the sleep no value is left, as it was * consumed by other thread, the waking waiter can go back to sleep. Otherwise, * a new waiter becomes waking (if necessary) and control is returned to the * caller. * * Note that since wakeUpInterval is relative to the last awake time, in the * regime where post()s are spaced at least wakeUpInterval apart the waiters are * always awoken immediately, so there is no added latency in this case. Also, * when there is more outstanding value than waiters (for example, the thread * pool is saturated), there is no added latency compared to LifoSem. * * The interface is a subset of LifoSem, with semantics compatible with it. Only * the minimal subset needed to support task queues is currently implemented, * but the interface can be extended as needed. */ class ThrottledLifoSem { … }; } // namespace folly