OR-Tools  9.2
gscip_solver_callback.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 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 
15 
16 #include <functional>
17 #include <memory>
18 #include <utility>
19 
20 #include "ortools/base/logging.h"
21 #include "absl/container/flat_hash_set.h"
22 #include "absl/memory/memory.h"
23 #include "absl/status/status.h"
24 #include "absl/status/statusor.h"
25 #include "absl/strings/string_view.h"
26 #include "absl/synchronization/mutex.h"
27 #include "absl/time/clock.h"
28 #include "absl/time/time.h"
29 #include "absl/types/optional.h"
30 #include "scip/scip.h"
31 #include "scip/type_scip.h"
34 #include "ortools/math_opt/callback.pb.h"
39 #include "ortools/base/protoutil.h"
40 
41 namespace operations_research {
42 namespace math_opt {
43 
44 std::unique_ptr<GScipSolverCallbackHandler>
46  const CallbackRegistrationProto& callback_registration,
47  const SolverInterface::Callback callback, const absl::Time solve_start,
48  SCIP* const scip) {
49  // TODO(b/180617976): Don't ignore unknown callbacks.
50  const absl::flat_hash_set<CallbackEventProto> events =
51  EventSet(callback_registration);
52  if (!events.contains(CALLBACK_EVENT_MESSAGE)) {
53  return nullptr;
54  }
55 
56  return absl::WrapUnique(
57  new GScipSolverCallbackHandler(callback, solve_start, scip));
58 }
59 
61  SolverInterface::Callback callback, absl::Time solve_start,
62  SCIP* const scip)
63  : callback_(std::move(ABSL_DIE_IF_NULL(callback))),
64  solve_start_(std::move(solve_start)),
65  scip_(ABSL_DIE_IF_NULL(scip)) {}
66 
68  return std::bind(&GScipSolverCallbackHandler::MessageCallback, this,
69  std::placeholders::_1, std::placeholders::_2);
70 }
71 
73  {
74  // Here we don't expect to be called in MessageCallback() at the same time
75  // since GScip is not supposed to call the callback given to GScip::Solve()
76  // after the end of this Solve(). But we lock anyway in case GScip does not
77  // honor its contract or if the caller calls Flush() before the end of the
78  // GScip::Solve().
79  //
80  // See MessageCallback() for the rationale of keeping the lock while calling
81  // CallUserCallback().
82  const absl::MutexLock lock(&message_mutex_);
83 
84  absl::optional<CallbackDataProto> data = message_callback_data_.Flush();
85  if (data) {
87  absl::Now() - solve_start_, data->mutable_runtime()))
88  << "Failed to encode the time.";
89  CallUserCallback(*data);
90  }
91  }
92 
93  const absl::MutexLock lock(&callback_mutex_);
94  return status_;
95 }
96 
97 void GScipSolverCallbackHandler::MessageCallback(
98  const GScipMessageType type, const absl::string_view message) {
99  // We hold the mutex until the end of the call of the user callback to ensure
100  // proper ordering of messages. We don't expect any user action in the user
101  // callback to trigger another call to MessageCallback(). If it happens to be
102  // the case then we will need to make the code of CallUserCallback()
103  // asynchronous to ensure that we don't end up making recursive calls to the
104  // user callback.
105  const absl::MutexLock lock(&message_mutex_);
106 
107  absl::optional<CallbackDataProto> data =
108  message_callback_data_.Parse(message);
109  if (!data) {
110  return;
111  }
112 
113  const absl::Status runtime_status = util_time::EncodeGoogleApiProto(
114  absl::Now() - solve_start_, data->mutable_runtime());
115  if (!runtime_status.ok()) {
116  absl::MutexLock lock(&callback_mutex_);
117  // Here we must not modify the status if it is already not OK.
118  if (!status_.ok()) {
119  return;
120  }
121  status_ = util::StatusBuilder(runtime_status)
122  << "Failed to encode the time.";
123  // TODO(b/182919884): Make sure it is correct to use SCIPinterruptSolve()
124  // here and maybe migrate to the same architecture as the one used to
125  // interrupt the solve from foreign threads..
126  const auto interrupt_status = SCIP_TO_STATUS(SCIPinterruptSolve(scip_));
127  LOG_IF(ERROR, !interrupt_status.ok())
128  << "Failed to interrupt the solve on error: " << interrupt_status;
129  return;
130  }
131 
132  // Events of type CALLBACK_EVENT_MESSAGE are not expected to return anything
133  // but `terminate`. Since CallUserCallback() already handles the termination,
134  // we can simply ignore the returned value here.
135  CallUserCallback(*data);
136 }
137 
138 absl::optional<CallbackResultProto>
139 GScipSolverCallbackHandler::CallUserCallback(
140  const CallbackDataProto& callback_data) {
141  // We hold the lock during the call of the user callback to ensure only one
142  // call execute at a time. Having multiple calls at once may be an issue when
143  // the user asks for termination since it may ask for it in one call while
144  // another thread is about to make its call for another callback.
145  //
146  // We don't expect any valid actions taken by the user is a callback to lead
147  // to another callback. That said, a potential corner case could be that
148  // adding a constraint lead to a message callback. If this happens, then this
149  // code will need to be made more complex to deal with that. And there won't
150  // be any easy solution.
151  //
152  // The simplest would be to have the message callbacks being delivered by a
153  // background thread so that a call to MessageCallback() is never blocking on
154  // the user answering to it. The issue here would be to deal with `terminate`
155  // since we can't call SCIPinterruptSolve() from another thread than a SCIP
156  // thread (it is not thread safe). Maybe this means that `terminate` should
157  // not be callable for message callbacks, or that the termination should be
158  // delayed to the next time it can be made.
159  absl::MutexLock lock(&callback_mutex_);
160  if (!status_.ok()) {
161  return absl::nullopt;
162  }
163 
164  absl::StatusOr<CallbackResultProto> result_or = callback_(callback_data);
165  status_ = result_or.status();
166  if (!result_or.ok() || result_or->terminate()) {
167  // TODO(b/182919884): Make sure it is correct to use SCIPinterruptSolve()
168  // here and maybe migrate to the same architecture as the one used to
169  // interrupt the solve from foreign threads..
170  const auto interrupt_status = SCIP_TO_STATUS(SCIPinterruptSolve(scip_));
171  if (!interrupt_status.ok()) {
172  if (status_.ok()) {
173  status_ = interrupt_status;
174  } else {
175  LOG(ERROR) << "Failed to interrupt the solve on error: "
176  << interrupt_status;
177  }
178  }
179  return absl::nullopt;
180  }
181 
182  return *std::move(result_or);
183 }
184 
185 } // namespace math_opt
186 } // namespace operations_research
MPCallback * callback
#define LOG(severity)
Definition: base/logging.h:420
#define SCIP_TO_STATUS(x)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition: protoutil.h:27
static std::unique_ptr< GScipSolverCallbackHandler > RegisterIfNeeded(const CallbackRegistrationProto &callback_registration, SolverInterface::Callback callback, absl::Time solve_start, SCIP *scip)
std::string message
Definition: trace.cc:398
GScipSolverCallbackHandler(const GScipSolverCallbackHandler &)=delete
absl::flat_hash_set< CallbackEventProto > EventSet(const CallbackRegistrationProto &callback_registration)
#define LOG_IF(severity, condition)
Definition: base/logging.h:479
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
Collection of objects used to extend the Constraint Solver library.
#define RETURN_IF_ERROR(expr)
Definition: status_macros.h:29
#define ABSL_DIE_IF_NULL
Definition: base/logging.h:43
std::function< void(GScipMessageType type, absl::string_view message)> GScipMessageHandler