couchbase 3.0.0.beta.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +227 -0
  3. data/.rubocop_todo.yml +47 -0
  4. data/CONTRIBUTING.md +110 -0
  5. data/Gemfile +4 -0
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/couchbase.gemspec +40 -39
  9. data/examples/analytics.rb +123 -108
  10. data/examples/auth.rb +33 -0
  11. data/examples/crud.rb +16 -2
  12. data/examples/managing_analytics_indexes.rb +18 -4
  13. data/examples/managing_buckets.rb +17 -3
  14. data/examples/managing_collections.rb +22 -9
  15. data/examples/managing_query_indexes.rb +38 -18
  16. data/examples/managing_search_indexes.rb +21 -6
  17. data/examples/managing_view_indexes.rb +18 -4
  18. data/examples/query.rb +17 -3
  19. data/examples/query_with_consistency.rb +30 -20
  20. data/examples/search.rb +116 -101
  21. data/examples/search_with_consistency.rb +43 -30
  22. data/examples/subdocument.rb +42 -30
  23. data/examples/view.rb +19 -10
  24. data/ext/CMakeLists.txt +40 -2
  25. data/ext/build_version.hxx.in +1 -1
  26. data/ext/couchbase/bucket.hxx +190 -38
  27. data/ext/couchbase/cluster.hxx +22 -4
  28. data/ext/couchbase/configuration.hxx +14 -14
  29. data/ext/couchbase/couchbase.cxx +108 -12
  30. data/ext/couchbase/error_map.hxx +202 -2
  31. data/ext/couchbase/errors.hxx +8 -2
  32. data/ext/couchbase/io/dns_client.hxx +6 -6
  33. data/ext/couchbase/io/http_command.hxx +2 -2
  34. data/ext/couchbase/io/http_session.hxx +7 -11
  35. data/ext/couchbase/io/http_session_manager.hxx +3 -3
  36. data/ext/couchbase/io/mcbp_command.hxx +101 -44
  37. data/ext/couchbase/io/mcbp_session.hxx +144 -49
  38. data/ext/couchbase/io/retry_action.hxx +30 -0
  39. data/ext/couchbase/io/retry_context.hxx +39 -0
  40. data/ext/couchbase/io/retry_orchestrator.hxx +96 -0
  41. data/ext/couchbase/io/retry_reason.hxx +235 -0
  42. data/ext/couchbase/io/retry_strategy.hxx +156 -0
  43. data/ext/couchbase/operations/document_decrement.hxx +2 -0
  44. data/ext/couchbase/operations/document_exists.hxx +2 -0
  45. data/ext/couchbase/operations/document_get.hxx +2 -0
  46. data/ext/couchbase/operations/document_get_and_lock.hxx +2 -0
  47. data/ext/couchbase/operations/document_get_and_touch.hxx +2 -0
  48. data/ext/couchbase/operations/document_get_projected.hxx +2 -0
  49. data/ext/couchbase/operations/document_increment.hxx +2 -0
  50. data/ext/couchbase/operations/document_insert.hxx +2 -0
  51. data/ext/couchbase/operations/document_lookup_in.hxx +2 -0
  52. data/ext/couchbase/operations/document_mutate_in.hxx +3 -0
  53. data/ext/couchbase/operations/document_query.hxx +10 -0
  54. data/ext/couchbase/operations/document_remove.hxx +2 -0
  55. data/ext/couchbase/operations/document_replace.hxx +2 -0
  56. data/ext/couchbase/operations/document_search.hxx +8 -3
  57. data/ext/couchbase/operations/document_touch.hxx +2 -0
  58. data/ext/couchbase/operations/document_unlock.hxx +2 -0
  59. data/ext/couchbase/operations/document_upsert.hxx +2 -0
  60. data/ext/couchbase/operations/query_index_create.hxx +14 -4
  61. data/ext/couchbase/operations/query_index_drop.hxx +12 -2
  62. data/ext/couchbase/operations/query_index_get_all.hxx +11 -2
  63. data/ext/couchbase/origin.hxx +47 -17
  64. data/ext/couchbase/platform/backtrace.c +189 -0
  65. data/ext/couchbase/platform/backtrace.h +54 -0
  66. data/ext/couchbase/platform/terminate_handler.cc +122 -0
  67. data/ext/couchbase/platform/terminate_handler.h +36 -0
  68. data/ext/couchbase/protocol/cmd_get_cluster_config.hxx +6 -1
  69. data/ext/couchbase/protocol/status.hxx +14 -4
  70. data/ext/couchbase/version.hxx +2 -2
  71. data/ext/extconf.rb +39 -36
  72. data/ext/test/main.cxx +64 -16
  73. data/lib/couchbase.rb +0 -1
  74. data/lib/couchbase/analytics_options.rb +2 -4
  75. data/lib/couchbase/authenticator.rb +14 -0
  76. data/lib/couchbase/binary_collection.rb +9 -9
  77. data/lib/couchbase/binary_collection_options.rb +8 -6
  78. data/lib/couchbase/bucket.rb +18 -18
  79. data/lib/couchbase/cluster.rb +121 -90
  80. data/lib/couchbase/collection.rb +36 -38
  81. data/lib/couchbase/collection_options.rb +31 -17
  82. data/lib/couchbase/common_options.rb +1 -1
  83. data/lib/couchbase/datastructures/couchbase_list.rb +16 -16
  84. data/lib/couchbase/datastructures/couchbase_map.rb +18 -18
  85. data/lib/couchbase/datastructures/couchbase_queue.rb +13 -13
  86. data/lib/couchbase/datastructures/couchbase_set.rb +8 -7
  87. data/lib/couchbase/errors.rb +10 -3
  88. data/lib/couchbase/json_transcoder.rb +2 -2
  89. data/lib/couchbase/management/analytics_index_manager.rb +37 -37
  90. data/lib/couchbase/management/bucket_manager.rb +25 -25
  91. data/lib/couchbase/management/collection_manager.rb +3 -3
  92. data/lib/couchbase/management/query_index_manager.rb +59 -14
  93. data/lib/couchbase/management/search_index_manager.rb +15 -12
  94. data/lib/couchbase/management/user_manager.rb +1 -1
  95. data/lib/couchbase/management/view_index_manager.rb +11 -5
  96. data/lib/couchbase/mutation_state.rb +12 -0
  97. data/lib/couchbase/query_options.rb +23 -9
  98. data/lib/couchbase/scope.rb +61 -1
  99. data/lib/couchbase/search_options.rb +40 -27
  100. data/lib/couchbase/subdoc.rb +31 -28
  101. data/lib/couchbase/version.rb +2 -2
  102. data/lib/couchbase/view_options.rb +0 -1
  103. metadata +22 -9
@@ -1,6 +1,20 @@
1
- require 'couchbase'
1
+ # Copyright 2020 Couchbase, Inc.
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.
2
14
 
3
- include Couchbase
15
+ require "couchbase"
16
+
17
+ include Couchbase # rubocop:disable Style/MixinUsage for brevity
4
18
 
5
19
  options = Cluster::ClusterOptions.new
6
20
  options.authenticate("Administrator", "password")
@@ -18,27 +32,27 @@ rescue Error::IndexNotFound
18
32
  index.source_type = "couchbase"
19
33
  index.source_name = bucket_name
20
34
  index.params = {
21
- mapping: {
22
- types: {
23
- "knob" => {
24
- properties: {
25
- "name" => {
26
- fields: [
27
- {
28
- name: "name",
29
- type: "text",
30
- include_in_all: true,
31
- include_term_vectors: true,
32
- index: true,
33
- store: true,
34
- docvalues: true,
35
- }
36
- ]
37
- },
38
- }
39
- }
40
- }
41
- }
35
+ mapping: {
36
+ types: {
37
+ "knob" => {
38
+ properties: {
39
+ "name" => {
40
+ fields: [
41
+ {
42
+ name: "name",
43
+ type: "text",
44
+ include_in_all: true,
45
+ include_term_vectors: true,
46
+ index: true,
47
+ store: true,
48
+ docvalues: true,
49
+ },
50
+ ],
51
+ },
52
+ },
53
+ },
54
+ },
55
+ },
42
56
  }
43
57
 
44
58
  cluster.search_indexes.upsert_index(index)
@@ -48,6 +62,7 @@ rescue Error::IndexNotFound
48
62
  sleep(1)
49
63
  num = cluster.search_indexes.get_indexed_documents_count(search_index_name)
50
64
  break if num_indexed == num
65
+
51
66
  num_indexed = num
52
67
  puts "indexing #{search_index_name.inspect}: #{num_indexed} documents"
53
68
  end
@@ -59,9 +74,9 @@ collection = cluster.bucket(bucket_name).default_collection
59
74
  # and supply mutation tokens from those operations.
60
75
  random_string = ("a".."z").to_a.sample(10).join
61
76
  res = collection.upsert("user:#{random_string}", {
62
- "name" => "Brass Doorknob",
63
- "email" => "brass.doorknob@example.com",
64
- "data" => random_string,
77
+ "name" => "Brass Doorknob",
78
+ "email" => "brass.doorknob@example.com",
79
+ "data" => random_string,
65
80
  })
66
81
 
67
82
  state = MutationState.new(res.mutation_token)
@@ -74,10 +89,8 @@ options.consistent_with(state)
74
89
  res = cluster.search_query(search_index_name, query, options)
75
90
 
76
91
  res.rows.each do |row|
77
- if row.id == "user:#{random_string}"
78
- puts "--- Found our newly created document!"
79
- end
80
- if ENV['REMOVE_DOOR_KNOBS']
92
+ puts "--- Found our newly created document!" if row.id == "user:#{random_string}"
93
+ if ENV["REMOVE_DOOR_KNOBS"]
81
94
  puts "Removing #{row.id} (requested via env)"
82
95
  collection.remove(row.id)
83
96
  end
@@ -1,5 +1,19 @@
1
- require 'couchbase'
2
- include Couchbase
1
+ # Copyright 2020 Couchbase, Inc.
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
+ require "couchbase"
16
+ include Couchbase # rubocop:disable Style/MixinUsage for brevity
3
17
 
4
18
  options = Cluster::ClusterOptions.new
5
19
  options.authenticate("Administrator", "password")
@@ -8,44 +22,42 @@ bucket = cluster.bucket("default")
8
22
  collection = bucket.default_collection
9
23
 
10
24
  document = {
11
- name: "Douglas Reynholm",
12
- email: "douglas@reynholmindustries.com",
13
- addresses: {
14
- billing: {
15
- line1: "123 Any Street",
16
- line2: "Anytown",
17
- country: "United Kingdom"
18
- },
19
- delivery: {
20
- line1: "123 Any Street",
21
- line2: "Anytown",
22
- country: "United Kingdom"
23
- }
25
+ name: "Douglas Reynholm",
26
+ email: "douglas@reynholmindustries.com",
27
+ addresses: {
28
+ billing: {
29
+ line1: "123 Any Street",
30
+ line2: "Anytown",
31
+ country: "United Kingdom",
32
+ },
33
+ delivery: {
34
+ line1: "123 Any Street",
35
+ line2: "Anytown",
36
+ country: "United Kingdom",
24
37
  },
25
- purchases: {
26
- complete: [339, 976, 442, 666],
27
- abandoned: [157, 42, 999]
28
- }
38
+ },
39
+ purchases: {
40
+ complete: [339, 976, 442, 666],
41
+ abandoned: [157, 42, 999],
42
+ },
29
43
  }
30
44
  collection.upsert("customer123", document)
31
45
 
32
46
  res = collection.mutate_in("customer123", [
33
- MutateInSpec.upsert("fax", "311-555-0151")
34
- ])
47
+ MutateInSpec.upsert("fax", "311-555-0151"),
48
+ ])
35
49
  puts "The document has been modified successfully: cas=#{res.cas}"
36
50
 
37
51
  res = collection.mutate_in("customer123", [
38
- MutateInSpec.upsert("_framework.model_type", "Customer").xattr,
39
- MutateInSpec.remove("addresses.billing[2]"),
40
- MutateInSpec.replace("email", "dougr96@hotmail.com"),
41
- ])
52
+ MutateInSpec.upsert("_framework.model_type", "Customer").xattr,
53
+ MutateInSpec.remove("addresses.billing[2]"),
54
+ MutateInSpec.replace("email", "dougr96@hotmail.com"),
55
+ ])
42
56
  puts "The document has been modified successfully: cas=#{res.cas}"
43
57
 
44
58
  res = collection.lookup_in "customer123", [
45
- LookupInSpec.get("addresses.delivery.country"),
46
- LookupInSpec.exists("purchases.pending[-1]"),
59
+ LookupInSpec.get("addresses.delivery.country"),
60
+ LookupInSpec.exists("purchases.pending[-1]"),
47
61
  ]
48
62
  puts "The customer's delivery country is #{res.content(0)}"
49
- if res.exists?(1)
50
- puts "The customer has pending purchases"
51
- end
63
+ puts "The customer has pending purchases" if res.exists?(1)
@@ -1,8 +1,20 @@
1
- # coding: utf-8
1
+ # Copyright 2020 Couchbase, Inc.
2
2
  #
3
- require 'couchbase'
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
+ require "couchbase"
4
16
 
5
- include Couchbase
17
+ include Couchbase # rubocop:disable Style/MixinUsage for brevity
6
18
 
7
19
  options = Cluster::ClusterOptions.new
8
20
  options.authenticate("Administrator", "password")
@@ -26,17 +38,16 @@ options.order = :descending
26
38
  res = bucket.view_query("beer", "brewery_beers", options)
27
39
  puts "\nTotal documents in 'beer/brewery_beers' index: #{res.meta_data.total_rows}"
28
40
  puts "Last #{options.limit} documents:"
29
- res.rows.each_with_index do |row|
41
+ res.rows.each do |row|
30
42
  doc = collection.get(row.id)
31
43
  puts "#{row.id} (type: #{doc.content['type']}, key: #{row.key})"
32
44
  end
33
45
 
34
-
35
46
  random_number = rand(0..1_000_000_000)
36
47
  unique_brewery_id = "random_brewery:#{random_number}"
37
48
  collection.upsert("random_brewery:#{random_number}", {
38
- "name" => "Random brewery: #{random_number}",
39
- "type" => "brewery"
49
+ "name" => "Random brewery: #{random_number}",
50
+ "type" => "brewery",
40
51
  })
41
52
  puts "\nRequest with consistency. Generated brewery name: #{unique_brewery_id}"
42
53
  options = Bucket::ViewOptions.new
@@ -44,7 +55,5 @@ options.start_key = ["random_brewery:"]
44
55
  options.scan_consistency = :request_plus
45
56
  res = bucket.view_query("beer", "brewery_beers", options)
46
57
  res.rows.each do |row|
47
- if row.id == unique_brewery_id
48
- puts "Found newly created document: #{collection.get(row.id).content}"
49
- end
58
+ puts "Found newly created document: #{collection.get(row.id).content}" if row.id == unique_brewery_id
50
59
  end
@@ -8,6 +8,36 @@ if(CCACHE)
8
8
  set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
9
9
  endif()
10
10
 
11
+ include(CMakePushCheckState)
12
+ include(CheckSymbolExists)
13
+
14
+ cmake_push_check_state(RESET)
15
+ find_library(EXECINFO_LIBRARY NAMES execinfo)
16
+ if(EXECINFO_LIBRARY)
17
+ set(CMAKE_REQUIRED_LIBRARIES "${EXECINFO_LIBRARY}")
18
+ list(APPEND PLATFORM_LIBRARIES "${EXECINFO_LIBRARY}")
19
+ endif(EXECINFO_LIBRARY)
20
+ check_symbol_exists(backtrace execinfo.h HAVE_BACKTRACE)
21
+ cmake_pop_check_state()
22
+
23
+ cmake_push_check_state(RESET)
24
+ set(CMAKE_REQUIRED_DEFINITIONS "-D_GNU_SOURCE")
25
+ find_library(DL_LIBRARY NAMES dl)
26
+ if(DL_LIBRARY)
27
+ set(CMAKE_REQUIRED_LIBRARIES "${DL_LIBRARY}")
28
+ list(APPEND PLATFORM_LIBRARIES "${DL_LIBRARY}")
29
+ endif(DL_LIBRARY)
30
+ check_symbol_exists(dladdr dlfcn.h HAVE_DLADDR)
31
+ cmake_pop_check_state()
32
+
33
+ if(HAVE_BACKTRACE)
34
+ add_definitions(-DHAVE_BACKTRACE=1)
35
+ endif()
36
+
37
+ if(HAVE_DLADDR)
38
+ add_definitions(-DHAVE_DLADDR=1)
39
+ endif()
40
+
11
41
  add_subdirectory(third_party/gsl)
12
42
  add_subdirectory(third_party/json)
13
43
  add_subdirectory(third_party/spdlog)
@@ -90,6 +120,12 @@ else()
90
120
  -Wuseless-cast # warn if you perform a cast to the same type
91
121
  )
92
122
  endif()
123
+
124
+ if(HAVE_BACKTRACE OR HAVE_DLADDR)
125
+ target_compile_options(project_options INTERFACE -ggdb3)
126
+ target_link_libraries(project_options INTERFACE -rdynamic)
127
+ add_definitions(-D_GNU_SOURCE=1)
128
+ endif()
93
129
  endif()
94
130
 
95
131
  # Read more at https://wiki.wireshark.org/TLS
@@ -102,8 +138,10 @@ message(STATUS "OPENSSL_LIBRARIES: ${OPENSSL_LIBRARIES}")
102
138
 
103
139
  include_directories(${CMAKE_SOURCE_DIR}/couchbase)
104
140
 
105
- add_library(platform OBJECT couchbase/platform/string_hex.cc couchbase/platform/uuid.cc couchbase/platform/random.cc
106
- couchbase/platform/base64.cc)
141
+ add_library(
142
+ platform OBJECT couchbase/platform/string_hex.cc couchbase/platform/uuid.cc couchbase/platform/random.cc
143
+ couchbase/platform/base64.cc couchbase/platform/backtrace.c couchbase/platform/terminate_handler.cc)
144
+ target_include_directories(platform PRIVATE ${PROJECT_BINARY_DIR}/generated)
107
145
 
108
146
  add_library(cbcrypto OBJECT couchbase/cbcrypto/cbcrypto.cc)
109
147
 
@@ -22,5 +22,5 @@
22
22
  #define BACKEND_C_COMPILER "@CMAKE_C_COMPILER_ID@ @CMAKE_C_COMPILER_VERSION@"
23
23
  #define BACKEND_SYSTEM "@CMAKE_SYSTEM@"
24
24
  #define BACKEND_SYSTEM_PROCESSOR "@CMAKE_SYSTEM_PROCESSOR@"
25
- #define BACKEND_GIT_REVISION "f01d0c237750cc67ee75325ed160bc0da1d2bf7a"
25
+ #define BACKEND_GIT_REVISION "a23527c044e32c037f4a001c2b2a836716d85d35"
26
26
 
@@ -42,6 +42,7 @@ class bucket : public std::enable_shared_from_this<bucket>
42
42
  , origin_(std::move(origin))
43
43
  , known_features_(known_features)
44
44
  {
45
+ log_prefix_ = fmt::format("[{}/{}]", client_id_, name_);
45
46
  }
46
47
 
47
48
  ~bucket()
@@ -54,6 +55,142 @@ class bucket : public std::enable_shared_from_this<bucket>
54
55
  return name_;
55
56
  }
56
57
 
58
+ /**
59
+ * copies nodes from rhs that are not in lhs to output vector
60
+ */
61
+ static void diff_nodes(const std::vector<configuration::node>& lhs,
62
+ const std::vector<configuration::node>& rhs,
63
+ std::vector<configuration::node>& output)
64
+ {
65
+ for (const auto& re : rhs) {
66
+ bool known = false;
67
+ for (const auto& le : lhs) {
68
+ if (le.hostname == re.hostname && le.services_plain.management.value_or(0) == re.services_plain.management.value_or(0)) {
69
+ known = true;
70
+ break;
71
+ }
72
+ }
73
+ if (!known) {
74
+ output.push_back(re);
75
+ }
76
+ }
77
+ }
78
+
79
+ void update_config(const configuration& config)
80
+ {
81
+ if (!config_) {
82
+ spdlog::debug("{} initialize configuration rev={}", log_prefix_, config.rev);
83
+ } else if (config.rev > config_->rev) {
84
+ spdlog::debug("{} will update the configuration old={} -> new={}", log_prefix_, config_->rev, config.rev);
85
+ } else {
86
+ return;
87
+ }
88
+
89
+ std::vector<configuration::node> added{};
90
+ std::vector<configuration::node> removed{};
91
+ if (config_) {
92
+ diff_nodes(config_->nodes, config.nodes, added);
93
+ diff_nodes(config.nodes, config_->nodes, removed);
94
+ } else {
95
+ added = config.nodes;
96
+ }
97
+ config_ = config;
98
+ if (!added.empty() || removed.empty()) {
99
+ std::map<size_t, std::shared_ptr<io::mcbp_session>> new_sessions{};
100
+
101
+ for (auto entry : sessions_) {
102
+ std::size_t new_index = config.nodes.size() + 1;
103
+ for (const auto& node : config.nodes) {
104
+ if (entry.second->bootstrap_hostname() == node.hostname_for(origin_.options().network) &&
105
+ entry.second->bootstrap_port() ==
106
+ std::to_string(node.port_or(origin_.options().network, service_type::kv, origin_.options().enable_tls, 0))) {
107
+ new_index = node.index;
108
+ break;
109
+ }
110
+ }
111
+ if (new_index < config.nodes.size()) {
112
+ spdlog::debug(R"({} rev={}, preserve session="{}", address="{}:{}")",
113
+ log_prefix_,
114
+ config.rev,
115
+ entry.second->id(),
116
+ entry.second->bootstrap_hostname(),
117
+ entry.second->bootstrap_port());
118
+ new_sessions.emplace(new_index, std::move(entry.second));
119
+ } else {
120
+ spdlog::debug(R"({} rev={}, drop session="{}", address="{}:{}")",
121
+ log_prefix_,
122
+ config.rev,
123
+ entry.second->id(),
124
+ entry.second->bootstrap_hostname(),
125
+ entry.second->bootstrap_port());
126
+ entry.second.reset();
127
+ }
128
+ }
129
+
130
+ for (const auto& node : config.nodes) {
131
+ if (new_sessions.find(node.index) != new_sessions.end()) {
132
+ continue;
133
+ }
134
+
135
+ auto hostname = node.hostname_for(origin_.options().network);
136
+ auto port = node.port_or(origin_.options().network, service_type::kv, origin_.options().enable_tls, 0);
137
+ couchbase::origin origin(origin_.credentials(), hostname, port, origin_.options());
138
+ std::shared_ptr<io::mcbp_session> session;
139
+ if (origin_.options().enable_tls) {
140
+ session = std::make_shared<io::mcbp_session>(client_id_, ctx_, tls_, origin, name_, known_features_);
141
+ } else {
142
+ session = std::make_shared<io::mcbp_session>(client_id_, ctx_, origin, name_, known_features_);
143
+ }
144
+ spdlog::debug(R"({} rev={}, add session="{}", address="{}:{}")", log_prefix_, config.rev, session->id(), hostname, port);
145
+ session->bootstrap(
146
+ [self = shared_from_this(), session](std::error_code err, const configuration& cfg) {
147
+ if (!err) {
148
+ self->update_config(cfg);
149
+ session->on_configuration_update([self](const configuration& new_config) { self->update_config(new_config); });
150
+ session->on_stop([index = session->index(), self](io::retry_reason reason) {
151
+ if (reason == io::retry_reason::socket_closed_while_in_flight) {
152
+ self->restart_node(index);
153
+ }
154
+ });
155
+ }
156
+ },
157
+ true);
158
+ new_sessions.emplace(node.index, std::move(session));
159
+ }
160
+ sessions_ = new_sessions;
161
+ }
162
+ }
163
+
164
+ void restart_node(std::size_t index)
165
+ {
166
+ auto ptr = sessions_.find(index);
167
+ if (ptr == sessions_.end()) {
168
+ spdlog::debug(R"({} requested to restart session idx={}, which does not exist, ignoring)", log_prefix_, index);
169
+ return;
170
+ }
171
+ auto& old_session = ptr->second;
172
+ auto hostname = old_session->bootstrap_hostname();
173
+ auto port = old_session->bootstrap_port();
174
+ spdlog::debug(R"({} restarting session idx={}, id="{}", address="{}")", log_prefix_, index, old_session->id(), hostname, port);
175
+ couchbase::origin origin(origin_.credentials(), hostname, port, origin_.options());
176
+ sessions_.erase(ptr);
177
+ std::shared_ptr<io::mcbp_session> session;
178
+ if (origin_.options().enable_tls) {
179
+ session = std::make_shared<io::mcbp_session>(client_id_, ctx_, tls_, origin, name_, known_features_);
180
+ } else {
181
+ session = std::make_shared<io::mcbp_session>(client_id_, ctx_, origin, name_, known_features_);
182
+ }
183
+ session->bootstrap(
184
+ [self = shared_from_this(), session](std::error_code err, const configuration& config) {
185
+ if (!err) {
186
+ self->update_config(config);
187
+ session->on_configuration_update([self](const configuration& new_config) { self->update_config(new_config); });
188
+ }
189
+ },
190
+ true);
191
+ sessions_.emplace(index, std::move(session));
192
+ }
193
+
57
194
  template<typename Handler>
58
195
  void bootstrap(Handler&& handler)
59
196
  {
@@ -66,52 +203,37 @@ class bucket : public std::enable_shared_from_this<bucket>
66
203
  new_session->bootstrap([self = shared_from_this(), new_session, h = std::forward<Handler>(handler)](
67
204
  std::error_code ec, const configuration& cfg) mutable {
68
205
  if (!ec) {
69
- self->config_ = cfg;
70
206
  size_t this_index = new_session->index();
71
- self->sessions_.emplace(this_index, std::move(new_session));
72
- if (cfg.nodes.size() > 1) {
73
- for (const auto& n : cfg.nodes) {
74
- if (n.index != this_index) {
75
- couchbase::origin origin(
76
- self->origin_.get_username(),
77
- self->origin_.get_password(),
78
- n.hostname_for(self->origin_.options().network),
79
- n.port_or(self->origin_.options().network, service_type::kv, self->origin_.options().enable_tls, 0),
80
- self->origin_.options());
81
- std::shared_ptr<io::mcbp_session> s;
82
- if (self->origin_.options().enable_tls) {
83
- s = std::make_shared<io::mcbp_session>(
84
- self->client_id_, self->ctx_, self->tls_, origin, self->name_, self->known_features_);
85
- } else {
86
- s = std::make_shared<io::mcbp_session>(
87
- self->client_id_, self->ctx_, origin, self->name_, self->known_features_);
88
- }
89
- s->bootstrap([host = n.hostname, bucket = self->name_](std::error_code err, const configuration& /*config*/) {
90
- // TODO: retry, we know that auth is correct
91
- if (err) {
92
- spdlog::warn("unable to bootstrap node {} ({}): {}", host, bucket, err.message());
93
- }
94
- });
95
- self->sessions_.emplace(n.index, std::move(s));
96
- }
207
+ new_session->on_configuration_update([self](const configuration& config) { self->update_config(config); });
208
+ new_session->on_stop([this_index, self](io::retry_reason reason) {
209
+ if (reason == io::retry_reason::socket_closed_while_in_flight) {
210
+ self->restart_node(this_index);
97
211
  }
98
- }
99
- while (!self->deferred_commands_.empty()) {
100
- self->deferred_commands_.front()();
101
- self->deferred_commands_.pop();
102
- }
212
+ });
213
+
214
+ self->sessions_.emplace(this_index, std::move(new_session));
215
+ self->update_config(cfg);
216
+ self->drain_deferred_queue();
103
217
  }
104
218
  h(ec, cfg);
105
219
  });
106
220
  }
107
221
 
222
+ void drain_deferred_queue()
223
+ {
224
+ while (!deferred_commands_.empty()) {
225
+ deferred_commands_.front()();
226
+ deferred_commands_.pop();
227
+ }
228
+ }
229
+
108
230
  template<typename Request, typename Handler>
109
231
  void execute(Request request, Handler&& handler)
110
232
  {
111
233
  if (closed_) {
112
234
  return;
113
235
  }
114
- auto cmd = std::make_shared<operations::mcbp_command<Request>>(ctx_, request);
236
+ auto cmd = std::make_shared<operations::mcbp_command<bucket, Request>>(ctx_, shared_from_this(), request);
115
237
  cmd->start([cmd, handler = std::forward<Handler>(handler)](std::error_code ec, std::optional<io::mcbp_message> msg) mutable {
116
238
  using encoded_response_type = typename Request::encoded_response_type;
117
239
  handler(make_response(ec, cmd->request, msg ? encoded_response_type(*msg) : encoded_response_type{}));
@@ -129,18 +251,46 @@ class bucket : public std::enable_shared_from_this<bucket>
129
251
  return;
130
252
  }
131
253
  closed_ = true;
254
+
255
+ drain_deferred_queue();
132
256
  for (auto& session : sessions_) {
133
- session.second->stop();
257
+ session.second->stop(io::retry_reason::do_not_retry);
134
258
  }
135
259
  }
136
260
 
137
261
  template<typename Request>
138
- void map_and_send(std::shared_ptr<operations::mcbp_command<Request>> cmd)
262
+ void map_and_send(std::shared_ptr<operations::mcbp_command<bucket, Request>> cmd)
139
263
  {
140
- size_t index = 0;
264
+ if (closed_) {
265
+ return cmd->cancel(io::retry_reason::do_not_retry);
266
+ }
267
+ std::int16_t index = 0;
141
268
  std::tie(cmd->request.partition, index) = config_->map_key(cmd->request.id.key);
142
- auto session = sessions_.at(index);
143
- cmd->send_to(session);
269
+ if (index < 0) {
270
+ return io::retry_orchestrator::maybe_retry(
271
+ cmd->manager_, cmd, io::retry_reason::node_not_available, std::make_error_code(error::common_errc::request_canceled));
272
+ }
273
+ cmd->send_to(sessions_.at(static_cast<std::size_t>(index)));
274
+ }
275
+
276
+ template<typename Request>
277
+ void schedule_for_retry(std::shared_ptr<operations::mcbp_command<bucket, Request>> cmd, std::chrono::milliseconds duration)
278
+ {
279
+ if (closed_) {
280
+ return cmd->cancel(io::retry_reason::do_not_retry);
281
+ }
282
+ cmd->retry_backoff.expires_after(duration);
283
+ cmd->retry_backoff.async_wait([self = shared_from_this(), cmd](std::error_code ec) mutable {
284
+ if (ec == asio::error::operation_aborted) {
285
+ return;
286
+ }
287
+ self->map_and_send(cmd);
288
+ });
289
+ }
290
+
291
+ [[nodiscard]] const std::string& log_prefix()
292
+ {
293
+ return log_prefix_;
144
294
  }
145
295
 
146
296
  private:
@@ -157,5 +307,7 @@ class bucket : public std::enable_shared_from_this<bucket>
157
307
 
158
308
  bool closed_{ false };
159
309
  std::map<size_t, std::shared_ptr<io::mcbp_session>> sessions_{};
310
+
311
+ std::string log_prefix_{};
160
312
  };
161
313
  } // namespace couchbase