OR-Tools  9.6
update_trackers.h
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 #ifndef OR_TOOLS_MATH_OPT_STORAGE_UPDATE_TRACKERS_H_
15 #define OR_TOOLS_MATH_OPT_STORAGE_UPDATE_TRACKERS_H_
16 
17 #include <algorithm>
18 #include <atomic>
19 #include <iterator>
20 #include <memory>
21 #include <utility>
22 #include <vector>
23 
24 #include "absl/base/thread_annotations.h"
25 #include "absl/container/flat_hash_set.h"
26 #include "absl/synchronization/mutex.h"
27 #include "absl/log/check.h"
28 #include "ortools/base/stl_util.h"
31 
33 
34 // Manager the collection of update trackers for ModelStorage.
35 //
36 // The Data type is the type of data associated with trackers.
37 //
38 // This class makes sure it is possible to iterate on update trackers for
39 // ModelStorage modifications without having to hold a mutex. It does that by
40 // delaying additions & removals so that they are only applied when we need to
41 // iterate. This enables adding or removing trackers concurrently from multiple
42 // threads.
43 template <typename Data>
45  public:
46  // A pair (tracker_id, tracker_data).
47  using IdDataPair = std::pair<UpdateTrackerId, std::unique_ptr<Data>>;
48 
49  // Adds a new tracker. The args are forwarded to the constructor of the Data
50  // type.
51  //
52  // The actual addition is delayed to the next call of GetUpdatedTrackers().
53  //
54  // Thread-safety: this method is safe to be called from multiple threads at
55  // the same time.
56  template <typename... T>
57  UpdateTrackerId NewUpdateTracker(T&&... args);
58 
59  // Removes an update tracker.
60  //
61  // The actual removal is delayed to the next call of GetUpdatedTrackers().
62  //
63  // Thread-safety: this method is safe to be called from multiple threads at
64  // the same time. Since the update of vector returned by GetUpdatedTrackers()
65  // is delayed it is safe to iterate on it while this method is called.
66  //
67  // Complexity: O(n), n is the number of trackers. The number of update
68  // trackers should be small, if it grows to a point where this is an issue
69  // this class can easily be made more efficient (O(lg n) or better).
70  void DeleteUpdateTracker(UpdateTrackerId update_tracker);
71 
72  // Apply pending additions/deletions and return the trackers.
73  //
74  // Note that this function returns a const-reference to the vector of
75  // trackers, but since it contains (unique-)pointers on the trackers' Data,
76  // only the pointers are const not the pointee. It is thus possible (and
77  // expected) that callers modify the pointed Data.
78  //
79  // Thread-safety: this method should not be called from multiple threads as
80  // the result is not protected by a mutex and thus could be change by the
81  // other call. Note though that concurrent calls to NewUpdateTracker() and
82  // DeleteUpdateTracker() are fine since the changes will only be applied on
83  // the next call to this function.
84  const std::vector<IdDataPair>& GetUpdatedTrackers();
85 
86  // Returns the data corresponding to the provided tracker. It CHECKs that the
87  // tracker exists.
88  //
89  // It does not apply the pending actions (so that it can have a const
90  // overload), thus the result of GetUpdatedTrackers() is not modified.
91  //
92  // Thread-safety: this method can be called from multiple threads but the
93  // resulting reference can be invalidated if the returned tracker is deleted
94  // and GetUpdatedTrackers() is called.
95  //
96  // Complexity: O(n) where n is the number of trackers. The number of update
97  // trackers should be small, if it grows to a point where this is an issue
98  // this class can easily be made more efficient (O(lg n) or better).
99  Data& GetData(UpdateTrackerId update_tracker);
100  const Data& GetData(UpdateTrackerId update_tracker) const;
101 
102  private:
103  // Returns the iterator pointing to the provided tracker, or v.end() if not
104  // found.
105  //
106  // Complexity: O(n) where n is the number of trackers. Can be optimized to
107  // O(lg n) easily if needed.
108  static typename std::vector<IdDataPair>::iterator FindTracker(
109  std::vector<IdDataPair>& v, UpdateTrackerId update_tracker);
110  static typename std::vector<IdDataPair>::const_iterator FindTracker(
111  const std::vector<IdDataPair>& v, UpdateTrackerId update_tracker);
112 
113  // Updates has_pending_actions_ to be true iff pending_new_trackers_ or
114  // pending_removed_trackers_ are not empty.
115  void UpdateHasPendingActions() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
116 
117  mutable absl::Mutex mutex_;
118 
119  // Next index to use in NewUpdateTracker().
120  UpdateTrackerId next_update_tracker_ ABSL_GUARDED_BY(mutex_) = {};
121 
122  // New trackers not yet added to trackers_.
123  //
124  // Invariants: trackers in this collection are not in trackers_ or in
125  // pending_removed_trackers_.
126  std::vector<IdDataPair> pending_new_trackers_ ABSL_GUARDED_BY(mutex_);
127 
128  // Trackers returned by GetUpdatedTrackers().
129  std::vector<IdDataPair> trackers_;
130 
131  // Trackers to be removed.
132  //
133  // Invariants: trackers in this collection must only be in trackers_. When
134  // trackers in pending_new_trackers_ are deleted, they are simply removed from
135  // pending_new_trackers_.
136  absl::flat_hash_set<UpdateTrackerId> pending_removed_trackers_
137  ABSL_GUARDED_BY(mutex_);
138 
139  // Set to true iff pending_new_trackers_ or pending_removed_trackers_ are not
140  // empty. This must be set only while holding mutex_ but will be loaded
141  // without it. This atomic enables not having the performance penalty of
142  // acquiring a mutex in each call to GetUpdatedTrackers().
143  //
144  // This should not be set directly, instead UpdateHasPendingActions() should
145  // be called.
146  std::atomic<bool> has_pending_actions_ = false;
147 };
148 
150 // Inline implementations.
152 
153 template <typename Data>
154 void UpdateTrackers<Data>::UpdateHasPendingActions() {
155  has_pending_actions_ =
156  !pending_new_trackers_.empty() || !pending_removed_trackers_.empty();
157 }
158 
159 template <typename Data>
160 template <typename... Args>
161 UpdateTrackerId UpdateTrackers<Data>::NewUpdateTracker(Args&&... args) {
162  const absl::MutexLock lock(&mutex_);
163 
164  const UpdateTrackerId update_tracker = next_update_tracker_;
165  ++next_update_tracker_;
166 
167  pending_new_trackers_.push_back(std::make_pair(
168  update_tracker, std::make_unique<Data>(std::forward<Args>(args)...)));
169  UpdateHasPendingActions();
170 
171  return update_tracker;
172 }
173 
174 template <typename Data>
175 typename std::vector<typename UpdateTrackers<Data>::IdDataPair>::iterator
176 UpdateTrackers<Data>::FindTracker(std::vector<IdDataPair>& v,
177  const UpdateTrackerId update_tracker) {
178  return std::find_if(v.begin(), v.end(), [&](const IdDataPair& pair) {
179  return pair.first == update_tracker;
180  });
181 }
182 
183 template <typename Data>
184 typename std::vector<typename UpdateTrackers<Data>::IdDataPair>::const_iterator
185 UpdateTrackers<Data>::FindTracker(const std::vector<IdDataPair>& v,
186  const UpdateTrackerId update_tracker) {
187  return std::find_if(v.begin(), v.end(), [&](const IdDataPair& pair) {
188  return pair.first == update_tracker;
189  });
190 }
191 
192 template <typename Data>
194  const UpdateTrackerId update_tracker) {
195  const absl::MutexLock lock(&mutex_);
196 
197  // The delete trackers may still be in pending_new_trackers_. We have to
198  // remove it from there.
199  {
200  const auto found_new = FindTracker(pending_new_trackers_, update_tracker);
201  if (found_new != pending_new_trackers_.end()) {
202  pending_new_trackers_.erase(found_new);
203  UpdateHasPendingActions();
204  return;
205  }
206  }
207 
208  // The deleted trackers could already be in pending_removed_trackers_, which
209  // would be an issue since trackers can't be removed multiple times.
210  CHECK(pending_removed_trackers_.find(update_tracker) ==
211  pending_removed_trackers_.end())
212  << "Update tracker " << update_tracker << " does not exist";
213 
214  // Test that the tracker actually exists.
215  const auto found_existing = FindTracker(trackers_, update_tracker);
216  CHECK(found_existing != trackers_.end())
217  << "Update tracker " << update_tracker << " does not exist";
218 
219  pending_removed_trackers_.insert(update_tracker);
220  UpdateHasPendingActions();
221 }
222 
223 template <typename Data>
224 const std::vector<typename UpdateTrackers<Data>::IdDataPair>&
226  // Here we use the "relaxed" memory order since we don't want or need to
227  // ensure proper ordering of memory accesses. We only want to make sure it is
228  // safe to delete a tracker from another thread while we read it
229  // here. Temporarily modifying a deleted tracker has no downside effect; we
230  // only care that the tracker will eventually be deleted, not that it is
231  // deleted as soon as possible.
232  //
233  // Using a non-relaxed memory order has significant impact on performances
234  // here.
235  if (has_pending_actions_.load(std::memory_order_relaxed)) {
236  const absl::MutexLock lock(&mutex_);
237 
238  // Flush removed trackers.
240  &trackers_,
241  [&](const IdDataPair& pair) ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
242  const auto found = pending_removed_trackers_.find(pair.first);
243  if (found == pending_removed_trackers_.end()) {
244  return false;
245  }
246  pending_removed_trackers_.erase(found);
247  return true;
248  });
249  DCHECK(pending_removed_trackers_.empty());
250  pending_removed_trackers_.clear(); // Safeguard.
251 
252  // Move new trackers.
253  trackers_.insert(trackers_.end(),
254  std::make_move_iterator(pending_new_trackers_.begin()),
255  std::make_move_iterator(pending_new_trackers_.end()));
256  pending_new_trackers_.clear();
257 
258  UpdateHasPendingActions();
259  }
260  return trackers_;
261 }
262 
263 template <typename Data>
264 Data& UpdateTrackers<Data>::GetData(UpdateTrackerId update_tracker) {
265  const absl::MutexLock lock(&mutex_);
266  // Note that here we could apply the pending actions. We don't do so to keep
267  // the symmetry with the const overload below.
268 
269  // The tracker may still be in pending_new_trackers_.
270  {
271  const auto found_new = FindTracker(pending_new_trackers_, update_tracker);
272  if (found_new != pending_new_trackers_.end()) {
273  return *found_new->second;
274  }
275  }
276 
277  // The tracker could be in pending_removed_trackers_.
278  CHECK(pending_removed_trackers_.find(update_tracker) ==
279  pending_removed_trackers_.end())
280  << "Update tracker " << update_tracker << " does not exist";
281 
282  // The tracker must be in trackers_.
283  const auto found_existing = FindTracker(trackers_, update_tracker);
284  CHECK(found_existing != trackers_.end())
285  << "Update tracker " << update_tracker << " does not exist";
286 
287  return *found_existing->second;
288 }
289 
290 template <typename Data>
292  UpdateTrackerId update_tracker) const {
293  const absl::MutexLock lock(&mutex_);
294  // The tracker may still be in pending_new_trackers_.
295  {
296  const auto found_new = FindTracker(pending_new_trackers_, update_tracker);
297  if (found_new != pending_new_trackers_.end()) {
298  return *found_new->second;
299  }
300  }
301 
302  // The tracker could be in pending_removed_trackers_.
303  CHECK(pending_removed_trackers_.find(update_tracker) ==
304  pending_removed_trackers_.end())
305  << "Update tracker " << update_tracker << " does not exist";
306 
307  // The tracker must be in trackers_.
308  const auto found_existing = FindTracker(trackers_, update_tracker);
309  CHECK(found_existing != trackers_.end())
310  << "Update tracker " << update_tracker << " does not exist";
311 
312  return *found_existing->second;
313 }
314 
315 } // namespace operations_research::math_opt
316 
317 #endif // OR_TOOLS_MATH_OPT_STORAGE_UPDATE_TRACKERS_H_
Data & GetData(UpdateTrackerId update_tracker)
void DeleteUpdateTracker(UpdateTrackerId update_tracker)
const Data & GetData(UpdateTrackerId update_tracker) const
std::pair< UpdateTrackerId, std::unique_ptr< Data > > IdDataPair
const std::vector< IdDataPair > & GetUpdatedTrackers()
UpdateTrackerId NewUpdateTracker(T &&... args)
Definition: cleanup.h:22
void STLEraseAllFromSequenceIf(T *v, P pred)
Definition: stl_util.h:107
if(!yyg->yy_init)
Definition: parser.yy.cc:965