suppress warnings in tests for deprecated functions
[folly.git] / folly / detail / MemoryIdler.h
1 /*
2  * Copyright 2014-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 <chrono>
21
22 #include <folly/detail/Futex.h>
23 #include <folly/hash/Hash.h>
24 #include <folly/synchronization/AtomicStruct.h>
25 #include <folly/system/ThreadId.h>
26
27 namespace folly { namespace detail {
28
29 /// MemoryIdler provides helper routines that allow routines to return
30 /// some assigned memory resources back to the system.  The intended
31 /// use is that when a thread is waiting for a long time (perhaps it
32 /// is in a LIFO thread pool and hasn't been needed for a long time)
33 /// it should release its thread-local malloc caches (both jemalloc and
34 /// tcmalloc use these for better performance) and unmap the stack pages
35 /// that contain no useful data.
36 struct MemoryIdler {
37
38   /// Returns memory from thread-local allocation pools to the global
39   /// pool, if we know how to for the current malloc implementation.
40   /// jemalloc is supported.
41   static void flushLocalMallocCaches();
42
43
44   enum {
45     /// This value is a tradeoff between reclaiming memory and triggering
46     /// a page fault immediately on wakeup.  Note that the actual unit
47     /// of idling for the stack is pages, so the actual stack that
48     /// will be available on wakeup without a page fault is between
49     /// kDefaultStackToRetain and kDefaultStackToRetain + PageSize -
50     /// 1 bytes.
51     kDefaultStackToRetain = 1024,
52   };
53
54   /// Uses madvise to discard the portion of the thread's stack that
55   /// currently doesn't hold any data, trying to ensure that no page
56   /// faults will occur during the next retain bytes of stack allocation
57   static void unmapUnusedStack(size_t retain = kDefaultStackToRetain);
58
59
60   /// The system-wide default for the amount of time a blocking
61   /// thread should wait before reclaiming idle memory.  Set this to
62   /// Duration::max() to never wait.  The default value is 5 seconds.
63   /// Endpoints using this idle timeout might randomly wait longer to
64   /// avoid synchronizing their flushes.
65   static AtomicStruct<std::chrono::steady_clock::duration> defaultIdleTimeout;
66
67   /// Selects a timeout pseudo-randomly chosen to be between
68   /// idleTimeout and idleTimeout * (1 + timeoutVariationFraction), to
69   /// smooth out the behavior in a bursty system
70   template <typename IdleTime = std::chrono::steady_clock::duration>
71   static IdleTime getVariationTimeout(
72       IdleTime const& idleTimeout =
73           defaultIdleTimeout.load(std::memory_order_acquire),
74       float timeoutVariationFrac = 0.5) {
75     if (idleTimeout <= IdleTime::zero() || timeoutVariationFrac <= 0) {
76       return idleTimeout;
77     }
78
79     // hash the pthread_t and the time to get the adjustment
80     // Standard hash func isn't very good, so bit mix the result
81     uint64_t h = folly::hash::twang_mix64(folly::hash::hash_combine(
82         getCurrentThreadID(),
83         std::chrono::system_clock::now().time_since_epoch().count()));
84
85     // multiplying the duration by a floating point doesn't work, grr
86     auto extraFrac =
87         timeoutVariationFrac / std::numeric_limits<uint64_t>::max() * h;
88     auto tics = uint64_t(idleTimeout.count() * (1 + extraFrac));
89     return IdleTime(tics);
90   }
91
92   /// Equivalent to fut.futexWait(expected, waitMask), but calls
93   /// flushLocalMallocCaches() and unmapUnusedStack(stackToRetain)
94   /// after idleTimeout has passed (if it has passed). Internally uses
95   /// fut.futexWait and fut.futexWaitUntil. The actual timeout will be
96   /// pseudo-randomly chosen to be between idleTimeout and idleTimeout *
97   /// (1 + timeoutVariationFraction), to smooth out the behavior in a
98   /// system with bursty requests. The default is to wait up to 50%
99   /// extra, so on average 25% extra.
100   template <
101       template <typename> class Atom,
102       typename IdleTime = std::chrono::steady_clock::duration>
103   static FutexResult futexWait(
104       Futex<Atom>& fut,
105       uint32_t expected,
106       uint32_t waitMask = -1,
107       IdleTime const& idleTimeout =
108           defaultIdleTimeout.load(std::memory_order_acquire),
109       size_t stackToRetain = kDefaultStackToRetain,
110       float timeoutVariationFrac = 0.5) {
111     FutexResult pre;
112     if (futexWaitPreIdle(
113             pre,
114             fut,
115             expected,
116             std::chrono::steady_clock::time_point::max(),
117             waitMask,
118             idleTimeout,
119             stackToRetain,
120             timeoutVariationFrac)) {
121       return pre;
122     }
123     return fut.futexWait(expected, waitMask);
124   }
125
126   /// Equivalent to fut.futexWaitUntil(expected, deadline, waitMask), but
127   /// calls flushLocalMallocCaches() and unmapUnusedStack(stackToRetain)
128   /// after idleTimeout has passed (if it has passed). Internally uses
129   /// fut.futexWaitUntil. The actual timeout will be pseudo-randomly
130   /// chosen to be between idleTimeout and idleTimeout *
131   /// (1 + timeoutVariationFraction), to smooth out the behavior in a
132   /// system with bursty requests. The default is to wait up to 50%
133   /// extra, so on average 25% extra.
134   template <
135       template <typename> class Atom,
136       typename Deadline,
137       typename IdleTime = std::chrono::steady_clock::duration>
138   static FutexResult futexWaitUntil(
139       Futex<Atom>& fut,
140       uint32_t expected,
141       Deadline const& deadline,
142       uint32_t waitMask = -1,
143       IdleTime const& idleTimeout =
144           defaultIdleTimeout.load(std::memory_order_acquire),
145       size_t stackToRetain = kDefaultStackToRetain,
146       float timeoutVariationFrac = 0.5) {
147     FutexResult pre;
148     if (futexWaitPreIdle(
149             pre,
150             fut,
151             expected,
152             deadline,
153             waitMask,
154             idleTimeout,
155             stackToRetain,
156             timeoutVariationFrac)) {
157       return pre;
158     }
159     return fut.futexWaitUntil(expected, deadline, waitMask);
160   }
161
162  private:
163   template <
164       template <typename> class Atom,
165       typename Deadline,
166       typename IdleTime>
167   static bool futexWaitPreIdle(
168       FutexResult& _ret,
169       Futex<Atom>& fut,
170       uint32_t expected,
171       Deadline const& deadline,
172       uint32_t waitMask,
173       IdleTime idleTimeout,
174       size_t stackToRetain,
175       float timeoutVariationFrac) {
176     // idleTimeout < 0 means no flush behavior
177     if (idleTimeout < IdleTime::zero()) {
178       return false;
179     }
180
181     // idleTimeout == 0 means flush immediately, without variation
182     // idleTimeout > 0 means flush after delay, with variation
183     if (idleTimeout > IdleTime::zero()) {
184       idleTimeout = std::max(
185           IdleTime::zero(),
186           getVariationTimeout(idleTimeout, timeoutVariationFrac));
187     }
188     if (idleTimeout > IdleTime::zero()) {
189       auto idleDeadline = Deadline::clock::now() + idleTimeout;
190       if (idleDeadline < deadline) {
191         while (true) {
192           auto rv = fut.futexWaitUntil(expected, idleDeadline, waitMask);
193           if (rv == FutexResult::TIMEDOUT) {
194             break;
195           }
196           // finished before timeout hit, no flush
197           _ret = rv;
198           return true;
199         }
200       }
201     }
202
203     // flush, then wait
204     flushLocalMallocCaches();
205     unmapUnusedStack(stackToRetain);
206     return false;
207   }
208 };
209
210 } // namespace detail
211 } // namespace folly