folly/folly/synchronization/ThrottledLifoSem.h

/*
 * 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