couchbase 3.0.0.alpha.5 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) 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/management/user_manager.rb +1 -1
  98. data/lib/couchbase/mutation_state.rb +1 -0
  99. data/lib/couchbase/query_options.rb +25 -2
  100. data/lib/couchbase/scope.rb +0 -7
  101. data/lib/couchbase/search_options.rb +7 -0
  102. data/lib/couchbase/version.rb +1 -1
  103. data/lib/couchbase/view_options.rb +4 -3
  104. metadata +20 -82
  105. data/.github/workflows/tests-6.0.3.yml +0 -52
  106. data/.github/workflows/tests-dev-preview.yml +0 -55
  107. data/.github/workflows/tests.yml +0 -50
  108. data/.gitignore +0 -20
  109. data/.gitmodules +0 -21
  110. data/.idea/.gitignore +0 -5
  111. data/.idea/dictionaries/gem_terms.xml +0 -18
  112. data/.idea/inspectionProfiles/Project_Default.xml +0 -8
  113. data/.idea/vcs.xml +0 -13
  114. data/bin/check-cluster +0 -31
  115. data/bin/fetch-stats +0 -19
  116. data/bin/init-cluster +0 -82
  117. data/bin/jenkins/build-extension +0 -35
  118. data/bin/jenkins/install-dependencies +0 -47
  119. data/bin/jenkins/test-with-cbdyncluster +0 -58
  120. data/bin/setup +0 -24
  121. data/ext/couchbase/configuration_monitor.hxx +0 -93
  122. data/ext/couchbase/operations/command.hxx +0 -163
  123. 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_;