couchbase 3.0.0.alpha.5-universal-darwin-19 → 3.0.0.beta.1-universal-darwin-19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +12 -3
  3. data/README.md +4 -2
  4. data/Rakefile +1 -1
  5. data/couchbase.gemspec +17 -12
  6. data/ext/.idea/misc.xml +12 -0
  7. data/ext/CMakeLists.txt +10 -1
  8. data/ext/build_config.hxx.in +20 -0
  9. data/ext/build_version.hxx.in +1 -1
  10. data/ext/couchbase/bucket.hxx +90 -24
  11. data/ext/couchbase/cluster.hxx +125 -84
  12. data/ext/couchbase/cluster_options.hxx +53 -0
  13. data/ext/couchbase/configuration.hxx +220 -2
  14. data/ext/couchbase/couchbase.cxx +134 -127
  15. data/ext/couchbase/io/dns_client.hxx +3 -1
  16. data/ext/couchbase/io/http_command.hxx +91 -0
  17. data/ext/couchbase/io/http_session.hxx +58 -19
  18. data/ext/couchbase/io/http_session_manager.hxx +26 -31
  19. data/ext/couchbase/io/mcbp_command.hxx +180 -0
  20. data/ext/couchbase/io/mcbp_message.hxx +5 -0
  21. data/ext/couchbase/io/mcbp_session.hxx +213 -98
  22. data/ext/couchbase/io/streams.hxx +165 -0
  23. data/ext/couchbase/operations.hxx +1 -1
  24. data/ext/couchbase/operations/analytics_dataset_create.hxx +1 -1
  25. data/ext/couchbase/operations/bucket_create.hxx +4 -2
  26. data/ext/couchbase/operations/bucket_drop.hxx +4 -2
  27. data/ext/couchbase/operations/bucket_flush.hxx +4 -2
  28. data/ext/couchbase/operations/bucket_get.hxx +4 -2
  29. data/ext/couchbase/operations/bucket_get_all.hxx +4 -2
  30. data/ext/couchbase/operations/bucket_update.hxx +4 -2
  31. data/ext/couchbase/operations/cluster_developer_preview_enable.hxx +4 -2
  32. data/ext/couchbase/operations/collection_create.hxx +4 -2
  33. data/ext/couchbase/operations/collection_drop.hxx +4 -2
  34. data/ext/couchbase/operations/document_analytics.hxx +0 -4
  35. data/ext/couchbase/operations/document_decrement.hxx +6 -3
  36. data/ext/couchbase/operations/document_get.hxx +3 -0
  37. data/ext/couchbase/operations/document_get_and_lock.hxx +3 -0
  38. data/ext/couchbase/operations/document_get_and_touch.hxx +5 -2
  39. data/ext/couchbase/operations/document_get_projected.hxx +12 -9
  40. data/ext/couchbase/operations/document_increment.hxx +6 -3
  41. data/ext/couchbase/operations/document_insert.hxx +5 -2
  42. data/ext/couchbase/operations/document_lookup_in.hxx +3 -0
  43. data/ext/couchbase/operations/document_mutate_in.hxx +6 -3
  44. data/ext/couchbase/operations/document_remove.hxx +3 -0
  45. data/ext/couchbase/operations/document_replace.hxx +5 -2
  46. data/ext/couchbase/operations/document_search.hxx +6 -7
  47. data/ext/couchbase/operations/document_touch.hxx +5 -2
  48. data/ext/couchbase/operations/document_unlock.hxx +3 -0
  49. data/ext/couchbase/operations/document_upsert.hxx +5 -2
  50. data/ext/couchbase/operations/query_index_build_deferred.hxx +3 -3
  51. data/ext/couchbase/operations/query_index_create.hxx +3 -3
  52. data/ext/couchbase/operations/query_index_drop.hxx +3 -3
  53. data/ext/couchbase/operations/query_index_get_all.hxx +3 -3
  54. data/ext/couchbase/operations/scope_create.hxx +4 -2
  55. data/ext/couchbase/operations/scope_drop.hxx +4 -2
  56. data/ext/couchbase/operations/scope_get_all.hxx +4 -2
  57. data/ext/couchbase/operations/search_index_analyze_document.hxx +2 -2
  58. data/ext/couchbase/operations/search_index_control_ingest.hxx +2 -2
  59. data/ext/couchbase/operations/search_index_control_plan_freeze.hxx +2 -2
  60. data/ext/couchbase/operations/search_index_control_query.hxx +2 -2
  61. data/ext/couchbase/operations/search_index_drop.hxx +2 -2
  62. data/ext/couchbase/operations/search_index_get.hxx +2 -2
  63. data/ext/couchbase/operations/search_index_get_all.hxx +2 -2
  64. data/ext/couchbase/operations/search_index_get_documents_count.hxx +2 -2
  65. data/ext/couchbase/operations/search_index_upsert.hxx +2 -2
  66. data/ext/couchbase/origin.hxx +148 -0
  67. data/ext/couchbase/protocol/cmd_cluster_map_change_notification.hxx +1 -6
  68. data/ext/couchbase/protocol/cmd_decrement.hxx +5 -5
  69. data/ext/couchbase/protocol/cmd_get_and_touch.hxx +5 -5
  70. data/ext/couchbase/protocol/cmd_get_cluster_config.hxx +1 -6
  71. data/ext/couchbase/protocol/cmd_increment.hxx +5 -5
  72. data/ext/couchbase/protocol/cmd_info.hxx +0 -11
  73. data/ext/couchbase/protocol/cmd_insert.hxx +5 -5
  74. data/ext/couchbase/protocol/cmd_mutate_in.hxx +6 -6
  75. data/ext/couchbase/protocol/cmd_replace.hxx +5 -5
  76. data/ext/couchbase/protocol/cmd_touch.hxx +1 -1
  77. data/ext/couchbase/protocol/cmd_upsert.hxx +5 -5
  78. data/ext/couchbase/timeout_defaults.hxx +7 -0
  79. data/ext/couchbase/utils/connection_string.hxx +139 -0
  80. data/ext/extconf.rb +44 -11
  81. data/ext/test/main.cxx +93 -15
  82. data/ext/third_party/http_parser/Makefile +160 -0
  83. data/ext/third_party/json/Makefile +77 -0
  84. data/lib/couchbase/analytics_options.rb +18 -4
  85. data/lib/couchbase/binary_collection.rb +2 -2
  86. data/lib/couchbase/binary_collection_options.rb +2 -2
  87. data/lib/couchbase/bucket.rb +4 -4
  88. data/lib/couchbase/cluster.rb +60 -46
  89. data/lib/couchbase/collection.rb +13 -13
  90. data/lib/couchbase/collection_options.rb +15 -9
  91. data/{bin/console → lib/couchbase/datastructures.rb} +4 -7
  92. data/lib/couchbase/datastructures/couchbase_list.rb +171 -0
  93. data/lib/couchbase/datastructures/couchbase_map.rb +205 -0
  94. data/lib/couchbase/datastructures/couchbase_queue.rb +145 -0
  95. data/lib/couchbase/datastructures/couchbase_set.rb +138 -0
  96. data/lib/couchbase/errors.rb +66 -63
  97. data/lib/couchbase/libcouchbase.bundle +0 -0
  98. data/lib/couchbase/management/user_manager.rb +1 -1
  99. data/lib/couchbase/mutation_state.rb +1 -0
  100. data/lib/couchbase/query_options.rb +25 -2
  101. data/lib/couchbase/scope.rb +0 -7
  102. data/lib/couchbase/search_options.rb +7 -0
  103. data/lib/couchbase/version.rb +1 -1
  104. data/lib/couchbase/view_options.rb +4 -3
  105. metadata +20 -82
  106. data/.github/workflows/tests-6.0.3.yml +0 -52
  107. data/.github/workflows/tests-dev-preview.yml +0 -55
  108. data/.github/workflows/tests.yml +0 -50
  109. data/.gitignore +0 -20
  110. data/.gitmodules +0 -21
  111. data/.idea/.gitignore +0 -5
  112. data/.idea/dictionaries/gem_terms.xml +0 -18
  113. data/.idea/inspectionProfiles/Project_Default.xml +0 -8
  114. data/.idea/vcs.xml +0 -13
  115. data/bin/check-cluster +0 -31
  116. data/bin/fetch-stats +0 -19
  117. data/bin/init-cluster +0 -82
  118. data/bin/jenkins/build-extension +0 -35
  119. data/bin/jenkins/install-dependencies +0 -47
  120. data/bin/jenkins/test-with-cbdyncluster +0 -58
  121. data/bin/setup +0 -24
  122. data/ext/couchbase/configuration_monitor.hxx +0 -93
  123. data/ext/couchbase/operations/command.hxx +0 -163
  124. data/rbi/couchbase.rbi +0 -79
@@ -41,6 +41,11 @@ struct binary_header {
41
41
  std::uint32_t bodylen;
42
42
  std::uint32_t opaque;
43
43
  std::uint64_t cas;
44
+
45
+ std::uint16_t status()
46
+ {
47
+ return htons(specific);
48
+ }
44
49
  };
45
50
 
46
51
  struct mcbp_message {
@@ -27,6 +27,7 @@
27
27
 
28
28
  #include <io/mcbp_message.hxx>
29
29
  #include <io/mcbp_parser.hxx>
30
+ #include <io/streams.hxx>
30
31
 
31
32
  #include <timeout_defaults.hxx>
32
33
 
@@ -48,6 +49,7 @@
48
49
 
49
50
  #include <spdlog/fmt/bin_to_hex.h>
50
51
 
52
+ #include <origin.hxx>
51
53
  #include <errors.hxx>
52
54
  #include <version.hxx>
53
55
 
@@ -118,8 +120,8 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
118
120
 
119
121
  explicit bootstrap_handler(std::shared_ptr<mcbp_session> session)
120
122
  : session_(session)
121
- , sasl_([this]() -> std::string { return session_->username_; },
122
- [this]() -> std::string { return session_->password_; },
123
+ , sasl_([origin = session_->origin_]() -> std::string { return origin.get_username(); },
124
+ [origin = session_->origin_]() -> std::string { return origin.get_password(); },
123
125
  { "SCRAM-SHA512", "SCRAM-SHA256", "SCRAM-SHA1", "PLAIN" })
124
126
  {
125
127
  tao::json::value user_agent{
@@ -155,10 +157,8 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
155
157
 
156
158
  void complete(std::error_code ec)
157
159
  {
160
+ stopped_ = true;
158
161
  session_->invoke_bootstrap_handler(ec);
159
- if (!ec) {
160
- session_->handler_ = std::make_unique<normal_handler>(session_);
161
- }
162
162
  }
163
163
 
164
164
  void auth_success()
@@ -282,7 +282,7 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
282
282
  spdlog::warn("{} this server does not support GCCCP, open bucket before making any cluster-level command",
283
283
  session_->log_prefix_);
284
284
  session_->update_configuration(
285
- make_blank_configuration(session_->endpoint_.address().to_string(), session_->endpoint_.port(), 0));
285
+ make_blank_configuration(session_->endpoint_address_, session_->endpoint_.port(), 0));
286
286
  complete({});
287
287
  } else {
288
288
  spdlog::warn("{} unexpected message status during bootstrap: {} (opcode={})",
@@ -374,8 +374,9 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
374
374
  opaque,
375
375
  status,
376
376
  ec.message());
377
- handler->second(ec, std::move(msg));
377
+ auto fun = handler->second;
378
378
  session_->command_handlers_.erase(handler);
379
+ fun(ec, std::move(msg));
379
380
  } else {
380
381
  spdlog::debug("{} unexpected orphan response opcode={}, opaque={}",
381
382
  session_->log_prefix_,
@@ -415,10 +416,7 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
415
416
 
416
417
  void fetch_config(std::error_code ec)
417
418
  {
418
- if (ec == asio::error::operation_aborted) {
419
- return;
420
- }
421
- if (stopped_ || !session_) {
419
+ if (ec == asio::error::operation_aborted || stopped_ || !session_) {
422
420
  return;
423
421
  }
424
422
  protocol::client_request<protocol::get_cluster_config_request_body> req;
@@ -430,21 +428,46 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
430
428
  };
431
429
 
432
430
  public:
431
+ mcbp_session() = delete;
433
432
  mcbp_session(const std::string& client_id,
434
433
  asio::io_context& ctx,
434
+ const couchbase::origin& origin,
435
435
  std::optional<std::string> bucket_name = {},
436
436
  std::vector<protocol::hello_feature> known_features = {})
437
437
  : client_id_(client_id)
438
438
  , id_(uuid::to_string(uuid::random()))
439
439
  , ctx_(ctx)
440
440
  , resolver_(ctx_)
441
- , strand_(asio::make_strand(ctx_))
442
- , socket_(strand_)
443
- , deadline_timer_(ctx_)
441
+ , stream_(std::make_unique<plain_stream_impl>(ctx_))
442
+ , bootstrap_deadline_(ctx_)
443
+ , connection_deadline_(ctx_)
444
+ , retry_backoff_(ctx_)
445
+ , origin_(origin)
444
446
  , bucket_name_(std::move(bucket_name))
445
447
  , supported_features_(known_features)
446
448
  {
447
- log_prefix_ = fmt::format("[{}/{}/{}]", client_id_, id_, bucket_name_.value_or("-"));
449
+ log_prefix_ = fmt::format("[{}/{}/{}/{}]", stream_->log_prefix(), client_id_, id_, bucket_name_.value_or("-"));
450
+ }
451
+
452
+ mcbp_session(const std::string& client_id,
453
+ asio::io_context& ctx,
454
+ asio::ssl::context& tls,
455
+ const couchbase::origin& origin,
456
+ std::optional<std::string> bucket_name = {},
457
+ std::vector<protocol::hello_feature> known_features = {})
458
+ : client_id_(client_id)
459
+ , id_(uuid::to_string(uuid::random()))
460
+ , ctx_(ctx)
461
+ , resolver_(ctx_)
462
+ , stream_(std::make_unique<tls_stream_impl>(ctx_, tls))
463
+ , bootstrap_deadline_(ctx_)
464
+ , connection_deadline_(ctx_)
465
+ , retry_backoff_(ctx_)
466
+ , origin_(origin)
467
+ , bucket_name_(std::move(bucket_name))
468
+ , supported_features_(known_features)
469
+ {
470
+ log_prefix_ = fmt::format("[{}/{}/{}/{}]", stream_->log_prefix(), client_id_, id_, bucket_name_.value_or("-"));
448
471
  }
449
472
 
450
473
  ~mcbp_session()
@@ -452,17 +475,53 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
452
475
  stop();
453
476
  }
454
477
 
455
- void bootstrap(const std::string& hostname,
456
- const std::string& service,
457
- const std::string& username,
458
- const std::string& password,
459
- std::function<void(std::error_code, configuration)>&& handler)
478
+ [[nodiscard]] const std::string& log_prefix() const
479
+ {
480
+ return log_prefix_;
481
+ }
482
+
483
+ void bootstrap(std::function<void(std::error_code, configuration)>&& handler)
460
484
  {
461
- username_ = username;
462
- password_ = password;
463
485
  bootstrap_handler_ = std::move(handler);
464
- resolver_.async_resolve(
465
- hostname, service, std::bind(&mcbp_session::on_resolve, this, std::placeholders::_1, std::placeholders::_2));
486
+ bootstrap_deadline_.expires_after(timeout_defaults::bootstrap_timeout);
487
+ bootstrap_deadline_.async_wait([self = shared_from_this()](std::error_code ec) {
488
+ if (ec == asio::error::operation_aborted || self->stopped_) {
489
+ return;
490
+ }
491
+ spdlog::warn("{} unable to bootstrap in time", self->log_prefix_);
492
+ self->bootstrap_handler_(std::make_error_code(error::common_errc::unambiguous_timeout), {});
493
+ self->bootstrap_handler_ = nullptr;
494
+ self->stop();
495
+ });
496
+ initiate_bootstrap();
497
+ }
498
+
499
+ void initiate_bootstrap()
500
+ {
501
+ if (stopped_) {
502
+ return;
503
+ }
504
+ if (origin_.exhausted()) {
505
+ auto backoff = std::chrono::milliseconds(500);
506
+ spdlog::debug("{} reached the end of list of bootstrap nodes, waiting for {}ms before restart", log_prefix_, backoff.count());
507
+ retry_backoff_.expires_after(backoff);
508
+ retry_backoff_.async_wait([self = shared_from_this()](std::error_code ec) mutable {
509
+ if (ec == asio::error::operation_aborted || self->stopped_) {
510
+ return;
511
+ }
512
+ self->origin_.restart();
513
+ self->initiate_bootstrap();
514
+ });
515
+ return;
516
+ }
517
+ std::string service;
518
+ std::tie(bootstrap_hostname_, service) = origin_.next_address();
519
+ log_prefix_ = fmt::format(
520
+ "[{}/{}/{}/{}] <{}:{}>", stream_->log_prefix(), client_id_, id_, bucket_name_.value_or("-"), bootstrap_hostname_, service);
521
+ spdlog::debug("{} attempt to establish MCBP connection", log_prefix_);
522
+ resolver_.async_resolve(bootstrap_hostname_,
523
+ service,
524
+ std::bind(&mcbp_session::on_resolve, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
466
525
  }
467
526
 
468
527
  [[nodiscard]] const std::string& id() const
@@ -476,14 +535,26 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
476
535
  return;
477
536
  }
478
537
  stopped_ = true;
479
- deadline_timer_.cancel();
538
+ bootstrap_deadline_.cancel();
539
+ connection_deadline_.cancel();
540
+ retry_backoff_.cancel();
480
541
  resolver_.cancel();
481
- if (socket_.is_open()) {
482
- socket_.close();
542
+ if (stream_->is_open()) {
543
+ stream_->close();
544
+ }
545
+ auto ec = std::make_error_code(error::common_errc::request_canceled);
546
+ if (!bootstrapped_ && bootstrap_handler_) {
547
+ bootstrap_handler_(ec, {});
548
+ bootstrap_handler_ = nullptr;
483
549
  }
484
550
  if (handler_) {
485
551
  handler_->stop();
486
552
  }
553
+ for (auto& handler : command_handlers_) {
554
+ spdlog::debug("{} MCBP cancel operation during session close, opaque={}, ec={}", log_prefix_, handler.first, ec.message());
555
+ handler.second(ec, {});
556
+ }
557
+ command_handlers_.clear();
487
558
  }
488
559
 
489
560
  void write(const std::vector<uint8_t>& buf)
@@ -491,6 +562,11 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
491
562
  if (stopped_) {
492
563
  return;
493
564
  }
565
+ std::uint32_t opaque{ 0 };
566
+ std::memcpy(&opaque, buf.data() + 12, sizeof(opaque));
567
+ spdlog::debug("{} MCBP send, opaque={}, {:n}", log_prefix_, opaque, spdlog::to_hex(buf.begin(), buf.begin() + 24));
568
+ SPDLOG_TRACE("{} MCBP send, opaque={}{:a}", log_prefix_, opaque, spdlog::to_hex(data));
569
+ std::scoped_lock lock(output_buffer_mutex_);
494
570
  output_buffer_.push_back(buf);
495
571
  }
496
572
 
@@ -516,14 +592,15 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
516
592
  std::function<void(std::error_code, io::mcbp_message&&)> handler)
517
593
  {
518
594
  if (stopped_) {
595
+ spdlog::warn("{} MCBP cancel operation, while trying to write to closed session opaque={}", log_prefix_, opaque);
596
+ handler(std::make_error_code(error::common_errc::request_canceled), {});
519
597
  return;
520
598
  }
521
- spdlog::trace(
522
- "{} MCBP send, opaque={}{:a}", log_prefix_, endpoint_.address().to_string(), endpoint_.port(), opaque, spdlog::to_hex(data));
523
599
  command_handlers_.emplace(opaque, std::move(handler));
524
- if (bootstrapped_ && socket_.is_open()) {
600
+ if (bootstrapped_ && stream_->is_open()) {
525
601
  write_and_flush(data);
526
602
  } else {
603
+ std::scoped_lock lock(pending_buffer_mutex_);
527
604
  pending_buffer_.push_back(data);
528
605
  }
529
606
  }
@@ -572,6 +649,11 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
572
649
  return config_->index_for_this_node();
573
650
  }
574
651
 
652
+ [[nodiscard]] const std::string& bootstrap_hostname() const
653
+ {
654
+ return bootstrap_hostname_;
655
+ }
656
+
575
657
  [[nodiscard]] uint32_t next_opaque()
576
658
  {
577
659
  return ++opaque_;
@@ -717,7 +799,15 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
717
799
 
718
800
  void update_configuration(configuration&& config)
719
801
  {
802
+ if (stopped_) {
803
+ return;
804
+ }
720
805
  if (!config_ || config.rev > config_->rev) {
806
+ for (auto& node : config.nodes) {
807
+ if (node.this_node && node.hostname.empty()) {
808
+ node.hostname = endpoint_address_;
809
+ }
810
+ }
721
811
  config_.emplace(config);
722
812
  spdlog::debug("{} received new configuration: {}", log_prefix_, config_.value());
723
813
  }
@@ -730,6 +820,9 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
730
820
 
731
821
  void update_collection_uid(const std::string& path, std::uint32_t uid)
732
822
  {
823
+ if (stopped_) {
824
+ return;
825
+ }
733
826
  collection_cache_.update(path, uid);
734
827
  }
735
828
 
@@ -737,13 +830,16 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
737
830
  void invoke_bootstrap_handler(std::error_code ec)
738
831
  {
739
832
  if (!bootstrapped_ && bootstrap_handler_) {
833
+ bootstrap_deadline_.cancel();
740
834
  bootstrap_handler_(ec, config_.value_or(configuration{}));
741
835
  bootstrap_handler_ = nullptr;
742
836
  }
743
- bootstrapped_ = true;
744
837
  if (ec) {
745
- stop();
838
+ return stop();
746
839
  }
840
+ bootstrapped_ = true;
841
+ handler_ = std::make_unique<normal_handler>(shared_from_this());
842
+ std::scoped_lock lock(pending_buffer_mutex_);
747
843
  if (!pending_buffer_.empty()) {
748
844
  for (const auto& buf : pending_buffer_) {
749
845
  write(buf);
@@ -760,11 +856,12 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
760
856
  }
761
857
  if (ec) {
762
858
  spdlog::error("{} error on resolve: {}", log_prefix_, ec.message());
763
- return invoke_bootstrap_handler(std::make_error_code(error::network_errc::resolve_failure));
859
+ return initiate_bootstrap();
764
860
  }
765
861
  endpoints_ = endpoints;
766
862
  do_connect(endpoints_.begin());
767
- deadline_timer_.async_wait(std::bind(&mcbp_session::check_deadline, this, std::placeholders::_1));
863
+ connection_deadline_.expires_after(timeout_defaults::connect_timeout);
864
+ connection_deadline_.async_wait(std::bind(&mcbp_session::check_deadline, shared_from_this(), std::placeholders::_1));
768
865
  }
769
866
 
770
867
  void do_connect(asio::ip::tcp::resolver::results_type::iterator it)
@@ -774,12 +871,11 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
774
871
  }
775
872
  if (it != endpoints_.end()) {
776
873
  spdlog::debug("{} connecting to {}:{}", log_prefix_, it->endpoint().address().to_string(), it->endpoint().port());
777
- deadline_timer_.expires_after(timeout_defaults::connect_timeout);
778
- socket_.async_connect(it->endpoint(), std::bind(&mcbp_session::on_connect, this, std::placeholders::_1, it));
874
+ connection_deadline_.expires_after(timeout_defaults::connect_timeout);
875
+ stream_->async_connect(it->endpoint(), std::bind(&mcbp_session::on_connect, shared_from_this(), std::placeholders::_1, it));
779
876
  } else {
780
- spdlog::error("{} no more endpoints left to connect", log_prefix_);
781
- invoke_bootstrap_handler(std::make_error_code(error::network_errc::no_endpoints_left));
782
- stop();
877
+ spdlog::error("{} no more endpoints left to connect, will try another address", log_prefix_);
878
+ return initiate_bootstrap();
783
879
  }
784
880
  }
785
881
 
@@ -788,34 +884,39 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
788
884
  if (stopped_) {
789
885
  return;
790
886
  }
791
- if (!socket_.is_open() || ec) {
887
+ if (!stream_->is_open() || ec) {
888
+ spdlog::warn(
889
+ "{} unable to connect to {}:{}: {}", log_prefix_, it->endpoint().address().to_string(), it->endpoint().port(), ec.message());
792
890
  do_connect(++it);
793
891
  } else {
794
- socket_.set_option(asio::ip::tcp::no_delay{ true });
795
- socket_.set_option(asio::socket_base::keep_alive{ true });
892
+ stream_->set_options();
796
893
  endpoint_ = it->endpoint();
797
- spdlog::debug("{} connected to {}:{}", log_prefix_, endpoint_.address().to_string(), it->endpoint().port());
798
- log_prefix_ = fmt::format(
799
- "[{}/{}/{}] <{}:{}>", client_id_, id_, bucket_name_.value_or("-"), endpoint_.address().to_string(), endpoint_.port());
894
+ endpoint_address_ = endpoint_.address().to_string();
895
+ spdlog::debug("{} connected to {}:{}", log_prefix_, endpoint_address_, it->endpoint().port());
896
+ log_prefix_ = fmt::format("[{}/{}/{}/{}] <{}/{}:{}>",
897
+ stream_->log_prefix(),
898
+ client_id_,
899
+ id_,
900
+ bucket_name_.value_or("-"),
901
+ bootstrap_hostname_,
902
+ endpoint_address_,
903
+ endpoint_.port());
800
904
  handler_ = std::make_unique<bootstrap_handler>(shared_from_this());
801
- deadline_timer_.expires_at(asio::steady_timer::time_point::max());
802
- deadline_timer_.cancel();
905
+ connection_deadline_.expires_at(asio::steady_timer::time_point::max());
906
+ connection_deadline_.cancel();
803
907
  }
804
908
  }
805
909
 
806
910
  void check_deadline(std::error_code ec)
807
911
  {
808
- if (ec == asio::error::operation_aborted) {
809
- return;
810
- }
811
- if (stopped_) {
912
+ if (ec == asio::error::operation_aborted || stopped_) {
812
913
  return;
813
914
  }
814
- if (deadline_timer_.expiry() <= asio::steady_timer::clock_type::now()) {
815
- socket_.close();
816
- deadline_timer_.expires_at(asio::steady_timer::time_point::max());
915
+ if (connection_deadline_.expiry() <= asio::steady_timer::clock_type::now()) {
916
+ stream_->close();
917
+ connection_deadline_.expires_at(asio::steady_timer::time_point::max());
817
918
  }
818
- deadline_timer_.async_wait(std::bind(&mcbp_session::check_deadline, this, std::placeholders::_1));
919
+ connection_deadline_.async_wait(std::bind(&mcbp_session::check_deadline, shared_from_this(), std::placeholders::_1));
819
920
  }
820
921
 
821
922
  void do_read()
@@ -827,36 +928,44 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
827
928
  return;
828
929
  }
829
930
  reading_ = true;
830
- socket_.async_read_some(asio::buffer(input_buffer_),
831
- [self = shared_from_this()](std::error_code ec, std::size_t bytes_transferred) {
832
- if (ec == asio::error::operation_aborted || self->stopped_) {
833
- return;
834
- }
835
- if (ec) {
836
- spdlog::error("{} IO error while reading from the socket: {}", self->log_prefix_, ec.message());
837
- return self->stop();
838
- }
839
- self->parser_.feed(self->input_buffer_.data(), self->input_buffer_.data() + ssize_t(bytes_transferred));
840
-
841
- for (;;) {
842
- mcbp_message msg{};
843
- switch (self->parser_.next(msg)) {
844
- case mcbp_parser::ok:
845
- spdlog::trace("{} MCBP recv, opaque={}{:a}{:a}",
846
- self->log_prefix_,
847
- msg.header.opaque,
848
- spdlog::to_hex(msg.header_data()),
849
- spdlog::to_hex(msg.body));
850
- self->handler_->handle(std::move(msg));
851
- break;
852
- case mcbp_parser::need_data:
853
- self->reading_ = false;
854
- return self->do_read();
855
- case mcbp_parser::failure:
856
- return self->stop();
857
- }
858
- }
859
- });
931
+ stream_->async_read_some(
932
+ asio::buffer(input_buffer_), [self = shared_from_this()](std::error_code ec, std::size_t bytes_transferred) {
933
+ if (ec == asio::error::operation_aborted || self->stopped_) {
934
+ return;
935
+ }
936
+ if (ec) {
937
+ spdlog::error("{} IO error while reading from the socket: {}", self->log_prefix_, ec.message());
938
+ return self->stop();
939
+ }
940
+ self->parser_.feed(self->input_buffer_.data(), self->input_buffer_.data() + ssize_t(bytes_transferred));
941
+
942
+ for (;;) {
943
+ mcbp_message msg{};
944
+ switch (self->parser_.next(msg)) {
945
+ case mcbp_parser::ok:
946
+ spdlog::debug(
947
+ "{} MCBP recv, opaque={}, {:n}", self->log_prefix_, msg.header.opaque, spdlog::to_hex(msg.header_data()));
948
+ SPDLOG_TRACE("{} MCBP recv, opaque={}{:a}{:a}",
949
+ self->log_prefix_,
950
+ msg.header.opaque,
951
+ spdlog::to_hex(msg.header_data()),
952
+ spdlog::to_hex(msg.body));
953
+ self->handler_->handle(std::move(msg));
954
+ if (self->stopped_) {
955
+ return;
956
+ }
957
+ break;
958
+ case mcbp_parser::need_data:
959
+ self->reading_ = false;
960
+ if (!self->stopped_) {
961
+ self->do_read();
962
+ }
963
+ return;
964
+ case mcbp_parser::failure:
965
+ return self->stop();
966
+ }
967
+ }
968
+ });
860
969
  }
861
970
 
862
971
  void do_write()
@@ -864,7 +973,8 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
864
973
  if (stopped_) {
865
974
  return;
866
975
  }
867
- if (!writing_buffer_.empty()) {
976
+ std::scoped_lock lock(writing_buffer_mutex_, output_buffer_mutex_);
977
+ if (!writing_buffer_.empty() || output_buffer_.empty()) {
868
978
  return;
869
979
  }
870
980
  std::swap(writing_buffer_, output_buffer_);
@@ -873,18 +983,19 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
873
983
  for (auto& buf : writing_buffer_) {
874
984
  buffers.emplace_back(asio::buffer(buf));
875
985
  }
876
- asio::async_write(socket_, buffers, [self = shared_from_this()](std::error_code ec, std::size_t /*unused*/) {
877
- if (self->stopped_) {
986
+ stream_->async_write(buffers, [self = shared_from_this()](std::error_code ec, std::size_t /*unused*/) {
987
+ if (ec == asio::error::operation_aborted || self->stopped_) {
878
988
  return;
879
989
  }
880
990
  if (ec) {
881
991
  spdlog::error("{} IO error while writing to the socket: {}", self->log_prefix_, ec.message());
882
992
  return self->stop();
883
993
  }
884
- self->writing_buffer_.clear();
885
- if (!self->output_buffer_.empty()) {
886
- self->do_write();
994
+ {
995
+ std::scoped_lock inner_lock(self->writing_buffer_mutex_);
996
+ self->writing_buffer_.clear();
887
997
  }
998
+ self->do_write();
888
999
  self->do_read();
889
1000
  });
890
1001
  }
@@ -893,13 +1004,15 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
893
1004
  std::string id_;
894
1005
  asio::io_context& ctx_;
895
1006
  asio::ip::tcp::resolver resolver_;
896
- asio::strand<asio::io_context::executor_type> strand_;
897
- asio::ip::tcp::socket socket_;
898
- asio::steady_timer deadline_timer_;
1007
+ std::unique_ptr<stream_impl> stream_;
1008
+ asio::steady_timer bootstrap_deadline_;
1009
+ asio::steady_timer connection_deadline_;
1010
+ asio::steady_timer retry_backoff_;
1011
+ couchbase::origin origin_;
899
1012
  std::optional<std::string> bucket_name_;
900
1013
  mcbp_parser parser_;
901
1014
  std::unique_ptr<message_handler> handler_;
902
- std::function<void(std::error_code, configuration)> bootstrap_handler_;
1015
+ std::function<void(std::error_code, const configuration&)> bootstrap_handler_{};
903
1016
  std::map<uint32_t, std::function<void(std::error_code, io::mcbp_message&&)>> command_handlers_{};
904
1017
 
905
1018
  bool bootstrapped_{ false };
@@ -910,14 +1023,16 @@ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
910
1023
 
911
1024
  std::atomic<std::uint32_t> opaque_{ 0 };
912
1025
 
913
- std::string username_;
914
- std::string password_;
915
-
916
1026
  std::array<std::uint8_t, 16384> input_buffer_{};
917
1027
  std::vector<std::vector<std::uint8_t>> output_buffer_{};
918
1028
  std::vector<std::vector<std::uint8_t>> pending_buffer_{};
919
1029
  std::vector<std::vector<std::uint8_t>> writing_buffer_{};
1030
+ std::mutex output_buffer_mutex_{};
1031
+ std::mutex pending_buffer_mutex_{};
1032
+ std::mutex writing_buffer_mutex_{};
1033
+ std::string bootstrap_hostname_{};
920
1034
  asio::ip::tcp::endpoint endpoint_{}; // connected endpoint
1035
+ std::string endpoint_address_{}; // cached string with endpoint address
921
1036
  asio::ip::tcp::resolver::results_type endpoints_;
922
1037
  std::vector<protocol::hello_feature> supported_features_;
923
1038
  std::optional<configuration> config_;