folly/folly/observer/Observer.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 <memory>

#include <folly/SharedMutex.h>
#include <folly/ThreadLocal.h>
#include <folly/experimental/observer/detail/Core.h>
#include <folly/observer/Observer-pre.h>

namespace folly {
namespace observer {

/**
 * Observer - a library which lets you create objects which track updates of
 * their dependencies and get re-computed when any of the dependencies changes.
 *
 *
 * Given an Observer, you can get a snapshot of the current version of the
 * object it holds:
 *
 *   Observer<int> myObserver = ...;
 *   Snapshot<int> mySnapshot = myObserver.getSnapshot();
 * or simply
 *   Snapshot<int> mySnapshot = *myObserver;
 *
 * Snapshot will hold a view of the object, even if object in the Observer
 * gets updated.
 *
 *
 * What makes Observer powerful is its ability to track updates to other
 * Observers. Imagine we have two separate Observers A and B which hold
 * integers.
 *
 *   Observer<int> observerA = ...;
 *   Observer<int> observerB = ...;
 *
 * To compute a sum of A and B we can create a new Observer which would track
 * updates to A and B and re-compute the sum only when necessary.
 *
 *   Observer<int> sumObserver = makeObserver(
 *       [observerA, observerB] {
 *         int a = **observerA;
 *         int b = **observerB;
 *         return a + b;
 *       });
 *
 *   int sum = **sumObserver;
 *
 * Notice that a + b will be only called when either a or b is changed. Getting
 * a snapshot from sumObserver won't trigger any re-computation.
 *
 * Getting an Observer snapshot involves acquiring a shared_ptr, which can be
 * expensive, especially if several threads do so concurrently. If the cost of
 * getSnapshot() is noticeable, alternative Observer implementations are
 * available, offering different trade-offs:
 *
 * - If T is a type for which std::atomic<T> is lock-free (all word-sized PODs
 *   for example), AtomicObserver and ReadMostlyAtomicObserver offer the best
 *   performance at no additional memory cost.
 *
 * - TLObserver stores a thread-local snapshot, so that it can be accessed
 *   without synchronization (except when it needs updating). This however can
 *   consume significant amounts of memory by stranding old snapshots in threads
 *   that do not access, and thus refresh, the observer.
 *
 * - HazptrObserver uses hazard pointers to protect the snapshot, which offer
 *   high read scalability and low cost, but the snapshot should be held as
 *   little as possible and should not cross coroutine suspension points.
 *
 * - ReadMostlyTLObserver returns a snapshot that can be used like a regular
 *   shared_ptr. Scalability and cost are comparable to HazptrObserver, but the
 *   snapshots can be held for arbitrary time. Memory cost is a small constant
 *   for each thread that acquires a snapshot.
 *
 * - CoreCachedObserver can be used if a std::shared_ptr<T> is strictly
 *   required. Read scalability is comparable to the previous options, but cost
 *   is moderately higher. Memory cost is a small constant for each CPU in the
 *   system.
 *
 * See ObserverCreator class if you want to wrap any existing subscription API
 * in an Observer object.
 */
template <typename T>
class Observer;

/**
 * An AtomicObserver provides read-optimized caching for an Observer using
 * `std::atomic`. Reading only requires atomic loads unless the cached value
 * is stale. If the cache needs to be refreshed, a mutex is used to
 * synchronize the update. This avoids creating a shared_ptr for every read.
 *
 * AtomicObserver models CopyConstructible and MoveConstructible. Copying or
 * moving simply invalidates the cache.
 *
 * AtomicObserver is ideal when there are lots of reads on a trivially-copyable
 * type. if `std::atomic<T>` is not possible but you still want to optimize
 * reads, consider a TLObserver.
 *
 *   Observer<int> observer = ...;
 *   AtomicObserver<int> atomicObserver(observer);
 *   auto value = *atomicObserver;
 */
template <typename T>
class AtomicObserver;

/**
 * A TLObserver provides read-optimized caching for an Observer using
 * thread-local storage. This avoids creating a shared_ptr for every read.
 *
 * The functionality is similar to that of AtomicObserver except it allows types
 * that don't support atomics. If possible, use AtomicObserver instead.
 *
 * TLObserver can consume significant amounts of memory if accessed from many
 * threads. The problem is exacerbated if you chain several TLObservers.
 * Therefore, TLObserver should be used sparingly.
 *
 *   Observer<int> observer = ...;
 *   TLObserver<int> tlObserver(observer);
 *   auto& snapshot = *tlObserver;
 */
template <typename T>
class TLObserver;

/**
 * A ReadMostlyAtomicObserver guarantees that reading is exactly one relaxed
 * atomic load and a read from a thread local bool. Like AtomicObserver, the
 * value is cached using `std::atomic`.  However, there is no version check when
 * reading which means that the cached value may be out-of-date with the
 * Observer value. The cached value will be updated asynchronously in a
 * background thread.
 *
 * When get() is called from makeObserver, the underlying observer is directly
 * snapshotted to ensure dependent observers have current values and capture
 * dependencies.
 *
 * ReadMostlyAtomicObserver is ideal for fastest possible reads on a
 * trivially-copyable type when a slightly out-of-date value will suffice. It is
 * perfect for very frequent reads coupled with very infrequent writes.
 *
 *   Observer<int> observer = ...;
 *   ReadMostlyAtomicObserver<int> atomicObserver(observer);
 *   auto value = *atomicObserver;
 */
template <typename T>
class ReadMostlyAtomicObserver;

template <typename T>
class Snapshot {};

class CallbackHandle {};

template <typename Observable, typename Traits>
class ObserverCreator;

template <typename T>
class Observer {};

template <typename T>
Observer<T> unwrap(Observer<T>);

template <typename T>
Observer<T> unwrapValue(Observer<T>);

template <typename T>
Observer<T> unwrap(Observer<Observer<T>>);

template <typename T>
Observer<T> unwrapValue(Observer<Observer<T>>);

/**
 * makeObserver(...) creates a new Observer<T> object given a functor to
 * compute it. The functor can return T or std::shared_ptr<const T>.
 *
 * makeObserver(...) blocks until the initial version of Observer is computed.
 * If creator functor fails (throws or returns a nullptr) during this first
 * call, the exception is re-thrown by makeObserver(...).
 *
 * For all subsequent updates if creator functor fails (throws or returs a
 * nullptr), the Observer (and all its dependents) is not updated.
 */
template <typename F>
Observer<observer_detail::ResultOf<F>> makeObserver(F&& creator);

template <typename F>
Observer<observer_detail::ResultOfUnwrapSharedPtr<F>> makeObserver(F&& creator);

template <typename F>
Observer<observer_detail::ResultOfUnwrapObserver<F>> makeObserver(F&& creator);

/**
 * The returned Observer will proxy updates from the input observer, but will
 * skip updates that contain the same (according to operator==) value even if
 * the actual object in the update is different.
 */
template <typename T>
Observer<T> makeValueObserver(Observer<T> observer);

/**
 * A more efficient short-cut for makeValueObserver(makeObserver(...)).
 */
template <typename F>
Observer<observer_detail::ResultOf<F>> makeValueObserver(F&& creator);

template <typename F>
Observer<observer_detail::ResultOfUnwrapSharedPtr<F>> makeValueObserver(
    F&& creator);

/**
 * The returned Observer will never update and always return the passed value.
 */
template <typename T>
Observer<T> makeStaticObserver(T value);

template <typename T>
Observer<T> makeStaticObserver(std::shared_ptr<T> value);

template <typename T>
class AtomicObserver {};

template <typename T>
class TLObserver {};

template <typename T>
class ReadMostlyAtomicObserver {};

/**
 * Same as makeObserver(...), but creates AtomicObserver.
 */
template <typename T>
AtomicObserver<T> makeAtomicObserver(Observer<T> observer) {}

template <typename F>
auto makeAtomicObserver(F&& creator) {}

/**
 * Same as makeObserver(...), but creates TLObserver.
 */
template <typename T>
TLObserver<T> makeTLObserver(Observer<T> observer) {}

template <typename F>
auto makeTLObserver(F&& creator) {}

/**
 * Same as makeObserver(...), but creates ReadMostlyAtomicObserver.
 */
template <typename T>
ReadMostlyAtomicObserver<T> makeReadMostlyAtomicObserver(Observer<T> observer) {}

template <typename F>
auto makeReadMostlyAtomicObserver(F&& creator) {}

template <typename T, bool CacheInThreadLocal>
struct ObserverTraits {};

ObserverTraits<T, false>;

ObserverTraits<T, true>;

ObserverT;
} // namespace observer
} // namespace folly

#include <folly/observer/Observer-inl.h>