1 /* Copyright 2016 The TensorFlow Authors. All Rights Reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 ==============================================================================*/ 15 16 #include <algorithm> 17 18 #include "tensorflow/core/platform/cloud/curl_http_request.h" 19 20 #include "tensorflow/core/lib/core/errors.h" 21 #include "tensorflow/core/lib/gtl/map_util.h" 22 #include "tensorflow/core/lib/strings/scanner.h" 23 #include "tensorflow/core/lib/strings/str_util.h" 24 #include "tensorflow/core/platform/types.h" 25 #include "tensorflow/core/public/version.h" 26 27 namespace tensorflow { 28 29 namespace { 30 31 // Set to 1 to enable verbose debug output from curl. 32 constexpr uint64 kVerboseOutput = 0; 33 34 // Proxy to the real libcurl implementation. 35 class LibCurlProxy : public LibCurl { 36 public: 37 static LibCurlProxy* Load() { 38 static LibCurlProxy* libcurl = []() -> LibCurlProxy* { 39 curl_global_init(CURL_GLOBAL_ALL); 40 return new LibCurlProxy; 41 }(); 42 return libcurl; 43 } 44 45 CURL* curl_easy_init() override { return ::curl_easy_init(); } 46 47 CURLcode curl_easy_setopt(CURL* curl, CURLoption option, 48 uint64 param) override { 49 return ::curl_easy_setopt(curl, option, param); 50 } 51 52 CURLcode curl_easy_setopt(CURL* curl, CURLoption option, 53 const char* param) override { 54 return ::curl_easy_setopt(curl, option, param); 55 } 56 57 CURLcode curl_easy_setopt(CURL* curl, CURLoption option, 58 void* param) override { 59 return ::curl_easy_setopt(curl, option, param); 60 } 61 62 CURLcode curl_easy_setopt(CURL* curl, CURLoption option, 63 size_t (*param)(void*, size_t, size_t, 64 FILE*)) override { 65 return ::curl_easy_setopt(curl, option, param); 66 } 67 68 CURLcode curl_easy_setopt(CURL* curl, CURLoption option, 69 size_t (*param)(const void*, size_t, size_t, 70 void*)) override { 71 return ::curl_easy_setopt(curl, option, param); 72 } 73 74 CURLcode curl_easy_setopt(CURL* curl, CURLoption option, 75 int (*param)(void* clientp, curl_off_t dltotal, 76 curl_off_t dlnow, curl_off_t ultotal, 77 curl_off_t ulnow)) override { 78 return ::curl_easy_setopt(curl, option, param); 79 } 80 81 CURLcode curl_easy_perform(CURL* curl) override { 82 return ::curl_easy_perform(curl); 83 } 84 85 CURLcode curl_easy_getinfo(CURL* curl, CURLINFO info, 86 uint64* value) override { 87 return ::curl_easy_getinfo(curl, info, value); 88 } 89 90 CURLcode curl_easy_getinfo(CURL* curl, CURLINFO info, 91 double* value) override { 92 return ::curl_easy_getinfo(curl, info, value); 93 } 94 95 void curl_easy_cleanup(CURL* curl) override { 96 return ::curl_easy_cleanup(curl); 97 } 98 99 char* curl_easy_escape(CURL* curl, const char* str, int length) override { 100 return ::curl_easy_escape(curl, str, length); 101 } 102 103 curl_slist* curl_slist_append(curl_slist* list, const char* str) override { 104 return ::curl_slist_append(list, str); 105 } 106 107 void curl_slist_free_all(curl_slist* list) override { 108 return ::curl_slist_free_all(list); 109 } 110 111 void curl_free(void* p) override { ::curl_free(p); } 112 113 const char* curl_easy_strerror(CURLcode errornum) override { 114 return ::curl_easy_strerror(errornum); 115 } 116 }; 117 } // namespace 118 119 CurlHttpRequest::CurlHttpRequest() : CurlHttpRequest(LibCurlProxy::Load()) {} 120 121 CurlHttpRequest::CurlHttpRequest(LibCurl* libcurl, Env* env) 122 : libcurl_(libcurl), env_(env) { 123 default_response_buffer_.reserve(CURL_MAX_WRITE_SIZE); 124 125 curl_ = libcurl_->curl_easy_init(); 126 CHECK(curl_ != nullptr) << "Couldn't initialize a curl session."; 127 128 // NOTE: CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt is configured by 129 // default in //third_party:curl.BUILD and can be customized via an 130 // environment variable. 131 132 libcurl_->curl_easy_setopt(curl_, CURLOPT_VERBOSE, kVerboseOutput); 133 libcurl_->curl_easy_setopt( 134 curl_, CURLOPT_USERAGENT, 135 strings::StrCat("TensorFlow/", TF_VERSION_STRING).c_str()); 136 // Do not use signals for timeouts - does not work in multi-threaded programs. 137 libcurl_->curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L); 138 libcurl_->curl_easy_setopt(curl_, CURLOPT_HTTP_VERSION, 139 CURL_HTTP_VERSION_2_0); 140 141 // Set up the progress meter. 142 libcurl_->curl_easy_setopt(curl_, CURLOPT_NOPROGRESS, 0ULL); 143 libcurl_->curl_easy_setopt(curl_, CURLOPT_XFERINFODATA, this); 144 libcurl_->curl_easy_setopt(curl_, CURLOPT_XFERINFOFUNCTION, 145 &CurlHttpRequest::ProgressCallback); 146 147 // If response buffer is not set, libcurl will print results to stdout, 148 // so we always set it. 149 SetResultBuffer(&default_response_buffer_); 150 } 151 152 CurlHttpRequest::~CurlHttpRequest() { 153 if (curl_headers_) { 154 libcurl_->curl_slist_free_all(curl_headers_); 155 } 156 if (resolve_list_) { 157 libcurl_->curl_slist_free_all(resolve_list_); 158 } 159 if (put_body_) { 160 fclose(put_body_); 161 } 162 if (curl_) { 163 libcurl_->curl_easy_cleanup(curl_); 164 } 165 } 166 167 string CurlHttpRequest::EscapeString(const string& str) { 168 char* out_char_str = libcurl_->curl_easy_escape(curl_, str.c_str(), 0); 169 string out_str(out_char_str); 170 libcurl_->curl_free(out_char_str); 171 return out_str; 172 } 173 174 void CurlHttpRequest::SetUri(const string& uri) { 175 CheckNotSent(); 176 is_uri_set_ = true; 177 uri_ = uri; 178 libcurl_->curl_easy_setopt(curl_, CURLOPT_URL, uri.c_str()); 179 } 180 181 void CurlHttpRequest::SetRange(uint64 start, uint64 end) { 182 CheckNotSent(); 183 libcurl_->curl_easy_setopt(curl_, CURLOPT_RANGE, 184 strings::StrCat(start, "-", end).c_str()); 185 } 186 187 void CurlHttpRequest::AddHeader(const string& name, const string& value) { 188 CheckNotSent(); 189 curl_headers_ = libcurl_->curl_slist_append( 190 curl_headers_, strings::StrCat(name, ": ", value).c_str()); 191 } 192 193 void CurlHttpRequest::AddResolveOverride(const string& hostname, int64 port, 194 const string& ip_addr) { 195 CheckNotSent(); 196 // Resolve values are hostname:port:IP.add.ress 197 resolve_list_ = libcurl_->curl_slist_append( 198 resolve_list_, 199 strings::StrCat(hostname, ":", port, ":", ip_addr).c_str()); 200 } 201 202 void CurlHttpRequest::AddAuthBearerHeader(const string& auth_token) { 203 CheckNotSent(); 204 if (!auth_token.empty()) { 205 AddHeader("Authorization", strings::StrCat("Bearer ", auth_token)); 206 } 207 } 208 209 void CurlHttpRequest::SetDeleteRequest() { 210 CheckNotSent(); 211 CheckMethodNotSet(); 212 is_method_set_ = true; 213 libcurl_->curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "DELETE"); 214 } 215 216 Status CurlHttpRequest::SetPutFromFile(const string& body_filepath, 217 size_t offset) { 218 CheckNotSent(); 219 CheckMethodNotSet(); 220 is_method_set_ = true; 221 if (put_body_) { 222 fclose(put_body_); 223 } 224 put_body_ = fopen(body_filepath.c_str(), "r"); 225 if (!put_body_) { 226 return errors::InvalidArgument("Couldn't open the specified file: " + 227 body_filepath); 228 } 229 fseek(put_body_, 0, SEEK_END); 230 const auto size = ftell(put_body_) - offset; 231 fseek(put_body_, offset, SEEK_SET); 232 233 curl_headers_ = libcurl_->curl_slist_append( 234 curl_headers_, strings::StrCat("Content-Length: ", size).c_str()); 235 libcurl_->curl_easy_setopt(curl_, CURLOPT_PUT, 1); 236 libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA, 237 reinterpret_cast<void*>(put_body_)); 238 // Using the default CURLOPT_READFUNCTION, which is doing an fread() on the 239 // FILE * userdata set with CURLOPT_READDATA. 240 return Status::OK(); 241 } 242 243 void CurlHttpRequest::SetPutEmptyBody() { 244 CheckNotSent(); 245 CheckMethodNotSet(); 246 is_method_set_ = true; 247 libcurl_->curl_easy_setopt(curl_, CURLOPT_PUT, 1); 248 curl_headers_ = 249 libcurl_->curl_slist_append(curl_headers_, "Content-Length: 0"); 250 libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA, 251 reinterpret_cast<void*>(this)); 252 libcurl_->curl_easy_setopt(curl_, CURLOPT_READFUNCTION, 253 &CurlHttpRequest::ReadCallback); 254 } 255 256 void CurlHttpRequest::SetPostFromBuffer(const char* buffer, size_t size) { 257 CheckNotSent(); 258 CheckMethodNotSet(); 259 is_method_set_ = true; 260 curl_headers_ = libcurl_->curl_slist_append( 261 curl_headers_, strings::StrCat("Content-Length: ", size).c_str()); 262 libcurl_->curl_easy_setopt(curl_, CURLOPT_POST, 1); 263 libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA, 264 reinterpret_cast<void*>(this)); 265 libcurl_->curl_easy_setopt(curl_, CURLOPT_READFUNCTION, 266 &CurlHttpRequest::ReadCallback); 267 post_body_buffer_ = StringPiece(buffer, size); 268 } 269 270 void CurlHttpRequest::SetPostEmptyBody() { 271 CheckNotSent(); 272 CheckMethodNotSet(); 273 is_method_set_ = true; 274 libcurl_->curl_easy_setopt(curl_, CURLOPT_POST, 1); 275 curl_headers_ = 276 libcurl_->curl_slist_append(curl_headers_, "Content-Length: 0"); 277 libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA, 278 reinterpret_cast<void*>(this)); 279 libcurl_->curl_easy_setopt(curl_, CURLOPT_READFUNCTION, 280 &CurlHttpRequest::ReadCallback); 281 } 282 283 void CurlHttpRequest::SetResultBuffer(std::vector<char>* out_buffer) { 284 CheckNotSent(); 285 CHECK(out_buffer != nullptr); 286 287 out_buffer->clear(); 288 response_buffer_ = out_buffer; 289 290 libcurl_->curl_easy_setopt(curl_, CURLOPT_WRITEDATA, 291 reinterpret_cast<void*>(this)); 292 libcurl_->curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, 293 &CurlHttpRequest::WriteCallback); 294 } 295 296 void CurlHttpRequest::SetResultBufferDirect(char* buffer, size_t size) { 297 CHECK(buffer != nullptr); 298 CheckNotSent(); 299 300 direct_response_ = DirectResponseState{buffer, size, 0}; 301 302 libcurl_->curl_easy_setopt(curl_, CURLOPT_WRITEDATA, 303 reinterpret_cast<void*>(this)); 304 libcurl_->curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, 305 &CurlHttpRequest::WriteCallbackDirect); 306 } 307 308 bool CurlHttpRequest::IsDirectResponse() const { 309 return direct_response_.buffer_ != nullptr; 310 } 311 312 size_t CurlHttpRequest::WriteCallbackDirect(const void* ptr, size_t size, 313 size_t nmemb, void* userdata) { 314 CHECK(ptr != nullptr); 315 auto that = reinterpret_cast<CurlHttpRequest*>(userdata); 316 DirectResponseState* state = &that->direct_response_; 317 CHECK(state->buffer_ != nullptr); 318 CHECK(state->bytes_transferred_ <= state->buffer_size_); 319 320 size_t curl_bytes_received = size * nmemb; 321 size_t user_buffer_bytes_available = 322 state->buffer_size_ - state->bytes_transferred_; 323 324 // The HTTP server may send a response body that is longer than what we 325 // expected. We must not use CHECK() for this situation, because that would 326 // imply a code bug (in this client code) where none exists; the violation of 327 // expectations would have been caused by the server, not the client. So we 328 // report a log warning, if an HTTP server is misbehaving. 329 if (curl_bytes_received > user_buffer_bytes_available) { 330 LOG(WARNING) << "The HTTP response body that we received is longer than we " 331 "requested or expected. " 332 << "Total bytes requested: " << state->buffer_size_ 333 << " Bytes received (so far) in HTTP response body: " 334 << (state->bytes_transferred_ + curl_bytes_received); 335 } 336 337 size_t bytes_to_copy = 338 std::min<size_t>(curl_bytes_received, user_buffer_bytes_available); 339 memcpy(&state->buffer_[state->bytes_transferred_], ptr, bytes_to_copy); 340 state->bytes_transferred_ += bytes_to_copy; 341 return bytes_to_copy; 342 } 343 344 size_t CurlHttpRequest::GetResultBufferDirectBytesTransferred() { 345 CHECK(direct_response_.buffer_ != nullptr); 346 return direct_response_.bytes_transferred_; 347 } 348 349 void CurlHttpRequest::SetTimeouts(uint32 connection, uint32 inactivity, 350 uint32 total) { 351 CheckNotSent(); 352 connect_timeout_secs_ = connection; 353 inactivity_timeout_secs_ = inactivity; 354 request_timeout_secs_ = total; 355 } 356 357 size_t CurlHttpRequest::WriteCallback(const void* ptr, size_t size, 358 size_t nmemb, void* this_object) { 359 CHECK(ptr); 360 auto that = reinterpret_cast<CurlHttpRequest*>(this_object); 361 CHECK(that->response_buffer_); 362 const size_t bytes_to_copy = size * nmemb; 363 that->response_buffer_->insert( 364 that->response_buffer_->end(), reinterpret_cast<const char*>(ptr), 365 reinterpret_cast<const char*>(ptr) + bytes_to_copy); 366 367 return bytes_to_copy; 368 } 369 370 size_t CurlHttpRequest::ReadCallback(void* ptr, size_t size, size_t nmemb, 371 FILE* this_object) { 372 CHECK(ptr); 373 auto that = reinterpret_cast<CurlHttpRequest*>(this_object); 374 CHECK(that->post_body_read_ <= that->post_body_buffer_.size()); 375 const size_t bytes_to_copy = std::min( 376 size * nmemb, that->post_body_buffer_.size() - that->post_body_read_); 377 memcpy(ptr, that->post_body_buffer_.data() + that->post_body_read_, 378 bytes_to_copy); 379 that->post_body_read_ += bytes_to_copy; 380 return bytes_to_copy; 381 } 382 383 size_t CurlHttpRequest::HeaderCallback(const void* ptr, size_t size, 384 size_t nmemb, void* this_object) { 385 CHECK(ptr); 386 auto that = reinterpret_cast<CurlHttpRequest*>(this_object); 387 StringPiece header(reinterpret_cast<const char*>(ptr), size * nmemb); 388 StringPiece name, value; 389 // The supplied header has the form "<name>: <value>", parse it. 390 if (strings::Scanner(header) 391 .ScanEscapedUntil(':') 392 .StopCapture() 393 .OneLiteral(": ") 394 .GetResult(&value, &name)) { 395 string str_value = value.ToString(); 396 str_util::StripTrailingWhitespace(&str_value); 397 that->response_headers_[name.ToString()] = str_value; 398 } 399 return size * nmemb; 400 } 401 402 Status CurlHttpRequest::Send() { 403 CheckNotSent(); 404 CHECK(is_uri_set_) << "URI has not been set."; 405 406 is_sent_ = true; 407 408 if (curl_headers_) { 409 libcurl_->curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers_); 410 } 411 if (resolve_list_) { 412 libcurl_->curl_easy_setopt(curl_, CURLOPT_RESOLVE, resolve_list_); 413 } 414 libcurl_->curl_easy_setopt(curl_, CURLOPT_HEADERDATA, 415 reinterpret_cast<void*>(this)); 416 libcurl_->curl_easy_setopt(curl_, CURLOPT_HEADERFUNCTION, 417 &CurlHttpRequest::HeaderCallback); 418 419 libcurl_->curl_easy_setopt(curl_, CURLOPT_TIMEOUT, request_timeout_secs_); 420 libcurl_->curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, 421 connect_timeout_secs_); 422 423 char error_buffer[CURL_ERROR_SIZE] = {0}; 424 libcurl_->curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buffer); 425 426 const auto curl_result = libcurl_->curl_easy_perform(curl_); 427 428 double written_size = 0; 429 libcurl_->curl_easy_getinfo(curl_, CURLINFO_SIZE_DOWNLOAD, &written_size); 430 431 libcurl_->curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &response_code_); 432 433 const auto& error_message = strings::StrCat( 434 "Error executing an HTTP request (HTTP response code ", response_code_, 435 ", error code ", curl_result, ", error message '", error_buffer, "')"); 436 437 Status result; 438 StringPiece response = GetResponse(); 439 string extended_error_message; 440 switch (response_code_) { 441 // The group of response codes indicating that the request achieved 442 // the expected goal. 443 case 200: // OK 444 case 201: // Created 445 case 204: // No Content 446 case 206: // Partial Content 447 if (curl_result != CURLE_OK) { 448 // This means the server executed the request successfully, but then 449 // something went wrong during the transmission of the response. 450 result = errors::Unavailable(error_message); 451 } else { 452 result = Status::OK(); 453 } 454 break; 455 case 416: // Requested Range Not Satisfiable 456 // The requested range had no overlap with the available range. 457 // This doesn't indicate an error, but this does mean an empty response 458 // body. 459 response_buffer_->clear(); 460 result = Status::OK(); 461 break; 462 463 // INVALID_ARGUMENT indicates a problem with how the request is constructed. 464 case 400: // Bad Request 465 case 411: // Length Required 466 result = errors::InvalidArgument(error_message); 467 break; 468 469 // PERMISSION_DENIED indicates an authentication or an authorization issue. 470 case 401: // Unauthorized 471 case 403: // Forbidden 472 if (!response.empty()) { 473 extended_error_message = strings::StrCat( 474 error_message, ", response ", 475 response.substr( 476 0, std::min(response.size(), response_to_error_limit_))); 477 result = errors::PermissionDenied(extended_error_message); 478 } else { 479 result = errors::PermissionDenied(error_message); 480 } 481 break; 482 483 // NOT_FOUND indicates that the requested resource does not exist. 484 case 404: // Not found 485 case 410: // Gone 486 result = errors::NotFound(error_message); 487 break; 488 489 // FAILED_PRECONDITION indicates that the request failed because some 490 // of the underlying assumptions were not satisfied. The request 491 // shouldn't be retried unless the external context has changed. 492 case 302: // Found 493 case 303: // See Other 494 case 304: // Not Modified 495 case 307: // Temporary Redirect 496 case 308: // Resume Incomplete 497 case 412: // Precondition Failed 498 case 413: // Payload Too Large 499 result = errors::FailedPrecondition(error_message); 500 break; 501 502 // UNAVAILABLE indicates a problem that can go away if the request 503 // is just retried without any modification. 504 case 409: // Conflict 505 case 429: // Too Many Requests 506 case 500: // Internal Server Error 507 case 502: // Bad Gateway 508 case 503: // Service Unavailable 509 default: // All other HTTP response codes also should be retried. 510 result = errors::Unavailable(error_message); 511 break; 512 } 513 if (!result.ok()) { 514 response_buffer_->clear(); 515 } 516 return result; 517 } 518 519 void CurlHttpRequest::CheckMethodNotSet() const { 520 CHECK(!is_method_set_) << "HTTP method has been already set."; 521 } 522 523 void CurlHttpRequest::CheckNotSent() const { 524 CHECK(!is_sent_) << "The request has already been sent."; 525 } 526 527 StringPiece CurlHttpRequest::GetResponse() const { 528 StringPiece response; 529 if (IsDirectResponse()) { 530 response = StringPiece(direct_response_.buffer_, 531 direct_response_.bytes_transferred_); 532 } else { 533 response = StringPiece(response_buffer_->data(), response_buffer_->size()); 534 } 535 return response; 536 } 537 538 string CurlHttpRequest::GetResponseHeader(const string& name) const { 539 const auto& header = response_headers_.find(name); 540 return header != response_headers_.end() ? header->second : ""; 541 } 542 543 uint64 CurlHttpRequest::GetResponseCode() const { return response_code_; } 544 545 // Cancels the transmission if no progress has been made for too long. 546 int CurlHttpRequest::ProgressCallback(void* this_object, curl_off_t dltotal, 547 curl_off_t dlnow, curl_off_t ultotal, 548 curl_off_t ulnow) { 549 auto that = reinterpret_cast<CurlHttpRequest*>(this_object); 550 const auto now = that->env_->NowSeconds(); 551 const auto current_progress = dlnow + ulnow; 552 if (that->last_progress_timestamp_ == 0 || 553 current_progress > that->last_progress_bytes_) { 554 // This is the first time the callback is called or some progress 555 // was made since the last tick. 556 that->last_progress_timestamp_ = now; 557 that->last_progress_bytes_ = current_progress; 558 return 0; 559 } 560 561 if (now - that->last_progress_timestamp_ > that->inactivity_timeout_secs_) { 562 double lookup_time = -1; 563 const auto lookup_time_status = that->libcurl_->curl_easy_getinfo( 564 that->curl_, CURLINFO_NAMELOOKUP_TIME, &lookup_time); 565 566 double connect_time = -1; 567 const auto connect_time_status = that->libcurl_->curl_easy_getinfo( 568 that->curl_, CURLINFO_CONNECT_TIME, &connect_time); 569 570 double pretransfer_time = -1; 571 const auto pretransfer_time_status = that->libcurl_->curl_easy_getinfo( 572 that->curl_, CURLINFO_PRETRANSFER_TIME, &pretransfer_time); 573 574 double starttransfer_time = -1; 575 const auto starttransfer_time_status = that->libcurl_->curl_easy_getinfo( 576 that->curl_, CURLINFO_PRETRANSFER_TIME, &starttransfer_time); 577 578 LOG(ERROR) << "The transmission of request " << this_object 579 << " (URI: " << that->uri_ << ") has been stuck at " 580 << current_progress << " of " << dltotal + ultotal 581 << " bytes for " << now - that->last_progress_timestamp_ 582 << " seconds and will be aborted. CURL timing information: " 583 << "lookup time: " << lookup_time << " (" 584 << that->libcurl_->curl_easy_strerror(lookup_time_status) 585 << "), connect time: " << connect_time << " (" 586 << that->libcurl_->curl_easy_strerror(connect_time_status) 587 << "), pre-transfer time: " << pretransfer_time << " (" 588 << that->libcurl_->curl_easy_strerror(pretransfer_time_status) 589 << "), start-transfer time: " << starttransfer_time << " (" 590 << that->libcurl_->curl_easy_strerror(starttransfer_time_status) 591 << ")"; 592 return 1; // Will abort the request. 593 } 594 595 // No progress was made since the last call, but we should wait a bit longer. 596 return 0; 597 } 598 599 } // namespace tensorflow 600