Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
update_trackers.h
Go to the documentation of this file.
1// Copyright 2010-2024 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/log/check.h"
27#include "absl/synchronization/mutex.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.
43template <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
153template <typename Data>
154void UpdateTrackers<Data>::UpdateHasPendingActions() {
155 has_pending_actions_ =
156 !pending_new_trackers_.empty() || !pending_removed_trackers_.empty();
157}
158
159template <typename Data>
160template <typename... Args>
161UpdateTrackerId UpdateTrackers<Data>::NewUpdateTracker(Args&&... args) {
162 const absl::MutexLock lock(&mutex_);
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
174template <typename Data>
175typename std::vector<typename UpdateTrackers<Data>::IdDataPair>::iterator
176UpdateTrackers<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
183template <typename Data>
184typename std::vector<typename UpdateTrackers<Data>::IdDataPair>::const_iterator
185UpdateTrackers<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
192template <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
223template <typename Data>
224const 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
263template <typename Data>
264Data& 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
290template <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_
const std::vector< IdDataPair > & GetUpdatedTrackers()
Data & GetData(UpdateTrackerId update_tracker)
UpdateTrackerId NewUpdateTracker(T &&... args)
std::pair< UpdateTrackerId, std::unique_ptr< Data > > IdDataPair
A pair (tracker_id, tracker_data).
void DeleteUpdateTracker(UpdateTrackerId update_tracker)
void STLEraseAllFromSequenceIf(T *v, P pred)
Remove each element e in v satisfying pred(e).
Definition stl_util.h:107
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28