couchbase 3.0.0.beta.1-universal-darwin-19 → 3.0.0-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 (104) 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/libcouchbase.bundle +0 -0
  90. data/lib/couchbase/management/analytics_index_manager.rb +37 -37
  91. data/lib/couchbase/management/bucket_manager.rb +25 -25
  92. data/lib/couchbase/management/collection_manager.rb +3 -3
  93. data/lib/couchbase/management/query_index_manager.rb +59 -14
  94. data/lib/couchbase/management/search_index_manager.rb +15 -12
  95. data/lib/couchbase/management/user_manager.rb +1 -1
  96. data/lib/couchbase/management/view_index_manager.rb +11 -5
  97. data/lib/couchbase/mutation_state.rb +12 -0
  98. data/lib/couchbase/query_options.rb +23 -9
  99. data/lib/couchbase/scope.rb +61 -1
  100. data/lib/couchbase/search_options.rb +40 -27
  101. data/lib/couchbase/subdoc.rb +31 -28
  102. data/lib/couchbase/version.rb +2 -2
  103. data/lib/couchbase/view_options.rb +0 -1
  104. metadata +20 -7
@@ -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