metadata_recorder.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. // Copyright 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #ifndef BASE_PROFILER_METADATA_RECORDER_H_
  5. #define BASE_PROFILER_METADATA_RECORDER_H_
  6. #include <array>
  7. #include <atomic>
  8. #include <utility>
  9. #include "base/optional.h"
  10. #include "base/synchronization/lock.h"
  11. #include "base/thread_annotations.h"
  12. namespace base {
  13. // MetadataRecorder provides a data structure to store metadata key/value pairs
  14. // to be associated with samples taken by the sampling profiler. Whatever
  15. // metadata is present in this map when a sample is recorded is then associated
  16. // with the sample.
  17. //
  18. // Methods on this class are safe to call unsynchronized from arbitrary threads.
  19. //
  20. // This class was designed to read metadata from a single sampling thread and
  21. // write metadata from many Chrome threads within the same process. These other
  22. // threads might be suspended by the sampling thread at any time in order to
  23. // collect a sample.
  24. //
  25. // This class has a few notable constraints:
  26. //
  27. // A) If a lock that's required to read the metadata might be held while writing
  28. // the metadata, that lock must be acquirable *before* the thread is
  29. // suspended. Otherwise, the sampling thread might suspend the target thread
  30. // while it is holding the required lock, causing deadlock.
  31. //
  32. // Ramifications:
  33. //
  34. // - When retrieving items, lock acquisition (through
  35. // CreateMetadataProvider()) and actual item retrieval (through
  36. // MetadataProvider::GetItems()) are separate.
  37. //
  38. // B) We can't allocate data on the heap while reading the metadata items. This
  39. // is because, on many operating systems, there's a process-wide heap lock
  40. // that is held while allocating on the heap. If a thread is suspended while
  41. // holding this lock and the sampling thread then tries to allocate on the
  42. // heap to read the metadata, it will deadlock trying to acquire the heap
  43. // lock.
  44. //
  45. // Ramifications:
  46. //
  47. // - We hold and retrieve the metadata using a fixed-size array, which
  48. // allows readers to preallocate the data structure that we pass back
  49. // the metadata in.
  50. //
  51. // C) We shouldn't guard writes with a lock that also guards reads, since the
  52. // read lock is held from the time that the sampling thread requests that a
  53. // thread be suspended up to the time that the thread is resumed. If all
  54. // metadata writes block their thread during that time, we're very likely to
  55. // block all Chrome threads.
  56. //
  57. // Ramifications:
  58. //
  59. // - We use two locks to guard the metadata: a read lock and a write
  60. // lock. Only the write lock is required to write into the metadata, and
  61. // only the read lock is required to read the metadata.
  62. //
  63. // - Because we can't guard reads and writes with the same lock, we have to
  64. // face the possibility of writes occurring during a read. This is
  65. // especially problematic because there's no way to read both the key and
  66. // value for an item atomically without using mutexes, which violates
  67. // constraint A). If the sampling thread were to see the following
  68. // interleaving of reads and writes:
  69. //
  70. // * Reader thread reads key for slot 0
  71. // * Writer thread removes item at slot 0
  72. // * Writer thread creates new item with different key in slot 0
  73. // * Reader thread reads value for slot 0
  74. //
  75. // then the reader would see an invalid value for the given key. Because
  76. // of this possibility, we keep slots reserved for a specific key even
  77. // after that item has been removed. We reclaim these slots on a
  78. // best-effort basis during writes when the metadata recorder has become
  79. // sufficiently full and we can acquire the read lock.
  80. //
  81. // - We use state stored in atomic data types to ensure that readers and
  82. // writers are synchronized about where data should be written to and
  83. // read from. We must use atomic data types to guarantee that there's no
  84. // instruction during a write after which the recorder is in an
  85. // inconsistent state that might yield garbage data for a reader.
  86. //
  87. // Here are a few of the many states the recorder can be in:
  88. //
  89. // - No thread is using the recorder.
  90. //
  91. // - A single writer is writing into the recorder without a simultaneous read.
  92. // The write will succeed.
  93. //
  94. // - A reader is reading from the recorder without a simultaneous write. The
  95. // read will succeed.
  96. //
  97. // - Multiple writers attempt to write into the recorder simultaneously. All
  98. // writers but one will block because only one can hold the write lock.
  99. //
  100. // - A writer is writing into the recorder, which hasn't reached the threshold
  101. // at which it will try to reclaim inactive slots. The writer won't try to
  102. // acquire the read lock to reclaim inactive slots. The reader will therefore
  103. // be able to immediately acquire the read lock, suspend the target thread,
  104. // and read the metadata.
  105. //
  106. // - A writer is writing into the recorder, the recorder has reached the
  107. // threshold at which it needs to reclaim inactive slots, and the writer
  108. // thread is now in the middle of reclaiming those slots when a reader
  109. // arrives. The reader will try to acquire the read lock before suspending the
  110. // thread but will block until the writer thread finishes reclamation and
  111. // releases the read lock. The reader will then be able to acquire the read
  112. // lock and suspend the target thread.
  113. //
  114. // - A reader is reading the recorder when a writer attempts to write. The write
  115. // will be successful. However, if the writer deems it necessary to reclaim
  116. // inactive slots, it will skip doing so because it won't be able to acquire
  117. // the read lock.
  118. class BASE_EXPORT MetadataRecorder {
  119. public:
  120. MetadataRecorder();
  121. virtual ~MetadataRecorder();
  122. MetadataRecorder(const MetadataRecorder&) = delete;
  123. MetadataRecorder& operator=(const MetadataRecorder&) = delete;
  124. struct BASE_EXPORT Item {
  125. Item(uint64_t name_hash, Optional<int64_t> key, int64_t value);
  126. Item();
  127. Item(const Item& other);
  128. Item& operator=(const Item& other);
  129. // The hash of the metadata name, as produced by HashMetricName().
  130. uint64_t name_hash;
  131. // The key if specified when setting the item.
  132. Optional<int64_t> key;
  133. // The value of the metadata item.
  134. int64_t value;
  135. };
  136. static constexpr size_t MAX_METADATA_COUNT = 50;
  137. typedef std::array<Item, MAX_METADATA_COUNT> ItemArray;
  138. // Sets a value for a (|name_hash|, |key|) pair, overwriting any value
  139. // previously set for the pair. Nullopt keys are treated as just another key
  140. // state for the purpose of associating values.
  141. void Set(uint64_t name_hash, Optional<int64_t> key, int64_t value);
  142. // Removes the item with the specified name hash and optional key. Has no
  143. // effect if such an item does not exist.
  144. void Remove(uint64_t name_hash, Optional<int64_t> key);
  145. // An object that provides access to a MetadataRecorder's items and holds the
  146. // necessary exclusive read lock until the object is destroyed. Reclaiming of
  147. // inactive slots in the recorder can't occur while this object lives, so it
  148. // should be created as soon before it's needed as possible and released as
  149. // soon as possible.
  150. //
  151. // This object should be created *before* suspending the target thread and
  152. // destroyed after resuming the target thread. Otherwise, that thread might be
  153. // suspended while reclaiming inactive slots and holding the read lock, which
  154. // would cause the sampling thread to deadlock.
  155. //
  156. // Example usage:
  157. //
  158. // MetadataRecorder r;
  159. // base::MetadataRecorder::ItemArray arr;
  160. // size_t item_count;
  161. // ...
  162. // {
  163. // MetadtaRecorder::MetadataProvider provider;
  164. // item_count = provider.GetItems(arr);
  165. // }
  166. class SCOPED_LOCKABLE BASE_EXPORT MetadataProvider {
  167. public:
  168. // Acquires an exclusive read lock on the metadata recorder which is held
  169. // until the object is destroyed.
  170. explicit MetadataProvider(MetadataRecorder* metadata_recorder)
  171. EXCLUSIVE_LOCK_FUNCTION(metadata_recorder_->read_lock_);
  172. ~MetadataProvider() UNLOCK_FUNCTION();
  173. MetadataProvider(const MetadataProvider&) = delete;
  174. MetadataProvider& operator=(const MetadataProvider&) = delete;
  175. // Retrieves the first |available_slots| items in the metadata recorder and
  176. // copies them into |items|, returning the number of metadata items that
  177. // were copied. To ensure that all items can be copied, |available slots|
  178. // should be greater than or equal to |MAX_METADATA_COUNT|. Requires
  179. // NO_THREAD_SAFETY_ANALYSIS because clang's analyzer doesn't understand the
  180. // cross-class locking used in this class' implementation.
  181. size_t GetItems(ItemArray* const items) const NO_THREAD_SAFETY_ANALYSIS;
  182. private:
  183. const MetadataRecorder* const metadata_recorder_;
  184. base::AutoLock auto_lock_;
  185. };
  186. private:
  187. // TODO(charliea): Support large quantities of metadata efficiently.
  188. struct ItemInternal {
  189. ItemInternal();
  190. ~ItemInternal();
  191. // Indicates whether the metadata item is still active (i.e. not removed).
  192. //
  193. // Requires atomic reads and writes to avoid word tearing when reading and
  194. // writing unsynchronized. Requires acquire/release semantics to ensure that
  195. // the other state in this struct is visible to the reading thread before it
  196. // is marked as active.
  197. std::atomic<bool> is_active{false};
  198. // Neither name_hash or key require atomicity or memory order constraints
  199. // because no reader will attempt to read them mid-write. Specifically,
  200. // readers wait until |is_active| is true to read them. Because |is_active|
  201. // is always stored with a memory_order_release fence, we're guaranteed that
  202. // |name_hash| and |key| will be finished writing before |is_active| is set
  203. // to true.
  204. uint64_t name_hash;
  205. Optional<int64_t> key;
  206. // Requires atomic reads and writes to avoid word tearing when updating an
  207. // existing item unsynchronized. Does not require acquire/release semantics
  208. // because we rely on the |is_active| acquire/release semantics to ensure
  209. // that an item is fully created before we attempt to read it.
  210. std::atomic<int64_t> value;
  211. };
  212. // Attempts to free slots in the metadata map that are currently allocated to
  213. // inactive items. May fail silently if the read lock is already held, in
  214. // which case no slots will be freed. Returns the number of item slots used
  215. // after the reclamation.
  216. size_t TryReclaimInactiveSlots(size_t item_slots_used)
  217. EXCLUSIVE_LOCKS_REQUIRED(write_lock_) LOCKS_EXCLUDED(read_lock_);
  218. // Updates item_slots_used_ to reflect the new item count and returns the
  219. // number of item slots used after the reclamation.
  220. size_t ReclaimInactiveSlots(size_t item_slots_used)
  221. EXCLUSIVE_LOCKS_REQUIRED(write_lock_)
  222. EXCLUSIVE_LOCKS_REQUIRED(read_lock_);
  223. size_t GetItems(ItemArray* const items) const
  224. EXCLUSIVE_LOCKS_REQUIRED(read_lock_);
  225. // Metadata items that the recorder has seen. Rather than implementing the
  226. // metadata recorder as a dense array, we implement it as a sparse array where
  227. // removed metadata items keep their slot with their |is_active| bit set to
  228. // false. This avoids race conditions caused by reusing slots that might
  229. // otherwise cause mismatches between metadata name hashes and values.
  230. //
  231. // For the rationale behind this design (along with others considered), see
  232. // https://docs.google.com/document/d/18shLhVwuFbLl_jKZxCmOfRB98FmNHdKl0yZZZ3aEO4U/edit#.
  233. std::array<ItemInternal, MAX_METADATA_COUNT> items_;
  234. // The number of item slots used in the metadata map.
  235. //
  236. // Requires atomic reads and writes to avoid word tearing when reading and
  237. // writing unsynchronized. Requires acquire/release semantics to ensure that a
  238. // newly-allocated slot is fully initialized before the reader becomes aware
  239. // of its existence.
  240. std::atomic<size_t> item_slots_used_{0};
  241. // The number of item slots occupied by inactive items.
  242. size_t inactive_item_count_ GUARDED_BY(write_lock_) = 0;
  243. // A lock that guards against multiple threads trying to manipulate items_,
  244. // item_slots_used_, or inactive_item_count_ at the same time.
  245. base::Lock write_lock_;
  246. // A lock that guards against a reader trying to read items_ while inactive
  247. // slots are being reclaimed.
  248. base::Lock read_lock_;
  249. };
  250. } // namespace base
  251. #endif // BASE_PROFILER_METADATA_RECORDER_H_