1/*
2 * Copyright 2016-present Facebook, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
19#include <atomic>
20#include <mutex>
21#include <utility>
22
23#include <folly/Likely.h>
24#include <folly/Portability.h>
25#include <folly/SharedMutex.h>
26#include <folly/functional/Invoke.h>
27
28namespace folly {
29
30template <typename Mutex, template <typename> class Atom = std::atomic>
31class basic_once_flag;
32
33// call_once
34//
35// Drop-in replacement for std::call_once.
36//
37// The libstdc++ implementation has two flaws:
38// * it lacks a fast path, and
39// * it deadlocks (in explicit violation of the standard) when invoked twice
40// with a given flag, and the callable passed to the first invocation throws.
41//
42// This implementation corrects both flaws.
43//
44// The tradeoff is a slightly larger once_flag struct at 8 bytes, vs 4 bytes
45// with libstdc++ on Linux/x64.
46//
47// Does not work with std::once_flag.
48//
49// mimic: std::call_once
50template <
51 typename Mutex,
52 template <typename> class Atom,
53 typename F,
54 typename... Args>
55FOLLY_ALWAYS_INLINE void
56call_once(basic_once_flag<Mutex, Atom>& flag, F&& f, Args&&... args) {
57 flag.call_once(std::forward<F>(f), std::forward<Args>(args)...);
58}
59
60// test_once
61//
62// Tests whether any invocation to call_once with the given flag has succeeded.
63//
64// May help with space usage in certain esoteric scenarios compared with caller
65// code tracking a separate and possibly-padded bool.
66//
67// Note: This is has no parallel in the std::once_flag interface.
68template <typename Mutex, template <typename> class Atom>
69FOLLY_ALWAYS_INLINE bool test_once(basic_once_flag<Mutex, Atom> const& flag) {
70 return flag.called_.load(std::memory_order_acquire);
71}
72
73// basic_once_flag
74//
75// The flag template to be used with call_once. Parameterizable by the mutex
76// type and atomic template. The mutex type is required to mimic std::mutex and
77// the atomic type is required to mimic std::atomic.
78template <typename Mutex, template <typename> class Atom>
79class basic_once_flag {
80 public:
81 constexpr basic_once_flag() noexcept = default;
82 basic_once_flag(const basic_once_flag&) = delete;
83 basic_once_flag& operator=(const basic_once_flag&) = delete;
84
85 private:
86 template <
87 typename Mutex_,
88 template <typename> class Atom_,
89 typename F,
90 typename... Args>
91 friend void call_once(basic_once_flag<Mutex_, Atom_>&, F&&, Args&&...);
92
93 template <typename Mutex_, template <typename> class Atom_>
94 friend bool test_once(basic_once_flag<Mutex_, Atom_> const& flag);
95
96 template <typename F, typename... Args>
97 FOLLY_ALWAYS_INLINE void call_once(F&& f, Args&&... args) {
98 if (LIKELY(called_.load(std::memory_order_acquire))) {
99 return;
100 }
101 call_once_slow(std::forward<F>(f), std::forward<Args>(args)...);
102 }
103
104 template <typename F, typename... Args>
105 FOLLY_NOINLINE void call_once_slow(F&& f, Args&&... args) {
106 std::lock_guard<Mutex> lock(mutex_);
107 if (called_.load(std::memory_order_relaxed)) {
108 return;
109 }
110 invoke(std::forward<F>(f), std::forward<Args>(args)...);
111 called_.store(true, std::memory_order_release);
112 }
113
114 Atom<bool> called_{false};
115 Mutex mutex_;
116};
117
118// once_flag
119//
120// The flag type to be used with call_once. An instance of basic_once_flag.
121//
122// Does not work with sd::call_once.
123//
124// mimic: std::once_flag
125using once_flag = basic_once_flag<SharedMutex>;
126
127} // namespace folly
128