OR-Tools  9.1
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
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"
40
41namespace operations_research {
42namespace math_opt {
43
44std::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
97void 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
138absl::optional<CallbackResultProto>
139GScipSolverCallbackHandler::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
#define LOG_IF(severity, condition)
Definition: base/logging.h:475
#define LOG(severity)
Definition: base/logging.h:416
#define ABSL_DIE_IF_NULL
Definition: base/logging.h:41
static std::unique_ptr< GScipSolverCallbackHandler > RegisterIfNeeded(const CallbackRegistrationProto &callback_registration, SolverInterface::Callback callback, absl::Time solve_start, SCIP *scip)
GScipSolverCallbackHandler(const GScipSolverCallbackHandler &)=delete
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
MPCallback * callback
const int ERROR
Definition: log_severity.h:32
absl::flat_hash_set< CallbackEventProto > EventSet(const CallbackRegistrationProto &callback_registration)
Collection of objects used to extend the Constraint Solver library.
std::function< void(GScipMessageType type, absl::string_view message)> GScipMessageHandler
STL namespace.
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition: protoutil.h:27
#define SCIP_TO_STATUS(x)
#define RETURN_IF_ERROR(expr)
Definition: status_macros.h:29
std::string message
Definition: trace.cc:398