1 /** 2 * Orthanc - A Lightweight, RESTful DICOM Store 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics 4 * Department, University Hospital of Liege, Belgium 5 * Copyright (C) 2017-2021 Osimis S.A., Belgium 6 * 7 * This program is free software: you can redistribute it and/or 8 * modify it under the terms of the GNU Affero General Public License 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 19 **/ 20 21 22 #include "StorageBackend.h" 23 24 #if HAS_ORTHANC_EXCEPTION != 1 25 # error HAS_ORTHANC_EXCEPTION must be set to 1 26 #endif 27 28 #include "../../Framework/Common/BinaryStringValue.h" 29 #include "../../Framework/Common/ResultFileValue.h" 30 31 #include <Compatibility.h> // For std::unique_ptr<> 32 #include <Logging.h> 33 #include <OrthancException.h> 34 35 #include <boost/thread.hpp> 36 #include <cassert> 37 #include <limits> 38 39 40 #define ORTHANC_PLUGINS_DATABASE_CATCH \ 41 catch (::Orthanc::OrthancException& e) \ 42 { \ 43 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); \ 44 } \ 45 catch (::std::runtime_error& e) \ 46 { \ 47 std::string s = "Exception in storage area back-end: " + std::string(e.what()); \ 48 OrthancPluginLogError(context_, s.c_str()); \ 49 return OrthancPluginErrorCode_DatabasePlugin; \ 50 } \ 51 catch (...) \ 52 { \ 53 OrthancPluginLogError(context_, "Native exception"); \ 54 return OrthancPluginErrorCode_DatabasePlugin; \ 55 } 56 57 58 namespace OrthancDatabases 59 { 60 class StorageBackend::ReadWholeOperation : public StorageBackend::IDatabaseOperation 61 { 62 private: 63 IFileContentVisitor& visitor_; 64 const char* uuid_; 65 OrthancPluginContentType type_; 66 67 public: ReadWholeOperation(IFileContentVisitor & visitor,const char * uuid,OrthancPluginContentType type)68 ReadWholeOperation(IFileContentVisitor& visitor, 69 const char* uuid, 70 OrthancPluginContentType type) : 71 visitor_(visitor), 72 uuid_(uuid), 73 type_(type) 74 { 75 } 76 Execute(StorageBackend::IAccessor & accessor)77 virtual void Execute(StorageBackend::IAccessor& accessor) ORTHANC_OVERRIDE 78 { 79 accessor.ReadWhole(visitor_, uuid_, type_); 80 } 81 }; 82 83 StorageBackend(IDatabaseFactory * factory,unsigned int maxRetries)84 StorageBackend::StorageBackend(IDatabaseFactory* factory, 85 unsigned int maxRetries) : 86 manager_(factory), 87 maxRetries_(maxRetries) 88 { 89 } 90 Create(const std::string & uuid,const void * content,size_t size,OrthancPluginContentType type)91 void StorageBackend::AccessorBase::Create(const std::string& uuid, 92 const void* content, 93 size_t size, 94 OrthancPluginContentType type) 95 { 96 DatabaseManager::Transaction transaction(manager_, TransactionType_ReadWrite); 97 98 { 99 DatabaseManager::CachedStatement statement( 100 STATEMENT_FROM_HERE, manager_, 101 "INSERT INTO StorageArea VALUES (${uuid}, ${content}, ${type})"); 102 103 statement.SetParameterType("uuid", ValueType_Utf8String); 104 statement.SetParameterType("content", ValueType_InputFile); 105 statement.SetParameterType("type", ValueType_Integer64); 106 107 Dictionary args; 108 args.SetUtf8Value("uuid", uuid); 109 args.SetFileValue("content", content, size); 110 args.SetIntegerValue("type", type); 111 112 statement.Execute(args); 113 } 114 115 transaction.Commit(); 116 } 117 118 ReadWhole(StorageBackend::IFileContentVisitor & visitor,const std::string & uuid,OrthancPluginContentType type)119 void StorageBackend::AccessorBase::ReadWhole(StorageBackend::IFileContentVisitor& visitor, 120 const std::string& uuid, 121 OrthancPluginContentType type) 122 { 123 DatabaseManager::Transaction transaction(manager_, TransactionType_ReadOnly); 124 125 { 126 DatabaseManager::CachedStatement statement( 127 STATEMENT_FROM_HERE, manager_, 128 "SELECT content FROM StorageArea WHERE uuid=${uuid} AND type=${type}"); 129 130 statement.SetParameterType("uuid", ValueType_Utf8String); 131 statement.SetParameterType("type", ValueType_Integer64); 132 133 Dictionary args; 134 args.SetUtf8Value("uuid", uuid); 135 args.SetIntegerValue("type", type); 136 137 statement.Execute(args); 138 139 if (statement.IsDone()) 140 { 141 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); 142 } 143 else if (statement.GetResultFieldsCount() != 1) 144 { 145 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); 146 } 147 else 148 { 149 const IValue& value = statement.GetResultField(0); 150 151 switch (value.GetType()) 152 { 153 case ValueType_ResultFile: 154 { 155 std::string content; 156 dynamic_cast<const ResultFileValue&>(value).ReadWhole(content); 157 visitor.Assign(content); 158 break; 159 } 160 161 case ValueType_BinaryString: 162 visitor.Assign(dynamic_cast<const BinaryStringValue&>(value).GetContent()); 163 break; 164 165 default: 166 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); 167 } 168 } 169 } 170 171 transaction.Commit(); 172 173 if (!visitor.IsSuccess()) 174 { 175 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Could not read attachment from the storage area"); 176 } 177 } 178 179 ReadRange(IFileContentVisitor & visitor,const std::string & uuid,OrthancPluginContentType type,uint64_t start,size_t length)180 void StorageBackend::AccessorBase::ReadRange(IFileContentVisitor& visitor, 181 const std::string& uuid, 182 OrthancPluginContentType type, 183 uint64_t start, 184 size_t length) 185 { 186 /** 187 * This is a generic implementation, that will only work if 188 * "ResultFileValue" is implemented by the database backend. For 189 * instance, this will *not* work with MySQL, as the latter uses 190 * BLOB columns to store files. 191 **/ 192 DatabaseManager::Transaction transaction(manager_, TransactionType_ReadOnly); 193 194 { 195 DatabaseManager::CachedStatement statement( 196 STATEMENT_FROM_HERE, manager_, 197 "SELECT content FROM StorageArea WHERE uuid=${uuid} AND type=${type}"); 198 199 statement.SetParameterType("uuid", ValueType_Utf8String); 200 statement.SetParameterType("type", ValueType_Integer64); 201 202 Dictionary args; 203 args.SetUtf8Value("uuid", uuid); 204 args.SetIntegerValue("type", type); 205 206 statement.Execute(args); 207 208 if (statement.IsDone()) 209 { 210 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); 211 } 212 else if (statement.GetResultFieldsCount() != 1) 213 { 214 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); 215 } 216 else 217 { 218 const IValue& value = statement.GetResultField(0); 219 if (value.GetType() == ValueType_ResultFile) 220 { 221 std::string content; 222 dynamic_cast<const ResultFileValue&>(value).ReadRange(content, start, length); 223 visitor.Assign(content); 224 } 225 else 226 { 227 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); 228 } 229 } 230 } 231 232 transaction.Commit(); 233 234 if (!visitor.IsSuccess()) 235 { 236 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Could not read attachment from the storage area"); 237 } 238 } 239 240 Remove(const std::string & uuid,OrthancPluginContentType type)241 void StorageBackend::AccessorBase::Remove(const std::string& uuid, 242 OrthancPluginContentType type) 243 { 244 DatabaseManager::Transaction transaction(manager_, TransactionType_ReadWrite); 245 246 { 247 DatabaseManager::CachedStatement statement( 248 STATEMENT_FROM_HERE, manager_, 249 "DELETE FROM StorageArea WHERE uuid=${uuid} AND type=${type}"); 250 251 statement.SetParameterType("uuid", ValueType_Utf8String); 252 statement.SetParameterType("type", ValueType_Integer64); 253 254 Dictionary args; 255 args.SetUtf8Value("uuid", uuid); 256 args.SetIntegerValue("type", type); 257 258 statement.Execute(args); 259 } 260 261 transaction.Commit(); 262 } 263 264 265 static OrthancPluginContext* context_ = NULL; 266 static std::unique_ptr<StorageBackend> backend_; 267 268 StorageCreate(const char * uuid,const void * content,int64_t size,OrthancPluginContentType type)269 static OrthancPluginErrorCode StorageCreate(const char* uuid, 270 const void* content, 271 int64_t size, 272 OrthancPluginContentType type) 273 { 274 class Operation : public StorageBackend::IDatabaseOperation 275 { 276 private: 277 const char* uuid_; 278 const void* content_; 279 int64_t size_; 280 OrthancPluginContentType type_; 281 282 public: 283 Operation(const char* uuid, 284 const void* content, 285 int64_t size, 286 OrthancPluginContentType type) : 287 uuid_(uuid), 288 content_(content), 289 size_(size), 290 type_(type) 291 { 292 } 293 294 virtual void Execute(StorageBackend::IAccessor& accessor) ORTHANC_OVERRIDE 295 { 296 accessor.Create(uuid_, content_, size_, type_); 297 } 298 }; 299 300 301 try 302 { 303 if (backend_.get() == NULL) 304 { 305 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 306 } 307 else 308 { 309 Operation operation(uuid, content, size, type); 310 backend_->Execute(operation); 311 return OrthancPluginErrorCode_Success; 312 } 313 } 314 ORTHANC_PLUGINS_DATABASE_CATCH; 315 } 316 317 318 #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) 319 # if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 0) StorageReadWhole(OrthancPluginMemoryBuffer64 * target,const char * uuid,OrthancPluginContentType type)320 static OrthancPluginErrorCode StorageReadWhole(OrthancPluginMemoryBuffer64* target, 321 const char* uuid, 322 OrthancPluginContentType type) 323 { 324 class Visitor : public StorageBackend::IFileContentVisitor 325 { 326 private: 327 OrthancPluginMemoryBuffer64* target_; 328 bool success_; 329 330 public: 331 Visitor(OrthancPluginMemoryBuffer64* target) : 332 target_(target), 333 success_(false) 334 { 335 if (target == NULL) 336 { 337 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); 338 } 339 } 340 341 virtual bool IsSuccess() const ORTHANC_OVERRIDE 342 { 343 return success_; 344 } 345 346 virtual void Assign(const std::string& content) ORTHANC_OVERRIDE 347 { 348 if (success_) 349 { 350 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 351 } 352 else 353 { 354 assert(context_ != NULL); 355 356 if (OrthancPluginCreateMemoryBuffer64(context_, target_, static_cast<uint64_t>(content.size())) != 357 OrthancPluginErrorCode_Success) 358 { 359 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory); 360 } 361 362 if (!content.empty()) 363 { 364 memcpy(target_->data, content.c_str(), content.size()); 365 } 366 367 success_ = true; 368 } 369 } 370 }; 371 372 373 try 374 { 375 if (backend_.get() == NULL) 376 { 377 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 378 } 379 else 380 { 381 Visitor visitor(target); 382 383 { 384 StorageBackend::ReadWholeOperation operation(visitor, uuid, type); 385 backend_->Execute(operation); 386 } 387 388 return OrthancPluginErrorCode_Success; 389 } 390 } 391 ORTHANC_PLUGINS_DATABASE_CATCH; 392 } 393 394 StorageReadRange(OrthancPluginMemoryBuffer64 * target,const char * uuid,OrthancPluginContentType type,uint64_t start)395 static OrthancPluginErrorCode StorageReadRange(OrthancPluginMemoryBuffer64* target, 396 const char* uuid, 397 OrthancPluginContentType type, 398 uint64_t start) 399 { 400 class Visitor : public StorageBackend::IFileContentVisitor 401 { 402 private: 403 OrthancPluginMemoryBuffer64* target_; // This buffer is already allocated by the Orthanc core 404 bool success_; 405 406 public: 407 Visitor(OrthancPluginMemoryBuffer64* target) : 408 target_(target), 409 success_(false) 410 { 411 if (target == NULL) 412 { 413 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); 414 } 415 } 416 417 virtual bool IsSuccess() const ORTHANC_OVERRIDE 418 { 419 return success_; 420 } 421 422 virtual void Assign(const std::string& content) ORTHANC_OVERRIDE 423 { 424 if (success_) 425 { 426 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 427 } 428 else 429 { 430 if (content.size() != target_->size) 431 { 432 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 433 } 434 435 if (!content.empty()) 436 { 437 memcpy(target_->data, content.c_str(), content.size()); 438 } 439 440 success_ = true; 441 } 442 } 443 }; 444 445 446 class Operation : public StorageBackend::IDatabaseOperation 447 { 448 private: 449 Visitor& visitor_; 450 const char* uuid_; 451 OrthancPluginContentType type_; 452 uint64_t start_; 453 size_t length_; 454 455 public: 456 Operation(Visitor& visitor, 457 const char* uuid, 458 OrthancPluginContentType type, 459 uint64_t start, 460 size_t length) : 461 visitor_(visitor), 462 uuid_(uuid), 463 type_(type), 464 start_(start), 465 length_(length) 466 { 467 } 468 469 virtual void Execute(StorageBackend::IAccessor& accessor) ORTHANC_OVERRIDE 470 { 471 accessor.ReadRange(visitor_, uuid_, type_, start_, length_); 472 } 473 }; 474 475 476 try 477 { 478 if (backend_.get() == NULL) 479 { 480 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 481 } 482 else 483 { 484 Visitor visitor(target); 485 486 { 487 Operation operation(visitor, uuid, type, start, target->size); 488 backend_->Execute(operation); 489 } 490 491 return OrthancPluginErrorCode_Success; 492 } 493 } 494 ORTHANC_PLUGINS_DATABASE_CATCH; 495 } 496 # endif 497 #endif 498 499 StorageRead(void ** data,int64_t * size,const char * uuid,OrthancPluginContentType type)500 static OrthancPluginErrorCode StorageRead(void** data, 501 int64_t* size, 502 const char* uuid, 503 OrthancPluginContentType type) 504 { 505 class Visitor : public StorageBackend::IFileContentVisitor 506 { 507 private: 508 void** data_; 509 int64_t* size_; 510 bool success_; 511 512 public: 513 Visitor(void** data, 514 int64_t* size) : 515 data_(data), 516 size_(size), 517 success_(false) 518 { 519 } 520 521 ~Visitor() 522 { 523 if (data_ != NULL /* this condition is invalidated by "Release()" */ && 524 *data_ != NULL) 525 { 526 free(*data_); 527 } 528 } 529 530 void Release() 531 { 532 data_ = NULL; 533 } 534 535 virtual bool IsSuccess() const ORTHANC_OVERRIDE 536 { 537 return success_; 538 } 539 540 virtual void Assign(const std::string& content) ORTHANC_OVERRIDE 541 { 542 if (success_) 543 { 544 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 545 } 546 else if (data_ == NULL) 547 { 548 // "Release()" has been called 549 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 550 } 551 else 552 { 553 if (content.empty()) 554 { 555 *data_ = NULL; 556 *size_ = 0; 557 } 558 else 559 { 560 *size_ = static_cast<int64_t>(content.size()); 561 562 if (static_cast<size_t>(*size_) != content.size()) 563 { 564 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory, 565 "File cannot be stored in a 63bit buffer"); 566 } 567 568 *data_ = malloc(*size_); 569 if (*data_ == NULL) 570 { 571 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory); 572 } 573 574 memcpy(*data_, content.c_str(), *size_); 575 } 576 577 success_ = true; 578 } 579 } 580 }; 581 582 583 try 584 { 585 if (backend_.get() == NULL) 586 { 587 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 588 } 589 else if (data == NULL || 590 size == NULL) 591 { 592 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); 593 } 594 else 595 { 596 Visitor visitor(data, size); 597 598 { 599 StorageBackend::ReadWholeOperation operation(visitor, uuid, type); 600 backend_->Execute(operation); 601 } 602 603 visitor.Release(); 604 605 return OrthancPluginErrorCode_Success; 606 } 607 } 608 ORTHANC_PLUGINS_DATABASE_CATCH; 609 } 610 611 StorageRemove(const char * uuid,OrthancPluginContentType type)612 static OrthancPluginErrorCode StorageRemove(const char* uuid, 613 OrthancPluginContentType type) 614 { 615 class Operation : public StorageBackend::IDatabaseOperation 616 { 617 private: 618 const char* uuid_; 619 OrthancPluginContentType type_; 620 621 public: 622 Operation(const char* uuid, 623 OrthancPluginContentType type) : 624 uuid_(uuid), 625 type_(type) 626 { 627 } 628 629 virtual void Execute(StorageBackend::IAccessor& accessor) ORTHANC_OVERRIDE 630 { 631 accessor.Remove(uuid_, type_); 632 } 633 }; 634 635 636 try 637 { 638 if (backend_.get() == NULL) 639 { 640 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 641 } 642 else 643 { 644 Operation operation(uuid, type); 645 backend_->Execute(operation); 646 return OrthancPluginErrorCode_Success; 647 } 648 } 649 ORTHANC_PLUGINS_DATABASE_CATCH; 650 } 651 652 Register(OrthancPluginContext * context,StorageBackend * backend)653 void StorageBackend::Register(OrthancPluginContext* context, 654 StorageBackend* backend) 655 { 656 if (context == NULL || 657 backend == NULL) 658 { 659 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); 660 } 661 else if (context_ != NULL || 662 backend_.get() != NULL) 663 { 664 // This function can only be invoked once in the plugin 665 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 666 } 667 else 668 { 669 context_ = context; 670 backend_.reset(backend); 671 672 bool hasLoadedV2 = false; 673 674 #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 675 # if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 0) 676 if (OrthancPluginCheckVersionAdvanced(context, 1, 9, 0) == 1) 677 { 678 OrthancPluginStorageReadRange readRange = NULL; 679 if (backend_->HasReadRange()) 680 { 681 readRange = StorageReadRange; 682 } 683 684 OrthancPluginRegisterStorageArea2(context_, StorageCreate, StorageReadWhole, readRange, StorageRemove); 685 hasLoadedV2 = true; 686 } 687 # endif 688 #endif 689 690 if (!hasLoadedV2) 691 { 692 LOG(WARNING) << "Performance warning: Your version of the Orthanc core or SDK doesn't support reading of file ranges"; 693 OrthancPluginRegisterStorageArea(context_, StorageCreate, StorageRead, StorageRemove); 694 } 695 696 LOG(WARNING) << "The storage area plugin will retry up to " << backend_->GetMaxRetries() 697 << " time(s) in the case of a collision"; 698 } 699 } 700 701 Finalize()702 void StorageBackend::Finalize() 703 { 704 backend_.reset(NULL); 705 context_ = NULL; 706 } 707 708 709 class StorageBackend::StringVisitor : public StorageBackend::IFileContentVisitor 710 { 711 private: 712 std::string& target_; 713 bool success_; 714 715 public: StringVisitor(std::string & target)716 explicit StringVisitor(std::string& target) : 717 target_(target), 718 success_(false) 719 { 720 } 721 IsSuccess() const722 virtual bool IsSuccess() const ORTHANC_OVERRIDE 723 { 724 return success_; 725 } 726 Assign(const std::string & content)727 virtual void Assign(const std::string& content) ORTHANC_OVERRIDE 728 { 729 if (success_) 730 { 731 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 732 } 733 else 734 { 735 target_.assign(content); 736 success_ = true; 737 } 738 } 739 }; 740 741 ReadWholeToString(std::string & target,IAccessor & accessor,const std::string & uuid,OrthancPluginContentType type)742 void StorageBackend::ReadWholeToString(std::string& target, 743 IAccessor& accessor, 744 const std::string& uuid, 745 OrthancPluginContentType type) 746 { 747 StringVisitor visitor(target); 748 accessor.ReadWhole(visitor, uuid, type); 749 750 if (!visitor.IsSuccess()) 751 { 752 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 753 } 754 } 755 756 ReadRangeToString(std::string & target,IAccessor & accessor,const std::string & uuid,OrthancPluginContentType type,uint64_t start,size_t length)757 void StorageBackend::ReadRangeToString(std::string& target, 758 IAccessor& accessor, 759 const std::string& uuid, 760 OrthancPluginContentType type, 761 uint64_t start, 762 size_t length) 763 { 764 StringVisitor visitor(target); 765 accessor.ReadRange(visitor, uuid, type, start, length); 766 767 if (!visitor.IsSuccess()) 768 { 769 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 770 } 771 } 772 773 Execute(IDatabaseOperation & operation)774 void StorageBackend::Execute(IDatabaseOperation& operation) 775 { 776 std::unique_ptr<IAccessor> accessor(CreateAccessor()); 777 if (accessor.get() == NULL) 778 { 779 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 780 } 781 782 #if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 9, 2) 783 unsigned int attempt = 0; 784 #endif 785 786 for (;;) 787 { 788 try 789 { 790 operation.Execute(*accessor); 791 return; // Success 792 } 793 catch (Orthanc::OrthancException& e) 794 { 795 #if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 9, 2) 796 if (e.GetErrorCode() == Orthanc::ErrorCode_DatabaseCannotSerialize) 797 { 798 if (attempt >= maxRetries_) 799 { 800 throw; 801 } 802 else 803 { 804 attempt++; 805 806 // The "rand()" adds some jitter to de-synchronize writers 807 boost::this_thread::sleep(boost::posix_time::milliseconds(100 * attempt + 5 * (rand() % 10))); 808 } 809 } 810 else 811 { 812 throw; 813 } 814 #else 815 throw; 816 #endif 817 } 818 } 819 } 820 } 821