From 85a3342ced182ef49d033de9deeaa588da2c84fc Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Wed, 20 Feb 2019 11:44:38 -0800 Subject: [PATCH 01/70] NCBC-1845: Status code OperationTimeout (Code 512) with DNE Motivation ---------- Fixes a bug where Timedout may be assigned to an operation when it really is a KeyNotFound error (DocumentDoesNotExistException). Modifications ------------- - Ensure that Timeout is only assigned when the op has timed out can cannot be retried. Result ------ A Timeout status will not return when the server return KeyNotFound. Change-Id: Ie9814de4d5e5e29c3d9192544efa968961681f75 Reviewed-on: http://review.couchbase.org/105194 Reviewed-by: Mike Goldsmith Tested-by: Build Bot Reviewed-on: http://review.couchbase.org/105911 --- Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs | 4 ++-- Src/Couchbase/Core/Buckets/MemcachedRequestExecuter.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs b/Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs index d50908cd0..7d18f1230 100644 --- a/Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs +++ b/Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs @@ -611,7 +611,7 @@ public override IOperationResult SendWithRetry(IOperation operation) if (!operationResult.Success) { - if (operation.TimedOut()) + if (operation.TimedOut() && operationResult.ShouldRetry()) { const string msg = "The operation has timed out."; ((OperationResult)operationResult).Message = msg; @@ -690,7 +690,7 @@ public override IOperationResult SendWithRetry(IOperation operation) if (!operationResult.Success) { - if (operation.TimedOut()) + if (operation.TimedOut() && operationResult.ShouldRetry()) { const string msg = "The operation has timed out."; ((OperationResult)operationResult).Message = msg; diff --git a/Src/Couchbase/Core/Buckets/MemcachedRequestExecuter.cs b/Src/Couchbase/Core/Buckets/MemcachedRequestExecuter.cs index 7e25f26ad..e6835c16d 100644 --- a/Src/Couchbase/Core/Buckets/MemcachedRequestExecuter.cs +++ b/Src/Couchbase/Core/Buckets/MemcachedRequestExecuter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; @@ -89,7 +89,7 @@ public override IOperationResult SendWithRetry(IOperation operation) if (!operationResult.Success) { - if (operation.TimedOut()) + if (operation.TimedOut() && operationResult.ShouldRetry()) { const string msg = "The operation has timed out."; ((OperationResult) operationResult).Message = msg; @@ -150,7 +150,7 @@ public override IOperationResult SendWithRetry(IOperation operation) if (!operationResult.Success) { - if (operation.TimedOut()) + if (operation.TimedOut() && operationResult.ShouldRetry()) { const string msg = "The operation has timed out."; ((OperationResult) operationResult).Message = msg; From 121851a07d0e3d9952d336616d184ea9a7147459 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Fri, 8 Feb 2019 19:17:58 -0800 Subject: [PATCH 02/70] NCBC-1843: In LookupIn last command is not always parsed correctly Motivation ---------- Parsing of the last command in an multi LookupIn may skip parsing and indicate the wrong status. Modifications ------------- - Fix off by on error in MultiLookup.Read - Add integration test. Result ------ The final command will always be parsed correctly in a Multi-LookUp. Change-Id: I0d93484e1a23cc13faf549e268e0e4dadfcfbc4a Reviewed-on: http://review.couchbase.org/105913 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- .../CouchbaseBucket_SubDocument_Tests.cs | 25 +++++++++++++++++++ .../IO/Operations/SubDocument/MultiLookup.cs | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs index 7eee71213..15eff1fbd 100644 --- a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs +++ b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs @@ -43,6 +43,31 @@ private IBucket GetBucket(bool useMutation) #region Retrieval Commands + [Test] + [TestCase(true)] + [TestCase(false)] + public void LookupIn_Last_Command_Returns_Status(bool useMutation) + { + var bucket = GetBucket(useMutation); + var key = "LookupIn_Last_Command_Returns_Status"; + + bucket.Upsert(key, new + { + field1 = "value1", + field2 = "value2" + }); + + var lookupInBuilder = bucket.LookupIn(key); + lookupInBuilder.Get("field1"); + lookupInBuilder.Get("field2"); + lookupInBuilder.Exists("does_not_exist"); + var result = lookupInBuilder.Execute(); + + Assert.AreEqual(result.OpStatus(0), ResponseStatus.Success); + Assert.AreEqual(result.OpStatus(1), ResponseStatus.Success); + Assert.AreEqual(result.OpStatus(2), ResponseStatus.SubDocPathNotFound); + } + [Test] [TestCase(true)] [TestCase(false)] diff --git a/Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs b/Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs index 84704752e..a70883f40 100644 --- a/Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs +++ b/Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs @@ -107,7 +107,7 @@ public override T GetValue() valueLengthOffset = statusOffset + 2; valueOffset = statusOffset + 6; - if (valueOffset >= response.Length) break; + if (valueOffset > response.Length) break; } return (T)_lookupCommands; } From 7acee771e08c98e2ece5b5f9a10c05dccc78b792 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 19 Mar 2019 12:56:11 +0000 Subject: [PATCH 03/70] NCBC-1879: Only use KV nodes when updating bootstrap URIs MOTIVATION ---------- When processing a bucket configuration from the cluster, the internal list of bootstrap URIs are updated. However, the bucket configuration includes non-KV nodes, which are required for bootstrapping. MODIFICATIONS ------------- - only use KV nodes when updating the list of bootstrap nodes RESULT ------ Only KV nodes are used when updating the bootstrap URIs using a cluster bucket configuration. Change-Id: I71b4f76aeaefc877818cae116c2c380551bb181a Reviewed-on: http://review.couchbase.org/106442 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- Src/Couchbase/Configuration/Client/ClientConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/Couchbase/Configuration/Client/ClientConfiguration.cs b/Src/Couchbase/Configuration/Client/ClientConfiguration.cs index d0a0252fa..d51817120 100644 --- a/Src/Couchbase/Configuration/Client/ClientConfiguration.cs +++ b/Src/Couchbase/Configuration/Client/ClientConfiguration.cs @@ -996,7 +996,7 @@ internal void UpdateBootstrapList(IBucketConfig bucketConfig) try { ConfigLock.EnterWriteLock(); - foreach (var node in bucketConfig.GetNodes()) + foreach (var node in bucketConfig.GetNodes().Where(node => node.IsDataNode)) { if (!string.IsNullOrWhiteSpace(node.Hostname)) { From a39f7f2291f4cb2bec120e50dfc9910cb1b2ebcd Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Mon, 18 Mar 2019 17:56:58 -0700 Subject: [PATCH 04/70] NCBC-1877: ConfigManager only checks first Bucket w/two or more Buckets Motivation ---------- The ConfigManager.cs checks the first configured ClientContext (Bucket)'s config and then breaks from the loop. If multiple Buckets are configured, they will be skipped. Modifications ------------- - Remove break statement from loop in ConfigManager.cs Result ------ Each Context (Bucket) in the list will have their configuration checked. Change-Id: I72077cc9198e9581f3a9b14f9d6640655a41640f Reviewed-on: http://review.couchbase.org/106412 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- Src/Couchbase/Configuration/Server/Monitoring/ConfigMonitor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Src/Couchbase/Configuration/Server/Monitoring/ConfigMonitor.cs b/Src/Couchbase/Configuration/Server/Monitoring/ConfigMonitor.cs index 107676c8d..79bc3ec33 100644 --- a/Src/Couchbase/Configuration/Server/Monitoring/ConfigMonitor.cs +++ b/Src/Couchbase/Configuration/Server/Monitoring/ConfigMonitor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -100,7 +100,6 @@ await Task.Delay(TimeSpan.FromMilliseconds(Configuration.ConfigPollInterval), _c { _log.Debug("Checking config with revision #{0}", config.Rev); ClusterController.EnqueueConfigForProcessing(config); - break; } } } From 4bed2232ba8ef0e2b46b09e83b1770af2da5547d Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Mon, 25 Mar 2019 12:53:34 +0000 Subject: [PATCH 05/70] NCBC-1891: Set extras in subdoc MultiMutation MOTIVATION ---------- When creating a subdoc multi-mutation, the resultant packet should include a document expiry and/or relevant subdoc doc flags. MODIFICATIONS ------------- - build the body bytes early to ensure the builder has been iterated to create the operation specs - override CreateExtras to determine the correct length based on if an expiry and/or document flags are available - set the Expiry on MultiMutation in CouchbaseBucket - add integration test RESULT ------ When performing multiple subodc operations, requested document flags and expiry are sent to the server. Change-Id: I9d14b7bb5bdfd8d1812b63ecab3bff98426c5250 Reviewed-on: http://review.couchbase.org/106704 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- .../CouchbaseBucket_SubDocument_Tests.cs | 32 +++++++++ Src/Couchbase/CouchbaseBucket.cs | 10 ++- .../Operations/SubDocument/MultiMutation.cs | 67 ++++++++++++++++--- 3 files changed, 98 insertions(+), 11 deletions(-) diff --git a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs index 15eff1fbd..457489a36 100644 --- a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs +++ b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs @@ -1545,6 +1545,38 @@ public void Can_Create_Document_With_CreateDocument_Subdoc_Flag(bool useMutation Assert.AreEqual(name, getResult.Content(0)); } + [Test] + public async Task Can_create_multiple_xattrs_in_single_call() + { + var bucket = GetBucket(false); + + if (!SupportsXAttributes(bucket)) + { + Assert.Ignore(XAttrsNotSupported); + } + + const string key = "Can_create_multiple_xattrs_in_single_call"; + + await bucket.RemoveAsync(key); + Assert.IsFalse(bucket.Exists(key)); + + var mutateResult = await bucket.MutateIn(key) + .Upsert("txn.id", "123", SubdocPathFlags.CreatePath | SubdocPathFlags.Xattr, SubdocDocFlags.InsertDocument) + .Upsert("txn.ver", "v1", SubdocPathFlags.Xattr) + .ExecuteAsync(); + + Assert.IsTrue(mutateResult.Success); + + var getResult = await bucket.LookupIn(key) + .Get("txn.id", SubdocPathFlags.Xattr) + .Get("txn.ver", SubdocPathFlags.Xattr) + .ExecuteAsync(); + + Assert.IsTrue(getResult.Success); + Assert.AreEqual("123", getResult.Content(0)); + Assert.AreEqual("v1", getResult.Content(1)); + } + [Ignore("Requires additional logical delete server feature")] [TestCase(false)] [TestCase(true)] diff --git a/Src/Couchbase/CouchbaseBucket.cs b/Src/Couchbase/CouchbaseBucket.cs index 4622f55e3..69d0e81d8 100644 --- a/Src/Couchbase/CouchbaseBucket.cs +++ b/Src/Couchbase/CouchbaseBucket.cs @@ -6405,7 +6405,10 @@ public IDocumentFragment Invoke(IMutateInBuilder builder) } var timeout = builder.Timeout.HasValue ? builder.Timeout.Value: GlobalTimeout; - var multiMutate = new MultiMutation(builder.Key, theBuilder, null, _transcoder, timeout.GetSeconds()); + var multiMutate = new MultiMutation(builder.Key, theBuilder, null, _transcoder, timeout.GetSeconds()) + { + Expires = builder.Expiry.ToTtl() + }; return (DocumentFragment)_requestExecuter.SendWithRetry(multiMutate); } @@ -6426,7 +6429,10 @@ public async Task> InvokeAsync(IMutateInBuilder build } var timeout = builder.Timeout.HasValue ? builder.Timeout.Value : GlobalTimeout; - var multiMutate = new MultiMutation(builder.Key, theBuilder, null, _transcoder, timeout.GetSeconds()); + var multiMutate = new MultiMutation(builder.Key, theBuilder, null, _transcoder, timeout.GetSeconds()) + { + Expires = builder.Expiry.ToTtl() + }; return (DocumentFragment)await _requestExecuter.SendWithRetryAsync(multiMutate).ContinueOnAnyContext(); } diff --git a/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs b/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs index 8c1956d5b..41f07f66f 100644 --- a/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs +++ b/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using Couchbase.Core; using Couchbase.Core.IO.SubDocument; @@ -12,7 +13,7 @@ namespace Couchbase.IO.Operations.SubDocument internal class MultiMutation : OperationBase, IEquatable> { private readonly MutateInBuilder _builder; - private readonly IList _lookupCommands = new List(); + private readonly IList _mutateCommands = new List(); public MultiMutation(string key, MutateInBuilder mutateInBuilder, IVBucket vBucket, ITypeTranscoder transcoder, uint timeout) : base(key, vBucket, transcoder, timeout) @@ -23,12 +24,19 @@ public MultiMutation(string key, MutateInBuilder mutateInBuilder, IVBucket vB public override byte[] Write() { - var totalLength = OperationHeader.Length + KeyLength + BodyLength; + // create this first because we need to iterate builder for operation specs + var bodyBytes = CreateBody(); + + var totalLength = OperationHeader.Length + KeyLength + ExtrasLength + bodyBytes.Length; var buffer = AllocateBuffer(totalLength); WriteHeader(buffer); - WriteKey(buffer, OperationHeader.Length); - WriteBody(buffer, OperationHeader.Length + KeyLength); + WriteExtras(buffer, OperationHeader.Length); + WriteKey(buffer, OperationHeader.Length + ExtrasLength); + + // manually write body to buffer so we won't re-create it + System.Buffer.BlockCopy(bodyBytes, 0, buffer, OperationHeader.Length + ExtrasLength + KeyLength, bodyBytes.Length); + //WriteBody(buffer, OperationHeader.Length + ExtrasLength + KeyLength); return buffer; } @@ -68,11 +76,32 @@ public override byte[] CreateBody() buffer.AddRange(spec); buffer.AddRange(fragment); - _lookupCommands.Add(mutate); + _mutateCommands.Add(mutate); } return buffer.ToArray(); } + public override void WriteExtras(byte[] buffer, int offset) + { + var hasExpiry = Expires > 0; + if (hasExpiry) + { + Converter.FromUInt32(Expires, buffer, offset); //4 Expiration time + } + + if (_mutateCommands.Any(spec => spec.DocFlags != SubdocDocFlags.None)) + { + var docFlags = SubdocDocFlags.None; + foreach (var spec in _mutateCommands) + { + docFlags |= spec.DocFlags; + } + + // write doc flags, offset depends on if there is an expiry + Converter.FromByte((byte) docFlags, buffer, offset + (hasExpiry ? 4 : 0)); + } + } + byte[] GetBytes(OperationSpec spec) { var bytes = Transcoder.Serializer.Serialize(spec.Value); @@ -133,7 +162,7 @@ public IList GetCommandValues() //all mutations successful if (response.Length == OperationHeader.Length + Header.FramingExtrasLength) { - return _lookupCommands; + return _mutateCommands; } var indexOffset = Header.ExtrasOffset; @@ -144,7 +173,7 @@ public IList GetCommandValues() for (;;) { var index = Converter.ToByte(response, indexOffset); - var command = _lookupCommands[index]; + var command = _mutateCommands[index]; command.Status = (ResponseStatus) Converter.ToUInt16(response, statusOffset); //if succcess read value and loop to next result - otherwise terminate loop here @@ -165,12 +194,32 @@ public IList GetCommandValues() if (valueOffset + Header.ExtrasLength > response.Length) break; } - return _lookupCommands; + return _mutateCommands; } + private short? _extrasLength; public override short ExtrasLength { - get { return (short)(Expires == 0 ? 0 : 4); } + get + { + if (!_extrasLength.HasValue) + { + short length = 0; + if (Expires > 0) + { + length += 4; + } + + if (_mutateCommands.Any(x => x.DocFlags != SubdocDocFlags.None)) + { + length += 1; + } + + _extrasLength = length; + } + + return _extrasLength.Value; + } } public override void WriteKey(byte[] buffer, int offset) From 639cc562c625a6717f5b733e108121ad73d4266a Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Mon, 25 Mar 2019 17:04:26 +0000 Subject: [PATCH 06/70] NCBC-1892: Add support for upserting document body with subdoc operation MOTIVATION ---------- Subdoc operations can atomically set the JSON document body and manipulate it's XATTRs. This adds support to do that. MODIFICATIONS ------------- - extend IMutationBuilder and MutationBuilder to have an Upsert option that takes an object without a path - add SubDocUpsert subdoc - extend Multimutation to take SubDocUpsert - update Multimutation to only write path if the mutation has a path - add unit test RESULT ------ It's now possible to set a JSON document body alongside other XATTR values in a single subdocument operation. Change-Id: If11921558aac50174d71080958853dd05a85db89 Reviewed-on: http://review.couchbase.org/106719 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- .../CouchbaseBucket_SubDocument_Tests.cs | 24 ++++++++++++++ Src/Couchbase/Core/IMutateInBuilder.cs | 11 ++++++- Src/Couchbase/Core/MutateInBuilder.cs | 23 ++++++++++++- Src/Couchbase/CouchbaseBucket.cs | 2 ++ .../Operations/SubDocument/MultiMutation.cs | 8 +++-- .../IO/Operations/SubDocument/SubDocUpsert.cs | 32 +++++++++++++++++++ 6 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 Src/Couchbase/IO/Operations/SubDocument/SubDocUpsert.cs diff --git a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs index 457489a36..d065fe101 100644 --- a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs +++ b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs @@ -1647,6 +1647,30 @@ public void Can_Use_Server_Macro_To_Populate_XATTR(bool useMutation) #endregion + [Test] + public async Task Can_upsert_full_doc_body() + { + var bucket = GetBucket(false); + if (!SupportsXAttributes(bucket)) + { + Assert.Ignore(XAttrsNotSupported); + } + + var key = Guid.NewGuid().ToString(); + try + { + var result = await bucket.MutateIn(key) + .Upsert("key", "value", SubdocPathFlags.CreatePath | SubdocPathFlags.Xattr) + .Upsert(new {name = "mike"}, SubdocDocFlags.UpsertDocument) + .ExecuteAsync(); + Assert.IsTrue(result.Success); + } + finally + { + //await bucket.RemoveAsync(key); + } + } + [OneTimeTearDown] public void OneTimeTearDown() { diff --git a/Src/Couchbase/Core/IMutateInBuilder.cs b/Src/Couchbase/Core/IMutateInBuilder.cs index cffd54c6b..9b2fd0545 100644 --- a/Src/Couchbase/Core/IMutateInBuilder.cs +++ b/Src/Couchbase/Core/IMutateInBuilder.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Couchbase.Core { @@ -47,6 +47,15 @@ public interface IMutateInBuilder : ISubDocBuilder /// An reference for chaining operations. IMutateInBuilder Insert(string path, object value, SubdocPathFlags pathFlags, SubdocDocFlags docFlags = SubdocDocFlags.None); + /// + /// Inserts or updates the JSON document body. This can be used in conjunction with XATTRs to atomically set both parts + /// at the same time. + /// + /// An array value, dictionary entry, scalar or any other valid JSON item. + /// The document flags. + /// An reference for chaining operations. + IMutateInBuilder Upsert(object value, SubdocDocFlags docFlags = SubdocDocFlags.None); + /// /// Inserts or updates an element within or into a JSON document at a given path. /// diff --git a/Src/Couchbase/Core/MutateInBuilder.cs b/Src/Couchbase/Core/MutateInBuilder.cs index deb610889..4365cb514 100644 --- a/Src/Couchbase/Core/MutateInBuilder.cs +++ b/Src/Couchbase/Core/MutateInBuilder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; @@ -175,6 +175,27 @@ public IMutateInBuilder Insert(string path, object value, SubdocPathF return this; } + /// + /// Inserts or updates the JSON document body. This can be used in conjunction with XATTRs to atomically set both parts + /// at the same time. + /// + /// An array value, dictionary entry, scalar or any other valid JSON item. + /// The document flags. + /// + /// An reference for chaining operations. + /// + public IMutateInBuilder Upsert(object value, SubdocDocFlags docFlags = SubdocDocFlags.None) + { + _commands.Enqueue(new OperationSpec + { + OpCode = OperationCode.Set, + Value = value, + PathFlags = SubdocPathFlags.None, + DocFlags = docFlags + }); + return this; + } + /// /// Inserts or updates an element within or into a JSON document at a given path. /// diff --git a/Src/Couchbase/CouchbaseBucket.cs b/Src/Couchbase/CouchbaseBucket.cs index 69d0e81d8..8bf005dde 100644 --- a/Src/Couchbase/CouchbaseBucket.cs +++ b/Src/Couchbase/CouchbaseBucket.cs @@ -6372,6 +6372,8 @@ private SubDocSingularMutationBase OptimizeSingleMutation(MutateInBuilder< case OperationCode.SubReplace: return new SubDocReplace(builder, builder.Key, null, _transcoder, GlobalTimeout.GetSeconds()) { BucketName = Name, Expires = builder.Expiry.ToTtl()}; + case OperationCode.Set: + return new SubDocUpsert(builder, null, _transcoder, GlobalTimeout.GetSeconds()); default: throw new NotSupportedException("Opcode is not supported for MutateInBuilder."); } diff --git a/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs b/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs index 41f07f66f..574e2ae8a 100644 --- a/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs +++ b/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs @@ -64,7 +64,7 @@ public override byte[] CreateBody() { var opcode = (byte)mutate.OpCode; var flags = (byte) mutate.PathFlags; - var pathLength = Encoding.UTF8.GetByteCount(mutate.Path); + var pathLength = string.IsNullOrWhiteSpace(mutate.Path) ? 0 : Encoding.UTF8.GetByteCount(mutate.Path); var fragment = mutate.Value == null ? new byte[0] : GetBytes(mutate); var spec = new byte[pathLength + 8]; @@ -72,7 +72,11 @@ public override byte[] CreateBody() Converter.FromByte(flags, spec, 1); Converter.FromUInt16((ushort)pathLength, spec, 2); Converter.FromUInt32((uint)fragment.Length, spec, 4); - Converter.FromString(mutate.Path, spec, 8); + + if (pathLength > 0) + { + Converter.FromString(mutate.Path, spec, 8); + } buffer.AddRange(spec); buffer.AddRange(fragment); diff --git a/Src/Couchbase/IO/Operations/SubDocument/SubDocUpsert.cs b/Src/Couchbase/IO/Operations/SubDocument/SubDocUpsert.cs new file mode 100644 index 000000000..bdb2eab75 --- /dev/null +++ b/Src/Couchbase/IO/Operations/SubDocument/SubDocUpsert.cs @@ -0,0 +1,32 @@ +using Couchbase.Core; +using Couchbase.Core.Transcoders; + +namespace Couchbase.IO.Operations.SubDocument +{ + internal class SubDocUpsert : SubDocSingularMutationBase + { + public override OperationCode OperationCode => OperationCode.Set; + public override bool RequiresKey => false; + + public SubDocUpsert(MutateInBuilder builder, IVBucket vBucket, ITypeTranscoder transcoder, uint timeout) + : base(builder, string.Empty, vBucket, transcoder, SequenceGenerator.GetNext(), timeout) + { + CurrentSpec = builder.FirstSpec(); + Cas = builder.Cas; + } + + public override IOperation Clone() + { + return new SubDocUpsert((MutateInBuilder)((MutateInBuilder)Builder).Clone(), VBucket, Transcoder, Timeout) + { + Attempts = Attempts, + Cas = Cas, + CreationTime = CreationTime, + LastConfigRevisionTried = LastConfigRevisionTried, + BucketName = BucketName, + ErrorCode = ErrorCode, + Expires = Expires + }; + } + } +} From 17d887bf3cb8547be9dfb352edad5a5f906ba9f7 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Fri, 29 Mar 2019 20:21:50 +0000 Subject: [PATCH 07/70] =?UTF-8?q?NCBC-1899:=20Don=E2=80=99t=20finish=20spa?= =?UTF-8?q?n=20when=20disposing=20of=20async=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MOTIVATION ---------- When passing the active dispatch span in an async manner, the span is getting Finish called multiple times. This should only happen once and can cause WARN messages in some tracer implementations. MODIFICATIONS ------------- - don't attempt to call Finish() on Dispatch span on either AsyncState or SocketAsyncState RESULT ------ The dispatch span is no longer attempted to be finished multiple times and therefore does not cause WARN messages. Change-Id: I410c092d6e79e476ca04910bf5d2891639787cd1 Reviewed-on: http://review.couchbase.org/107016 Reviewed-by: Jeffry Morris Tested-by: Build Bot --- Src/Couchbase/Core/Buckets/CallbackFactory.cs | 8 ++++++-- Src/Couchbase/IO/AsyncState.cs | 1 - Src/Couchbase/IO/SocketAsyncState.cs | 1 - 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Src/Couchbase/Core/Buckets/CallbackFactory.cs b/Src/Couchbase/Core/Buckets/CallbackFactory.cs index 3eb6837ce..cedb18485 100644 --- a/Src/Couchbase/Core/Buckets/CallbackFactory.cs +++ b/Src/Couchbase/Core/Buckets/CallbackFactory.cs @@ -19,8 +19,12 @@ private static OperationHeader CreateHeader(SocketAsyncState state, out ErrorCod { var header = state.CreateHeader(out errorCode); serverDuration = header.GetServerDuration(state.Data); - state.DispatchSpan?.SetPeerLatencyTag(serverDuration); - state.DispatchSpan?.Finish(); + + if (state.DispatchSpan != null) + { + state.DispatchSpan.SetPeerLatencyTag(serverDuration); + state.DispatchSpan.Finish(); + } return header; } diff --git a/Src/Couchbase/IO/AsyncState.cs b/Src/Couchbase/IO/AsyncState.cs index de1a6160c..84f095162 100644 --- a/Src/Couchbase/IO/AsyncState.cs +++ b/Src/Couchbase/IO/AsyncState.cs @@ -99,7 +99,6 @@ public void Complete(byte[] response) public void Dispose() { Timer?.Dispose(); - DispatchSpan?.Finish(); } } } diff --git a/Src/Couchbase/IO/SocketAsyncState.cs b/Src/Couchbase/IO/SocketAsyncState.cs index df04ff0c9..97dec7a51 100644 --- a/Src/Couchbase/IO/SocketAsyncState.cs +++ b/Src/Couchbase/IO/SocketAsyncState.cs @@ -87,7 +87,6 @@ internal void SetIOBuffer(IOBuffer ioBuffer) public void Dispose() { if (Data != null) Data.Dispose(); - DispatchSpan?.Finish(); } } From bfa0a0c4e6da970c2c9d7356037adcbf7d12d983 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Tue, 2 Apr 2019 17:46:43 -0700 Subject: [PATCH 08/70] NCBC-1904: Error converting value failed to type 'Couchbase.N1QL.QueryStatus' Change-Id: I00c48cca28b33faa35bf1adc45b3f9bc3221574a Reviewed-on: http://review.couchbase.org/107206 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- Src/Couchbase/N1QL/QueryStatus.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase/N1QL/QueryStatus.cs b/Src/Couchbase/N1QL/QueryStatus.cs index 44fb1a85e..7f5d6abfe 100644 --- a/Src/Couchbase/N1QL/QueryStatus.cs +++ b/Src/Couchbase/N1QL/QueryStatus.cs @@ -14,7 +14,9 @@ public enum QueryStatus Timeout, - Fatal + Fatal, + + Failed } } From 18bc3625d2ee614c664bc63e3f5096f9618d5547 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Wed, 3 Apr 2019 12:13:37 -0700 Subject: [PATCH 09/70] NCBC-1905: AnalyticsResult throws NRE when Errors is NULL Motivation ---------- If the Errors property is NULL after an analytics request will fail with a NullReferenceException when AnalyticsResult.ShouldRetry is called. Modifications ------------- - Ensure Errors is non-null before enumerating. - Cleanup ToString - Added additional logging of the actual error that was thrown Result ------ NRE is no longer thrown and the actual error returned by the server should be available to determine what failed. Change-Id: I04d2032a509c8f1ac757782c4767561ae437b841 Reviewed-on: http://review.couchbase.org/107247 Tested-by: Build Bot Reviewed-by: Brett Lawson --- Src/Couchbase/Analytics/AnalyticsClient.cs | 1 + Src/Couchbase/Analytics/AnalyticsResult.cs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Src/Couchbase/Analytics/AnalyticsClient.cs b/Src/Couchbase/Analytics/AnalyticsClient.cs index 721dc4d56..74ed08f26 100644 --- a/Src/Couchbase/Analytics/AnalyticsClient.cs +++ b/Src/Couchbase/Analytics/AnalyticsClient.cs @@ -120,6 +120,7 @@ public async Task> QueryAsync(IAnalyticsRequest queryRequ ae.Flatten().Handle(e => { Log.Info("Failed analytics query cid{0}: {1}", queryRequest.CurrentContextId, baseUri); + Log.Error(e); ProcessError(e, result); return true; }); diff --git a/Src/Couchbase/Analytics/AnalyticsResult.cs b/Src/Couchbase/Analytics/AnalyticsResult.cs index 81b9059f8..4533eccf0 100644 --- a/Src/Couchbase/Analytics/AnalyticsResult.cs +++ b/Src/Couchbase/Analytics/AnalyticsResult.cs @@ -91,12 +91,12 @@ public bool ShouldRetry() case QueryStatus.Errors: case QueryStatus.Timeout: case QueryStatus.Fatal: - return Errors.Any(error => - error.Code == 21002 || // Request timed out and will be cancelled - error.Code == 23000 || // Analytics Service is temporarily unavailable - error.Code == 23003 || // Operation cannot be performed during rebalance - error.Code == 23007 // Job queue is full with [string] jobs - ); + return Errors != null && Errors.Any(error => + error.Code == 21002 || // Request timed out and will be cancelled + error.Code == 23000 || // Analytics Service is temporarily unavailable + error.Code == 23003 || // Operation cannot be performed during rebalance + error.Code == 23007 // Job queue is full with [string] jobs + ); default: return false; } @@ -187,9 +187,9 @@ internal AnalyticsResult ToQueryResult(HttpClient client, IDataMapper dataMap Signature = signature, Rows = results.ToList(), Status = status, - Errors = errors != null ? errors.Select(e => e.ToError()).ToList() : null, - Warnings = warnings != null ? warnings.Select(w => w.ToWarning()).ToList() : null, - Metrics = metrics != null ? metrics.ToMetrics() : null + Errors = errors?.Select(e => e.ToError()).ToList(), + Warnings = warnings?.Select(w => w.ToWarning()).ToList(), + Metrics = metrics?.ToMetrics() }; if (!string.IsNullOrWhiteSpace(handle)) From 7eb722ff83f5f549c89d8308a69808d07c356b0f Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Wed, 3 Apr 2019 17:53:27 -0700 Subject: [PATCH 10/70] NCBC-1906: Analytics URI cache is empty Motivation ---------- When a swap/failover and rebalance is triggered, the cached URIs for the analytics service may all be set to failed. This commit clears the failed state so the URI can be retried in case it was a temp failure. Modifications ------------- - Make ConfigContextBase.GetAnalyticsUri reset the URIs when all are in a failed state so that the they can be tried again. Result ------ URIs should be always be available whether or not they succeed initially or not - this is the same behavior as Views, N1QL and FTS. Change-Id: I4b9f1d1063a0f6c34d7844f9b52556a3c2c818aa Reviewed-on: http://review.couchbase.org/107262 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- Src/Couchbase/Configuration/ConfigContextBase.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase/Configuration/ConfigContextBase.cs b/Src/Couchbase/Configuration/ConfigContextBase.cs index c082198ee..bf16f04b5 100644 --- a/Src/Couchbase/Configuration/ConfigContextBase.cs +++ b/Src/Couchbase/Configuration/ConfigContextBase.cs @@ -135,7 +135,20 @@ public FailureCountingUri GetSearchUri() public FailureCountingUri GetAnalyticsUri() { - return AnalyticsUris.Where(x => x.IsHealthy(2)).GetRandom(); + var analyticsUris = AnalyticsUris.Where(x => x.IsHealthy(2)).ToList(); + if (analyticsUris.Count == 0) + { + // All analytics URIs are unhealthy, so reset them all back to healthy and return the entire list + // It's better to at least try the nodes than assume they're all failing indefinitely + + foreach (var analyticsUri in AnalyticsUris) + { + analyticsUri.ClearFailed(); + analyticsUris.Add(analyticsUri); + } + } + + return analyticsUris.GetRandom(); } protected ITypeTranscoder Transcoder { get; private set; } From 1b39e02e5494b3a78fbda39a75256c47b5aae922 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Tue, 16 Apr 2019 11:51:39 -0700 Subject: [PATCH 11/70] NCBC-1926: Ensure connections that fail SelectBucket are Disposed Motivation ---------- If SelectBucket fails an AuthenticationException is thrown, but the socket itself is not explicitly closed. Modifications ------------- - Wrap SelectBucket calls in try/catch and Dipose socket. Result ------ The number of sockets that remain open after SelectBucket fails is zero. Change-Id: I3fb0e23fc822e43ca66131760556c1f75dfc590a Reviewed-on: http://review.couchbase.org/107913 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- Src/Couchbase/IO/SharedConnectionPool.cs | 62 ++++++++++++++++-------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/Src/Couchbase/IO/SharedConnectionPool.cs b/Src/Couchbase/IO/SharedConnectionPool.cs index 210492220..3fa70f2a1 100644 --- a/Src/Couchbase/IO/SharedConnectionPool.cs +++ b/Src/Couchbase/IO/SharedConnectionPool.cs @@ -1,12 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Security.Authentication; using Couchbase.Configuration.Client; -using Couchbase.Core.Transcoders; using Couchbase.IO.Converters; -using Couchbase.IO.Operations.Authentication; using Couchbase.Logging; namespace Couchbase.IO @@ -71,9 +68,23 @@ public override IConnection Acquire() if (_connections.Count >= Configuration.MaxSize) { var connection = _connections[GetIndex()]; - Authenticate(connection); - EnableEnhancedAuthentication(connection); - return connection; + try + { + Authenticate(connection); + EnableEnhancedAuthentication(connection); + return connection; + } + catch (Exception e) + { + Log.Debug($"Connection creation or authentication failed for {connection?.ConnectionId}", e); + if (connection != null) + { + connection.IsDead = true; + Release((T) connection); + } + + throw; + } } lock (_lockObj) { @@ -88,15 +99,23 @@ private IConnection CreateAndAuthConnection() Log.Debug("Trying to acquire new connection! Refs={0}", _connections.Count); var connection = Factory(this, Converter, BufferAllocator); + try + { + //Perform sasl auth + Authenticate(connection); + EnableEnhancedAuthentication(connection); - //Perform sasl auth - Authenticate(connection); - EnableEnhancedAuthentication(connection); - - Log.Debug("Acquire new: {0} | {1} | [{2}, {3}] - {4} - Disposed: {5}", + Log.Debug("Acquire new: {0} | {1} | [{2}, {3}] - {4} - Disposed: {5}", connection.Identity, EndPoint, _connections.Count, Configuration.MaxSize, _identity, _disposed); - return connection; + return connection; + } + catch (Exception) + { + Log.Debug("Connection creation or authentication failed for {0}", connection?.ConnectionId); + connection?.Dispose(); + throw; + } } public override void Release(T connection) @@ -108,10 +127,7 @@ public override void Release(T connection) lock (_lockObj) { connection.Dispose(); - if (Owner != null) - { - Owner.CheckOnline(connection.IsDead); - } + Owner?.CheckOnline(connection.IsDead); _connections.Remove(connection); } } @@ -132,8 +148,16 @@ public override void Initialize() //auth the connection used to select the SASL type to use for auth foreach (var connection in _connections.Where(x=>!x.IsAuthenticated)) { - Authenticate(connection); - EnableEnhancedAuthentication(connection); + try + { + Authenticate(connection); + EnableEnhancedAuthentication(connection); + } + catch (Exception e) + { + Log.Debug($"Connection creation or authentication failed for {connection?.ConnectionId}", e); + connection?.Dispose(); + } } } } From f789c0ac305eb4a482b390e041b46676d83b72b6 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Wed, 24 Apr 2019 13:13:32 +0100 Subject: [PATCH 12/70] NCBC-1919: Allow compound FTS queries to use boosted sub-queries MOTIVATION ---------- When creating compound queries (Conjunction, Disjunction & Boolean), you provide sub-queries to perform more advanced searches. However, the query constructors and builder APIs for those queries accept FtsQueryBase but should be IFtsQuery to allow boosted queries to be used directly. MODIFICATIONS ------------- - update Conjunction, Disjunction and Boolean queries to accept IFtsQuery in constructor and builder APIs - add unit tests for each query type to ensure boosted queries are accepted in constructors and builder APIs RESULT ------ Compound queries can now use boosted queries. Change-Id: I3e8141f7b6782eeb934b3e0a09c724526bda1dc8 Reviewed-on: http://review.couchbase.org/108208 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- .../Search/BooleanQueryTests.cs | 23 ++++++++++++++++++- .../Search/ConjunctionQueryTests.cs | 17 +++++++++++++- .../Search/DisjunctionQueryTests.cs | 17 +++++++++++++- .../Search/Queries/Compound/BooleanQuery.cs | 8 +++---- .../Queries/Compound/ConjunctionQuery.cs | 8 +++---- .../Queries/Compound/DisjunctionQuery.cs | 10 ++++---- 6 files changed, 67 insertions(+), 16 deletions(-) mode change 100755 => 100644 Src/Couchbase.UnitTests/Search/BooleanQueryTests.cs mode change 100755 => 100644 Src/Couchbase.UnitTests/Search/ConjunctionQueryTests.cs mode change 100755 => 100644 Src/Couchbase.UnitTests/Search/DisjunctionQueryTests.cs mode change 100755 => 100644 Src/Couchbase/Search/Queries/Compound/BooleanQuery.cs mode change 100755 => 100644 Src/Couchbase/Search/Queries/Compound/ConjunctionQuery.cs mode change 100755 => 100644 Src/Couchbase/Search/Queries/Compound/DisjunctionQuery.cs diff --git a/Src/Couchbase.UnitTests/Search/BooleanQueryTests.cs b/Src/Couchbase.UnitTests/Search/BooleanQueryTests.cs old mode 100755 new mode 100644 index 5bc26194e..944d2325e --- a/Src/Couchbase.UnitTests/Search/BooleanQueryTests.cs +++ b/Src/Couchbase.UnitTests/Search/BooleanQueryTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Couchbase.Search.Queries.Compound; using Couchbase.Search.Queries.Simple; using Newtonsoft.Json; @@ -129,6 +129,27 @@ public void Export_Returns_Valid_Json_For_Should() Assert.AreEqual(expected, result); } + + [Test] + public void Can_add_should_query_with_boost() + { + new BooleanQuery() + .Should(new MatchQuery("term1").Field("field1").Boost(2.0)); + } + + [Test] + public void Can_add_must_query_with_boost() + { + new BooleanQuery() + .Must(new MatchQuery("term1").Field("field1").Boost(2.0)); + } + + [Test] + public void Can_add_mustnot_query_with_boost() + { + new BooleanQuery() + .MustNot(new MatchQuery("term1").Field("field1").Boost(2.0)); + } } } diff --git a/Src/Couchbase.UnitTests/Search/ConjunctionQueryTests.cs b/Src/Couchbase.UnitTests/Search/ConjunctionQueryTests.cs old mode 100755 new mode 100644 index a60b1dce9..c3481a29a --- a/Src/Couchbase.UnitTests/Search/ConjunctionQueryTests.cs +++ b/Src/Couchbase.UnitTests/Search/ConjunctionQueryTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Couchbase.Search; using Couchbase.Search.Queries.Compound; using Couchbase.Search.Queries.Simple; @@ -51,6 +51,21 @@ public void Export_ReturnsValidJson() Assert.AreEqual(expected, result); } + + [Test] + public void Can_create_conjunction_that_includes_query_with_boost() + { + new ConjunctionQuery( + new MatchQuery("term1").Field("field1").Boost(2.0) + ); + } + + [Test] + public void Can_add_query_with_boost() + { + new ConjunctionQuery() + .And(new MatchQuery("term1").Field("field1").Boost(2.0)); + } } } diff --git a/Src/Couchbase.UnitTests/Search/DisjunctionQueryTests.cs b/Src/Couchbase.UnitTests/Search/DisjunctionQueryTests.cs old mode 100755 new mode 100644 index 8c0dca4d7..a9a6e5cc8 --- a/Src/Couchbase.UnitTests/Search/DisjunctionQueryTests.cs +++ b/Src/Couchbase.UnitTests/Search/DisjunctionQueryTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Couchbase.Search; using Couchbase.Search.Queries.Compound; using Couchbase.Search.Queries.Simple; @@ -52,6 +52,21 @@ public void Export_ReturnsValidJson() Assert.AreEqual(expected, result); } + + [Test] + public void Can_create_disjunction_that_includes_query_with_boost() + { + new DisjunctionQuery( + new MatchQuery("term1").Field("field1").Boost(2.0) + ); + } + + [Test] + public void Can_add_query_with_boost() + { + new DisjunctionQuery() + .Or(new MatchQuery("term1").Field("field1").Boost(2.0)); + } } } diff --git a/Src/Couchbase/Search/Queries/Compound/BooleanQuery.cs b/Src/Couchbase/Search/Queries/Compound/BooleanQuery.cs old mode 100755 new mode 100644 index 4eef86d32..beb8ad830 --- a/Src/Couchbase/Search/Queries/Compound/BooleanQuery.cs +++ b/Src/Couchbase/Search/Queries/Compound/BooleanQuery.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Newtonsoft.Json.Linq; @@ -19,7 +19,7 @@ public class BooleanQuery : FtsQueryBase /// /// /// - public BooleanQuery Must(params FtsQueryBase[] queries) + public BooleanQuery Must(params IFtsQuery[] queries) { _mustQueries.And(queries); return this; @@ -30,7 +30,7 @@ public BooleanQuery Must(params FtsQueryBase[] queries) /// /// The query. /// - public BooleanQuery Should(params FtsQueryBase[] queries) + public BooleanQuery Should(params IFtsQuery[] queries) { _shouldQueries.Or(queries); return this; @@ -52,7 +52,7 @@ public BooleanQuery ShouldMin(int min) /// /// The query. /// - public BooleanQuery MustNot(params FtsQueryBase[] queries) + public BooleanQuery MustNot(params IFtsQuery[] queries) { _mustNotQueries.Or(queries); return this; diff --git a/Src/Couchbase/Search/Queries/Compound/ConjunctionQuery.cs b/Src/Couchbase/Search/Queries/Compound/ConjunctionQuery.cs old mode 100755 new mode 100644 index 3a2a61eb6..50469bf8e --- a/Src/Couchbase/Search/Queries/Compound/ConjunctionQuery.cs +++ b/Src/Couchbase/Search/Queries/Compound/ConjunctionQuery.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -12,9 +12,9 @@ namespace Couchbase.Search.Queries.Compound /// public class ConjunctionQuery : FtsQueryBase, IEnumerable { - private readonly List _queries = new List(); + private readonly List _queries = new List(); - public ConjunctionQuery(params FtsQueryBase[] queries) + public ConjunctionQuery(params IFtsQuery[] queries) { _queries.AddRange(queries); } @@ -24,7 +24,7 @@ public ConjunctionQuery(params FtsQueryBase[] queries) /// /// One or more queries to add. /// - public ConjunctionQuery And(params FtsQueryBase[] queries) + public ConjunctionQuery And(params IFtsQuery[] queries) { _queries.AddRange(queries); return this; diff --git a/Src/Couchbase/Search/Queries/Compound/DisjunctionQuery.cs b/Src/Couchbase/Search/Queries/Compound/DisjunctionQuery.cs old mode 100755 new mode 100644 index 0ecaed4c8..366f08c23 --- a/Src/Couchbase/Search/Queries/Compound/DisjunctionQuery.cs +++ b/Src/Couchbase/Search/Queries/Compound/DisjunctionQuery.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -13,11 +13,11 @@ namespace Couchbase.Search.Queries.Compound public class DisjunctionQuery : FtsQueryBase, IEnumerable { private int _min = 1; - private readonly List _queries; + private readonly List _queries; - public DisjunctionQuery(params FtsQueryBase[] queries) + public DisjunctionQuery(params IFtsQuery[] queries) { - _queries = new List(queries); + _queries = new List(queries); } /// @@ -25,7 +25,7 @@ public DisjunctionQuery(params FtsQueryBase[] queries) /// /// One or more queries to add. /// - public DisjunctionQuery Or(params FtsQueryBase[] queries) + public DisjunctionQuery Or(params IFtsQuery[] queries) { _queries.AddRange(queries); return this; From da4c6b6613a37c916ff88d513094a3c10837ee55 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Mon, 13 May 2019 15:30:22 +0100 Subject: [PATCH 13/70] NCBC-1957: Release connection acquired during PooledIOService constructor MOTIVATION ---------- When creating an instance of PooledIOService, a connection from the ConnectionPool parameter is used to check the server for available features. However, the connection is not returned back to the pool which means it can not be used again and another connection has to be created in the pool. MODIFICATIONS ------------- - release the connection at the end of PooledIOService constructor - add unit test to verify the connection has been released properly RESULT ------ The connection used during PooledIOService construction is released back to the pool and can be re-used. Also, an additional connection is not created to replace the original one. Change-Id: I54a3140aa16b8173934732e51995a402af89f500 Reviewed-on: http://review.couchbase.org/109049 Reviewed-by: Jeffry Morris Tested-by: Build Bot --- .../IO/Services/PooledIOServiceTests.cs | 29 +++++++++++++++++++ Src/Couchbase/IO/Services/PooledIOService.cs | 9 +++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase.UnitTests/IO/Services/PooledIOServiceTests.cs b/Src/Couchbase.UnitTests/IO/Services/PooledIOServiceTests.cs index cb4943d63..a5c192061 100644 --- a/Src/Couchbase.UnitTests/IO/Services/PooledIOServiceTests.cs +++ b/Src/Couchbase.UnitTests/IO/Services/PooledIOServiceTests.cs @@ -173,5 +173,34 @@ public void Result_Has_UnknownError_Status_If_ErrorMap_Not_Available() Assert.AreEqual(ResponseStatus.UnknownError, result.Status); Assert.AreEqual("Status code: UnknownError [-2]", result.Message); } + + [Test] + public void Connection_acquired_during_construction_should_be_released_back_to_pool() + { + var inUse = false; + var connection = new Mock(); + + var mockConnectionPool = new Mock(); + mockConnectionPool + .Setup(x => x.Acquire()) + .Returns(() => + { + inUse = true; + return connection.Object; + } + ); + mockConnectionPool + .Setup(x => x.Release(It.IsAny())) + .Callback(conn => + { + inUse = false; + }); + + // create io service + var ioService = new PooledIOService(mockConnectionPool.Object); + + // connection is marked as in use during constructor, after being released it should be false + Assert.IsFalse(inUse); + } } } diff --git a/Src/Couchbase/IO/Services/PooledIOService.cs b/Src/Couchbase/IO/Services/PooledIOService.cs index 5a6332a02..97f667385 100644 --- a/Src/Couchbase/IO/Services/PooledIOService.cs +++ b/Src/Couchbase/IO/Services/PooledIOService.cs @@ -33,7 +33,14 @@ public PooledIOService(IConnectionPool connectionPool) ConnectionPool = connectionPool; var connection = connectionPool.Connections.FirstOrDefault() ?? connectionPool.Acquire(); - CheckEnabledServerFeatures(connection); + try + { + CheckEnabledServerFeatures(connection); + } + finally + { + connectionPool.Release(connection); + } } /// From fba2e11be9f18bf2908c3f6b0ae7c31fdb974d1f Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Mon, 13 May 2019 15:48:26 +0100 Subject: [PATCH 14/70] NCBC-1927: Resolve vbucket server map endpoints if different from bucket config server list MOTIVATION ---------- When bootstraping the client receives a vbucket server map from as part of the bucket config. This server map is then used to map active and replica keys to cluster nodes. However, if the nodes are configured using an ingress system (eg Kubernetes) the nodes may not be reachable on the endpoints contained in the bucket config. MODIFICATIONS ------------- - during CCCP bootstrap, if the list of node hostnames is different to the resolved IPs for the nodes, the client uses the IPs instead - add method to clear vbucket server list IPs RESULT ------ The SDK can now successfully connect to a Couchbase cluster when the bucket config's nodes are internal (eg public Kubernetes cluster) by using resolved IP Endpoints instead. Change-Id: I3ff4f5c013f8bc1955603e9f00243f1ce05356bd Reviewed-on: http://review.couchbase.org/109050 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- .../CarrierPublication/CarrierPublicationProvider.cs | 12 ++++++++++++ .../Server/Serialization/VBucketServerMap.cs | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs b/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs index b467bcf4c..5d6c2131d 100644 --- a/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs +++ b/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs @@ -97,6 +97,18 @@ public override IConfigInfo GetConfig(string bucketName, string username, string // Set network type for bucket configuration bucketConfig.NetworkType = bucketConfiguration.NetworkType; + // get list of node ip/port endpoints + var endpoints = bucketConfig.GetNodes() + .Select(x => x.GetIPEndPoint(bucketConfiguration.UseSsl).ToString()) + .ToArray(); + + // if vbucketmap servers are different, use resolved endpoints instead + if (!bucketConfig.VBucketServerMap.ServerList.SequenceEqual(endpoints)) + { + bucketConfig.VBucketServerMap.ServerList = endpoints; + bucketConfig.VBucketServerMap.ClearIPEndPoints(); + } + configInfo = new CouchbaseConfigContext(bucketConfig, ClientConfig, IOServiceFactory, diff --git a/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs b/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs index 3dc0d49ce..f45d65e4d 100644 --- a/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs +++ b/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs @@ -46,6 +46,11 @@ public List IPEndPoints } } + public void ClearIPEndPoints() + { + _ipEndPoints = null; + } + public bool Equals(VBucketServerMap other) { return (other != null From 41934ed206e777cb51cbe520f13f11b03b2a20e7 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 21 May 2019 22:55:50 +0100 Subject: [PATCH 15/70] NCBC-1917: Fix typo and add volatile note to AnalyticsIngestor MOTIVATION ---------- There is a typo in the class AnalyticsExtensions that should be corrected. MODIFICATIONS ------------- - update class name to AnalyticsExtensions RESULT ------ Fixed a typo in AnalyticsExtensions class name. Change-Id: Iba6ae0387dde467215df59c5ec637b5f7f9419d5 Reviewed-on: http://review.couchbase.org/109463 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- Src/Couchbase/Analytics/Ingestion/AnalyticsIngester.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Src/Couchbase/Analytics/Ingestion/AnalyticsIngester.cs b/Src/Couchbase/Analytics/Ingestion/AnalyticsIngester.cs index 8f6d58ce2..98b94e908 100644 --- a/Src/Couchbase/Analytics/Ingestion/AnalyticsIngester.cs +++ b/Src/Couchbase/Analytics/Ingestion/AnalyticsIngester.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; @@ -8,10 +8,11 @@ namespace Couchbase.Analytics.Ingestion { - public static class AnalyticsExtensionsnsns + public static class AnalyticsExtensions { /// /// Executes a query and ingests the results as documents into Couchbase server for further analytics. + /// NOTE: This is an experimental feature and is subject to change. /// /// /// From 518a7653be72852449e2f98ab3ef09f50839645e Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 21 May 2019 22:21:35 +0100 Subject: [PATCH 16/70] NCBC-1816: Add support for exporting/importing deferred Analytics queries MOTIVATION ---------- As part of an up coming Couchbase Server release, it will be possible to created deferred Analytics queries where the server will return a handle that can be used to query the status and retrieve results. The SDKs need to be able to allow these handles be persisted between SDK life cycles with an Export/Import system. MODIFICATIONS ------------- - add Export/Import methods to IBucket, CouchbaseBucket and MemcachedBucket, IAnalyticsClient and AnalyticsClient - add unit tests to verify expected behaviour RESULT ------ It's now possible to export and import deferred analytics query handles using the SDK. Change-Id: I59d64c730e3146d2e01b13f9f65812dc88f60858 Reviewed-on: http://review.couchbase.org/109461 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- .../Analytics/AnalyticsClientTests.cs | 66 +++++++++++++++++++ Src/Couchbase/Analytics/AnalyticsClient.cs | 31 +++++++++ Src/Couchbase/Analytics/IAnalyticsClient.cs | 21 +++++- Src/Couchbase/Core/IBucket.cs | 19 ++++++ Src/Couchbase/CouchbaseBucket.cs | 14 ++++ Src/Couchbase/MemcachedBucket.cs | 12 ++++ 6 files changed, 162 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase.UnitTests/Analytics/AnalyticsClientTests.cs b/Src/Couchbase.UnitTests/Analytics/AnalyticsClientTests.cs index 0d1aa0ed5..95dce98f6 100644 --- a/Src/Couchbase.UnitTests/Analytics/AnalyticsClientTests.cs +++ b/Src/Couchbase.UnitTests/Analytics/AnalyticsClientTests.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Net; using System.Net.Http; @@ -7,7 +8,9 @@ using Couchbase.N1QL; using Couchbase.UnitTests.Utils; using Couchbase.Views; +using Moq; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using NUnit.Framework; namespace Couchbase.UnitTests.Analytics @@ -123,5 +126,68 @@ public void When_deferred_is_true_query_result_is_DeferredAnalyticsResult() var deferredResult = (AnalyticsDeferredResultHandle) result.Handle; Assert.AreEqual("handle", deferredResult.HandleUri); } + + [Test] + public void Can_export_deferred_handle() + { + const string handleUri = "/analytics/service/status/3-0"; + const string expectedJson = "{\"v\":1,\"uri\":\"/analytics/service/status/3-0\"}"; + var handle = new AnalyticsDeferredResultHandle(null, null, null, handleUri); + + var context = ContextFactory.GetCouchbaseContext(); + context.AnalyticsUris.Add(new FailureCountingUri("http://localhost")); + + var httpClient = new HttpClient( + FakeHttpMessageHandler.Create(request => new HttpResponseMessage(HttpStatusCode.OK)) + ); + + var client = new AnalyticsClient(httpClient, + new JsonDataMapper(context.ClientConfig), + context); + + var encodedHandle = client.ExportDeferredQueryHandle(handle); + Assert.AreEqual(expectedJson, encodedHandle); + } + + [Test] + public void Can_import_deferred_handle() + { + const string expectedHandle = "/analytics/service/status/3-0"; + const string json = "{\"v\":1,\"uri\":\"/analytics/service/status/3-0\"}"; + + var context = ContextFactory.GetCouchbaseContext(); + context.AnalyticsUris.Add(new FailureCountingUri("http://localhost")); + + var httpClient = new HttpClient( + FakeHttpMessageHandler.Create(request => new HttpResponseMessage(HttpStatusCode.OK)) + ); + + var client = new AnalyticsClient(httpClient, + new JsonDataMapper(context.ClientConfig), + context); + + var handle = client.ImportDeferredQueryHandle(json); + Assert.IsNotNull(handle); + Assert.AreEqual(expectedHandle, (handle as AnalyticsDeferredResultHandle).HandleUri); + } + + [TestCase(null)] + [TestCase("")] + public void Import_throws_exception_when_json_is_invalid(string handleUri) + { + var context = ContextFactory.GetCouchbaseContext(); + context.AnalyticsUris.Add(new FailureCountingUri("http://localhost")); + + var httpClient = new HttpClient( + FakeHttpMessageHandler.Create(request => new HttpResponseMessage(HttpStatusCode.OK)) + ); + + var client = new AnalyticsClient(httpClient, + new JsonDataMapper(context.ClientConfig), + context); + + var json = JsonConvert.SerializeObject(new {v = 1, uri = handleUri}); + Assert.Throws(() => client.ImportDeferredQueryHandle(json)); + } } } diff --git a/Src/Couchbase/Analytics/AnalyticsClient.cs b/Src/Couchbase/Analytics/AnalyticsClient.cs index 74ed08f26..bc7a80cfe 100644 --- a/Src/Couchbase/Analytics/AnalyticsClient.cs +++ b/Src/Couchbase/Analytics/AnalyticsClient.cs @@ -11,6 +11,8 @@ using Couchbase.Tracing; using Couchbase.Utils; using Couchbase.Views; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Couchbase.Analytics { @@ -172,6 +174,35 @@ private static void ApplyCredentials(IAnalyticsRequest request, ClientConfigurat } } } + + /// + public string ExportDeferredQueryHandle(IAnalyticsDeferredResultHandle handle) + { + var json = new JObject(); + json["v"] = 1; + json["uri"] = (handle as AnalyticsDeferredResultHandle).HandleUri; + + return json.ToString(Formatting.None); + } + + /// + public IAnalyticsDeferredResultHandle ImportDeferredQueryHandle(string encodedHandle) + { + var json = JObject.Parse(encodedHandle); + if (json["v"].Value() != "1") + { + throw new ArgumentException("Invalid encoded handle."); + } + + var uri = json["uri"].Value(); + if (string.IsNullOrWhiteSpace(uri)) + { + throw new ArgumentException("Invalid encoded handle."); + } + + var result = new AnalyticsResult {Status = QueryStatus.Running}; // default to running + return new AnalyticsDeferredResultHandle(result, HttpClient, DataMapper, uri); + } } } diff --git a/Src/Couchbase/Analytics/IAnalyticsClient.cs b/Src/Couchbase/Analytics/IAnalyticsClient.cs index f076d86d1..3edb19f27 100644 --- a/Src/Couchbase/Analytics/IAnalyticsClient.cs +++ b/Src/Couchbase/Analytics/IAnalyticsClient.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; @@ -27,6 +27,25 @@ public interface IAnalyticsClient /// A cancellation token that can be used to cancel the request. /// A that can be awaited on for the results. Task> QueryAsync(IAnalyticsRequest request, CancellationToken token); + + /// + /// Exports a deferred analytics query handle into an encoded format. + /// NOTE: This is an experimental feature is subject to change. + /// + /// The type to deserialize the results to. + /// The deferred analytics query handle. + /// The encoded query handle as a JSON . + string ExportDeferredQueryHandle(IAnalyticsDeferredResultHandle handle); + + /// + /// Imports a deferred analytics query handle. + /// NOTE: This is an experimental feature is subject to change. + /// + /// The type to deserialze results to + /// The encoded query handle. + /// An instance of that can be sued to retrieve results + /// from an deferred analytics query. + IAnalyticsDeferredResultHandle ImportDeferredQueryHandle(string encodedHandle); } } diff --git a/Src/Couchbase/Core/IBucket.cs b/Src/Couchbase/Core/IBucket.cs index 7cffce0ef..7f3a759b0 100644 --- a/Src/Couchbase/Core/IBucket.cs +++ b/Src/Couchbase/Core/IBucket.cs @@ -3634,6 +3634,25 @@ public interface IBucket : IDisposable /// An instance of with the result of the query. Task> QueryAsync(IAnalyticsRequest analyticsRequest, CancellationToken cancellationToken); + /// + /// Exports a deferred analytics query handle into an encoded format. + /// NOTE: This is an experimental feature is subject to change. + /// + /// The type to deserialize the results to. + /// The deferred analytics query handle. + /// The encoded query handle as a JSON . + string ExportDeferredAnalyticsQueryHandle(IAnalyticsDeferredResultHandle handle); + + /// + /// Imports a deferred analytics query handle. + /// NOTE: This is an experimental feature is subject to change. + /// + /// The type to deserialze results to + /// The encoded query handle. + /// An instance of that can be sued to retrieve results + /// from an deferred analytics query. + IAnalyticsDeferredResultHandle ImportDeferredAnalyticsQueryHandle(string encodedHandle); + /// /// Creates an instance of an object that implements , which targets a given bucket, design document and a published view. /// diff --git a/Src/Couchbase/CouchbaseBucket.cs b/Src/Couchbase/CouchbaseBucket.cs index 8bf005dde..99dd67253 100644 --- a/Src/Couchbase/CouchbaseBucket.cs +++ b/Src/Couchbase/CouchbaseBucket.cs @@ -7944,6 +7944,20 @@ public Task> QueryAsync(IAnalyticsRequest analyticsReques return _requestExecuter.SendWithRetryAsync(analyticsRequest, cancellationToken); } + /// + public string ExportDeferredAnalyticsQueryHandle(IAnalyticsDeferredResultHandle handle) + { + CheckDisposed(); + return _configInfo.GetAnalyticsNode().AnalyticsClient.ExportDeferredQueryHandle(handle); + } + + /// + public IAnalyticsDeferredResultHandle ImportDeferredAnalyticsQueryHandle(string encodedHandle) + { + CheckDisposed(); + return _configInfo.GetAnalyticsNode().AnalyticsClient.ImportDeferredQueryHandle(encodedHandle); + } + #endregion #region Diagnostics diff --git a/Src/Couchbase/MemcachedBucket.cs b/Src/Couchbase/MemcachedBucket.cs index b330b0c8a..dec349fb0 100644 --- a/Src/Couchbase/MemcachedBucket.cs +++ b/Src/Couchbase/MemcachedBucket.cs @@ -6956,6 +6956,18 @@ public Task> QueryAsync(IAnalyticsRequest request, Cancel throw new NotSupportedException("This method is only supported on Couchbase Bucket (persistent) types."); } + /// + public string ExportDeferredAnalyticsQueryHandle(IAnalyticsDeferredResultHandle handle) + { + throw new NotSupportedException("This method is only supported on Couchbase Bucket (persistent) types."); + } + + /// + public IAnalyticsDeferredResultHandle ImportDeferredAnalyticsQueryHandle(string encodedHandle) + { + throw new NotSupportedException("This method is only supported on Couchbase Bucket (persistent) types."); + } + #endregion #region Diagnostics From 866edbb04e86cb01321a34fb7c1ad41ec9e7b31b Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 4 Jun 2019 10:54:13 +0100 Subject: [PATCH 17/70] NCBC-1974: Add NuGet.config with default package source MOTIVATION ---------- When publishing packages to nuget using the dotnet CLI, you need to provide the package source to publish to. This is conventionally done by including a NuGet.config in the source repository. MODIFICATIONS ------------- - add basic nuget.config with default package source RESULT ------ Jenkins CI can now successfully publish packages using the default package source in the nuget.config. Change-Id: I2ecd4619b08f85057d4fdfde512f5b34c10f77a5 Reviewed-on: http://review.couchbase.org/110099 Reviewed-by: Charles Dixon Tested-by: Build Bot --- NuGet.config | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 NuGet.config diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 000000000..cfa230142 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 5fb5f4e5935c267de759315d6bca57666236b2fc Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 4 Jun 2019 16:43:18 +0100 Subject: [PATCH 18/70] NCBC-1975: Correct expiration and CAS parameter order in UpsertAsync overload MOTIVATION ---------- There are a number of overloads for UpsertAsync and the one that takes the key, value, expiration, replicatedto, persistTo and timeout incorrectly positions the expiration and CAS parameters to the next overload. MODIFICATIONS ------------- - switch the expiration and CAS parameters in the UpertAsync overload - add unit test to verify behavior RESULT ------ The UpsertAync overload passes the expiration and CAS parameters in the correct position. Change-Id: Ibbcbbdfc0188974c33e7cc0d8fbd7886fd6cea93 Reviewed-on: http://review.couchbase.org/110149 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- .../CouchbaseBucketTests.cs | 25 ++++++++++++++++++- Src/Couchbase/CouchbaseBucket.cs | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Src/Couchbase.UnitTests/CouchbaseBucketTests.cs b/Src/Couchbase.UnitTests/CouchbaseBucketTests.cs index e902ece46..bad179e97 100644 --- a/Src/Couchbase.UnitTests/CouchbaseBucketTests.cs +++ b/Src/Couchbase.UnitTests/CouchbaseBucketTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Security.Authentication; using System.Threading.Tasks; @@ -124,6 +124,29 @@ public void ExistsAsync_AnyKeyState_ReturnsExpectedResult(KeyState keyState, boo #region Upsert + [Test] + public async Task UpsertAsyncOverloadPassesExpirationAndCasParametersAsExpected() + { + var mockOperationResult = new Mock>(); + + var mockRequestExecutor = new Mock(); + mockRequestExecutor.Setup(x => x.SendWithDurabilityAsync(It.IsAny>(), false, ReplicateTo.One, PersistTo.One, null, null)) + .Returns(Task.FromResult(mockOperationResult.Object)); + + var bucket = new CouchbaseBucket(mockRequestExecutor.Object, new DefaultConverter(), new DefaultTranscoder()); + + await bucket.UpsertAsync("key", new { }, TimeSpan.FromSeconds(10), ReplicateTo.One, PersistTo.One, TimeSpan.FromSeconds(5)); + + mockRequestExecutor.Verify(x => x.SendWithDurabilityAsync( + It.Is>(set => set.Expires == 10 && set.Cas == 0 && set.Timeout == 5), + false, + ReplicateTo.One, + PersistTo.One, + null, + null) + ); + } + #region Upsert Disposed Bucket [Test()] diff --git a/Src/Couchbase/CouchbaseBucket.cs b/Src/Couchbase/CouchbaseBucket.cs index 99dd67253..d1294f5af 100644 --- a/Src/Couchbase/CouchbaseBucket.cs +++ b/Src/Couchbase/CouchbaseBucket.cs @@ -5559,7 +5559,7 @@ public IOperationResult Upsert(string key, T value, ulong cas, uint expira public Task> UpsertAsync(string key, T value, TimeSpan expiration, ReplicateTo replicateTo, PersistTo persistTo, TimeSpan timeout) { - return UpsertAsync(key, value, expiration.ToTtl(), 0, replicateTo, persistTo, timeout); + return UpsertAsync(key, value, 0, expiration.ToTtl(), replicateTo, persistTo, timeout); } /// From 738ea7578f26ef6c9f09b487c17a4cb278eabef9 Mon Sep 17 00:00:00 2001 From: Christopher Lupo Date: Fri, 31 May 2019 14:29:36 -0400 Subject: [PATCH 19/70] NCBC-1983: Add a missing State property to IEndPointDiagnostics MOTIVATION ---------- The EndPointDiagnostics.State property is missing from IEndPointDiagnostics MODIFICATIONS ------------- Add the State property to IEndPointDiagnostics RESULT ------ Added a missing State property Change-Id: I3347baafb2fc6751030ebbff57cf6e4ac3dceae6 Reviewed-on: http://review.couchbase.org/110364 Reviewed-by: Mike Goldsmith Tested-by: Build Bot --- Src/Couchbase/Core/Monitoring/IEndpointDiagnostics.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase/Core/Monitoring/IEndpointDiagnostics.cs b/Src/Couchbase/Core/Monitoring/IEndpointDiagnostics.cs index bdcd3bb12..5b48796c9 100644 --- a/Src/Couchbase/Core/Monitoring/IEndpointDiagnostics.cs +++ b/Src/Couchbase/Core/Monitoring/IEndpointDiagnostics.cs @@ -28,7 +28,7 @@ public interface IEndpointDiagnostics long? LastActivity { get; } /// - /// Gets the latency for service endpint expressed as microseconds. + /// Gets the latency for service endpoint expressed as microseconds. /// long? Latency { get; } @@ -37,5 +37,10 @@ public interface IEndpointDiagnostics /// This could be the bucket name for service endpoints. /// string Scope { get; } + + /// + /// Gets the service state. + /// + ServiceState? State { get; } } } From 66ed8104eaeb1782b853651d548c5e83117ba1b7 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Fri, 7 Jun 2019 12:13:46 -0700 Subject: [PATCH 20/70] NCBC-1984: Timestamp underflow causes expiration to be set to infinite Motivation ---------- Fixes a casting issue that happens during the unix time conversion - at a point the expiry is converted to a TimeSpan and then the TotalMilliseconds property is used which converts the value to a double (.999) which then is converted to a unsigned integer thus .NET makes it a zero. Modifications ------------- - Ensure values less than 1000ms are converted to 1s. All other non-whole seconds are truncated down to the next whole number; e.g. 2500ms is rounded down to 2s. - Add unit test Results ------- When the expiry is set as an unsigned int, this value is passed directly to IO without converting to a TimeStamp first. Change-Id: If0ec9914f8088776f3b82fa77eab198a1eafeda5 Reviewed-on: http://review.couchbase.org/110385 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- .../Utils/TimeSpanExtensionTests.cs | 29 +++++++++++++++++++ Src/Couchbase/Utils/TimeSpanExtensions.cs | 24 ++++++++++----- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Src/Couchbase.UnitTests/Utils/TimeSpanExtensionTests.cs b/Src/Couchbase.UnitTests/Utils/TimeSpanExtensionTests.cs index e940dc653..5f82a6405 100644 --- a/Src/Couchbase.UnitTests/Utils/TimeSpanExtensionTests.cs +++ b/Src/Couchbase.UnitTests/Utils/TimeSpanExtensionTests.cs @@ -40,5 +40,34 @@ public void Test_TryConverToMicros(string duration, long? expected) Assert.IsFalse(result); } } + + [Test] + [TestCase(0u, 0u)] + [TestCase(999u, 1u)] + [TestCase(1001u, 1u)] + [TestCase(2500u, 2u)] + public void Test_ToTtl(uint expiry, uint expected) + { + var doc = new Document + { + Expiry = expiry + }; + + var actual = doc.Expiry.ToTtl(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(0u, 0u)] + [TestCase(999u, 1u)] + [TestCase(1001u, 1u)] + [TestCase(2500u, 2u)] + public void Test_ToTtl_TimeStamp(uint expiry, uint expected) + { + var actual = TimeSpan.FromMilliseconds(expiry).ToTtl(); + + Assert.AreEqual(expected, actual); + } } } diff --git a/Src/Couchbase/Utils/TimeSpanExtensions.cs b/Src/Couchbase/Utils/TimeSpanExtensions.cs index 268dde067..db16d8204 100644 --- a/Src/Couchbase/Utils/TimeSpanExtensions.cs +++ b/Src/Couchbase/Utils/TimeSpanExtensions.cs @@ -19,14 +19,14 @@ public static uint ToTtl(this TimeSpan duration) { if (duration <= TimeSpan.FromDays(30)) { + //round up so ttl is not infinite (0) + if (duration.TotalMilliseconds > 0 && duration.TotalMilliseconds < 1000) return 1; return (uint)duration.TotalSeconds; } - else - { - var dateExpiry = DateTime.UtcNow + duration; - var unixTimeStamp = (uint) (dateExpiry.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; - return unixTimeStamp; - } + + var dateExpiry = DateTime.UtcNow + duration; + var unixTimeStamp = (uint) (dateExpiry.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; + return unixTimeStamp; } /// @@ -36,7 +36,17 @@ public static uint ToTtl(this TimeSpan duration) /// The TTL, expressed as a unix-based TTL in milliseconds. public static uint ToTtl(this uint duration) { - return ToTtl(TimeSpan.FromMilliseconds(duration)); + const uint thirtyDaysInMilliseconds = 2592000000; + if (duration <= thirtyDaysInMilliseconds) + { + //round up so ttl is not infinite (0) + if (duration > 0 && duration < 1000) return 1; + return duration / 1000; + } + + var dateExpiry = DateTime.UtcNow + TimeSpan.FromMilliseconds(duration); + var unixTimeStamp = (uint) (dateExpiry.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; + return unixTimeStamp; } /// From 493a944c3eb227e24a4c7509f46c92381cdbfd57 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Fri, 21 Jun 2019 09:05:37 -0700 Subject: [PATCH 21/70] NCBC-1998: Invalid Encoding parameter causes HTTP 400 bad request in Query Motivation ---------- Encoding enumeration only has one value Encoding.Utf8 which is ToString()'d and sent to the server as "Utf8"; the server fails with HTTP 400 Bad Request because the expected value for Encoding is "UTF-8". Modifications ------------- - Add extension method GetName() to convert the enum value from "Utf8" to "UTF-8". - Add QueryOptionsTests class - Update QueryRequest to use EncodingExtensions.GetName() Result ------ The correct encoding value is sent to the server. Change-Id: Ie40ec9141b12f8ab723a9521318eacc9ad23a322 Reviewed-on: http://review.couchbase.org/111033 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- Src/Couchbase.UnitTests/N1Ql/QueryRequestTests.cs | 5 +++-- Src/Couchbase/N1QL/Encoding.cs | 7 ++----- Src/Couchbase/N1QL/QueryRequest.cs | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Src/Couchbase.UnitTests/N1Ql/QueryRequestTests.cs b/Src/Couchbase.UnitTests/N1Ql/QueryRequestTests.cs index b8602a2eb..7d52b3681 100644 --- a/Src/Couchbase.UnitTests/N1Ql/QueryRequestTests.cs +++ b/Src/Couchbase.UnitTests/N1Ql/QueryRequestTests.cs @@ -207,7 +207,8 @@ private IQueryRequest CreateFullQueryRequest() .Timeout(new TimeSpan(0, 0, 0, 0, 10000)) .Compression(Compression.RLE) .AddCredentials("authenticated", "secret", false) - .AddPositionalParameter("boo"); + .AddPositionalParameter("boo") + .Encoding(Encoding.Utf8); } [Test] @@ -369,7 +370,7 @@ public void Test_ToString_Returns_Statment() var request = CreateFullQueryRequest(); QuerySequenceGenerator.Reset(); request.ClientContextId("0"); - Assert.AreEqual("http://localhost:8093/query[{\"statement\":\"SELECT * from Who WHERE $1\",\"timeout\":\"10000ms\",\"readonly\":false,\"metrics\":true,\"args\":[\"boo\"],\"compression\":\"RLE\",\"signature\":true,\"scan_consistency\":\"request_plus\",\"scan_wait\":\"100ms\",\"pretty\":true,\"creds\":[{\"user\":\"local:authenticated\",\"pass\":\"secret\"}],\"client_context_id\":\"0::0\"}]", request.ToString()); + Assert.AreEqual("http://localhost:8093/query[{\"statement\":\"SELECT * from Who WHERE $1\",\"timeout\":\"10000ms\",\"readonly\":false,\"metrics\":true,\"args\":[\"boo\"],\"encoding\":\"UTF-8\",\"compression\":\"RLE\",\"signature\":true,\"scan_consistency\":\"request_plus\",\"scan_wait\":\"100ms\",\"pretty\":true,\"creds\":[{\"user\":\"local:authenticated\",\"pass\":\"secret\"}],\"client_context_id\":\"0::0\"}]", request.ToString()); } [Test] diff --git a/Src/Couchbase/N1QL/Encoding.cs b/Src/Couchbase/N1QL/Encoding.cs index 38dd55f1f..fa88f928e 100644 --- a/Src/Couchbase/N1QL/Encoding.cs +++ b/Src/Couchbase/N1QL/Encoding.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.ComponentModel; namespace Couchbase.N1QL { public enum Encoding { + [Description("UTF-8")] Utf8 } } diff --git a/Src/Couchbase/N1QL/QueryRequest.cs b/Src/Couchbase/N1QL/QueryRequest.cs index 8f2ab75bd..d32e879fb 100644 --- a/Src/Couchbase/N1QL/QueryRequest.cs +++ b/Src/Couchbase/N1QL/QueryRequest.cs @@ -777,7 +777,7 @@ private IDictionary GetFormValues(bool generateNewId) } if (_encoding.HasValue) { - formValues.Add(QueryParameters.Encoding, _encoding.Value.ToString()); + formValues.Add(QueryParameters.Encoding, _encoding.Value.GetDescription()); } if (_compression.HasValue) { From 28cc37c5eb2ca167b6c75f6164c980d981787e88 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 2 Jul 2019 20:18:36 +0000 Subject: [PATCH 22/70] Revert "NCBC-1927: Resolve vbucket server map endpoints if different from bucket config server list" This reverts commit fba2e11be9f18bf2908c3f6b0ae7c31fdb974d1f. Reason for revert: Causes bug described in NCBC-2002 Change-Id: I9d9dcde17c27a670c4c190f5e338005e0f6af42e Reviewed-on: http://review.couchbase.org/111507 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- .../CarrierPublication/CarrierPublicationProvider.cs | 12 ------------ .../Server/Serialization/VBucketServerMap.cs | 5 ----- 2 files changed, 17 deletions(-) diff --git a/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs b/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs index 5d6c2131d..b467bcf4c 100644 --- a/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs +++ b/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs @@ -97,18 +97,6 @@ public override IConfigInfo GetConfig(string bucketName, string username, string // Set network type for bucket configuration bucketConfig.NetworkType = bucketConfiguration.NetworkType; - // get list of node ip/port endpoints - var endpoints = bucketConfig.GetNodes() - .Select(x => x.GetIPEndPoint(bucketConfiguration.UseSsl).ToString()) - .ToArray(); - - // if vbucketmap servers are different, use resolved endpoints instead - if (!bucketConfig.VBucketServerMap.ServerList.SequenceEqual(endpoints)) - { - bucketConfig.VBucketServerMap.ServerList = endpoints; - bucketConfig.VBucketServerMap.ClearIPEndPoints(); - } - configInfo = new CouchbaseConfigContext(bucketConfig, ClientConfig, IOServiceFactory, diff --git a/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs b/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs index f45d65e4d..3dc0d49ce 100644 --- a/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs +++ b/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs @@ -46,11 +46,6 @@ public List IPEndPoints } } - public void ClearIPEndPoints() - { - _ipEndPoints = null; - } - public bool Equals(VBucketServerMap other) { return (other != null From 8798645ebb766fabd0f223822e73728188504916 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Mon, 1 Jul 2019 17:46:07 -0700 Subject: [PATCH 23/70] NCBC-2002: ServiceNotSupportedException: Data service not found Motivation ---------- Fixes a regression caused by NCBC-1927 where if an alternate address may cause the data service to appear to not be configured. Alternate address is encountered in environments like Kubernetes where the external address and the internal addresses differ. Modifications ------------ - Check for alternate address; if found make the SDK use the alternate address for all internal communication - Remove logic introduced in NCBC-1927 - Remove OnDerserialization event from VBucketKeyMapper and instead delagate call in OnDerserilization event in BucketConfig Result ------ If alternate addresses are detected the client will use them; the client will detect the data service and not error. Change-Id: Ie7cacce15c8fc725e42dc3c8f6b89ad0be06a6a9 Reviewed-on: http://review.couchbase.org/111462 Reviewed-by: Mike Goldsmith Tested-by: Build Bot --- .../Configuration/BucketConfigTests_K8.cs | 22 + .../Serialization/BucketConfig2.cs | 122 ++ .../Couchbase.UnitTests.csproj | 4 + Src/Couchbase.UnitTests/Data/k8config2.json | 1170 +++++++++++++++++ Src/Couchbase.UnitTests/ResourceHelper.cs | 2 +- .../Serialization/AlternateAddressesConfig.cs | 2 + .../Server/Serialization/BucketConfig.cs | 30 +- .../Server/Serialization/NodeExt.cs | 2 + .../Server/Serialization/VBucketServerMap.cs | 15 +- 9 files changed, 1355 insertions(+), 14 deletions(-) create mode 100644 Src/Couchbase.UnitTests/Configuration/BucketConfigTests_K8.cs create mode 100644 Src/Couchbase.UnitTests/Configuration/Serialization/BucketConfig2.cs create mode 100644 Src/Couchbase.UnitTests/Data/k8config2.json diff --git a/Src/Couchbase.UnitTests/Configuration/BucketConfigTests_K8.cs b/Src/Couchbase.UnitTests/Configuration/BucketConfigTests_K8.cs new file mode 100644 index 000000000..f534e3534 --- /dev/null +++ b/Src/Couchbase.UnitTests/Configuration/BucketConfigTests_K8.cs @@ -0,0 +1,22 @@ +using System; +using Couchbase.Configuration.Server.Serialization; +using Couchbase.Utils; +using Newtonsoft.Json; +using NUnit.Framework; + +namespace Couchbase.UnitTests.Configuration +{ + [TestFixture] + public class BucketConfigTestsK8 + { + [Test] + public void Test_Parse() + { + var json = ResourceHelper.ReadResource(@"Data\k8config2.json"); + var config = JsonConvert.DeserializeObject(json); + + Assert.AreEqual(config.VBucketServerMap.IPEndPoints[0], IPEndPointExtensions.GetEndPoint(config.NodesExt[0].Hostname)); + Assert.AreEqual(config.VBucketServerMap.IPEndPoints[1], IPEndPointExtensions.GetEndPoint(config.NodesExt[1].Hostname)); + } + } +} diff --git a/Src/Couchbase.UnitTests/Configuration/Serialization/BucketConfig2.cs b/Src/Couchbase.UnitTests/Configuration/Serialization/BucketConfig2.cs new file mode 100644 index 000000000..d143582b5 --- /dev/null +++ b/Src/Couchbase.UnitTests/Configuration/Serialization/BucketConfig2.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Couchbase.UnitTests.Configuration.Serialization +{ + public class Ports +{ + public int proxy { get; set; } + public int direct { get; set; } + public int sslDirect { get; set; } + public int httpsCAPI { get; set; } + public int httpsMgmt { get; set; } +} + +public class Node +{ + public string couchApiBase { get; set; } + public string couchApiBaseHTTPS { get; set; } + public double replication { get; set; } + public object clusterMembership { get; set; } + public object status { get; set; } + public bool thisNode { get; set; } + public string hostname { get; set; } + public int clusterCompatibility { get; set; } + public object version { get; set; } + public object os { get; set; } + public object otpNode { get; set; } + public Ports ports { get; set; } + public object services { get; set; } +} + +public class Services +{ + public int fts { get; set; } + public int ftsSSL { get; set; } + public int mgmt { get; set; } + public int moxi { get; set; } + public int kv { get; set; } + public int capi { get; set; } + public int kvSSL { get; set; } + public int capiSSL { get; set; } + public int mgmtSSL { get; set; } + public int projector { get; set; } + public int indexAdmin { get; set; } + public int indexScan { get; set; } + public int indexHttp { get; set; } + public int indexStreamInit { get; set; } + public int indexStreamCatchup { get; set; } + public int indexStreamMaint { get; set; } + public int n1ql { get; set; } + public int n1qlSSL { get; set; } + public int cbas { get; set; } + public int cbasSSL { get; set; } +} + +public class External +{ + public string hostname { get; set; } + public object ports { get; set; } +} + +public class AlternateAddresses +{ + public External external { get; set; } +} + +public class NodesExt +{ + public Services services { get; set; } + public string hostname { get; set; } + public AlternateAddresses alternateAddresses { get; set; } +} + +public class Ddocs +{ + public string uri { get; set; } +} + +public class VBucketServerMap +{ + public string hashAlgorithm { get; set; } + public int numReplicas { get; set; } + public List serverList { get; set; } + public List> vBucketMap { get; set; } + public List vBucketMapForward { get; set; } +} + +public class RootObject +{ + public string name { get; set; } + public string bucketType { get; set; } + public object authType { get; set; } + public object saslPassword { get; set; } + public int proxyPort { get; set; } + public bool replicaIndex { get; set; } + public string uri { get; set; } + public string streamingUri { get; set; } + public object terseBucketsBase { get; set; } + public object terseStreamingBucketsBase { get; set; } + public object localRandomKeyUri { get; set; } + public object controllers { get; set; } + public List nodes { get; set; } + public List nodesExt { get; set; } + public object stats { get; set; } + public Ddocs ddocs { get; set; } + public string nodeLocator { get; set; } + public string uuid { get; set; } + public VBucketServerMap vBucketServerMap { get; set; } + public int replicaNumber { get; set; } + public int threadsNumber { get; set; } + public object quota { get; set; } + public object basicStats { get; set; } + public string bucketCapabilitiesVer { get; set; } + public List bucketCapabilities { get; set; } + public int rev { get; set; } + public bool UseSsl { get; set; } + public string SurrogateHost { get; set; } + public object Username { get; set; } + public string NetworkType { get; set; } +} +} diff --git a/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj b/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj index 1b46df756..5322431b4 100644 --- a/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj +++ b/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj @@ -61,11 +61,15 @@ + + + + diff --git a/Src/Couchbase.UnitTests/Data/k8config2.json b/Src/Couchbase.UnitTests/Data/k8config2.json new file mode 100644 index 000000000..69d29cc21 --- /dev/null +++ b/Src/Couchbase.UnitTests/Data/k8config2.json @@ -0,0 +1,1170 @@ +{ + "rev": 88, + "name": "default", + "uri": "/pools/default/buckets/default?bucket_uuid=68cbc49af0da3aea408d4701e44a95f5", + "streamingUri": "/pools/default/bucketsStreaming/default?bucket_uuid=68cbc49af0da3aea408d4701e44a95f5", + "nodes": [ + { + "couchApiBase": "http://cb-example-0000.cb-example.default.svc:8092/default%2B68cbc49af0da3aea408d4701e44a95f5", + "hostname": "cb-example-0000.cb-example.default.svc:8091", + "ports": { + "proxy": 11211, + "direct": 11210 + }, + "alternateAddresses": { "external": { "hostname": "cb-example-0000.cb-example.marabine.co.uk" } } + }, + { + "couchApiBase": "http://cb-example-0001.cb-example.default.svc:8092/default%2B68cbc49af0da3aea408d4701e44a95f5", + "hostname": "cb-example-0001.cb-example.default.svc:8091", + "ports": { + "proxy": 11211, + "direct": 11210 + }, + "alternateAddresses": { "external": { "hostname": "cb-example-0001.cb-example.marabine.co.uk" } } + }, + { + "couchApiBase": "http://cb-example-0002.cb-example.default.svc:8092/default%2B68cbc49af0da3aea408d4701e44a95f5", + "hostname": "cb-example-0002.cb-example.default.svc:8091", + "ports": { + "proxy": 11211, + "direct": 11210 + }, + "alternateAddresses": { "external": { "hostname": "cb-example-0002.cb-example.marabine.co.uk" } } + } + ], + "nodesExt": [ + { + "services": { + "mgmt": 8091, + "mgmtSSL": 18091, + "cbas": 8095, + "cbasCc": 9111, + "cbasAdmin": 9110, + "cbasSSL": 18095, + "eventingAdminPort": 8096, + "eventingDebug": 9140, + "eventingSSL": 18096, + "fts": 8094, + "ftsSSL": 18094, + "indexAdmin": 9100, + "indexScan": 9101, + "indexHttp": 9102, + "indexStreamInit": 9103, + "indexStreamCatchup": 9104, + "indexStreamMaint": 9105, + "indexHttps": 19102, + "capiSSL": 18092, + "capi": 8092, + "kvSSL": 11207, + "projector": 9999, + "kv": 11210, + "moxi": 11211, + "n1ql": 8093, + "n1qlSSL": 18093 + }, + "thisNode": true, + "hostname": "cb-example-0000.cb-example.default.svc", + "alternateAddresses": { "external": { "hostname": "cb-example-0000.cb-example.marabine.co.uk" } } + }, + { + "services": { + "mgmt": 8091, + "mgmtSSL": 18091, + "cbas": 8095, + "cbasCc": 9111, + "cbasAdmin": 9110, + "cbasSSL": 18095, + "eventingAdminPort": 8096, + "eventingDebug": 9140, + "eventingSSL": 18096, + "fts": 8094, + "ftsSSL": 18094, + "indexAdmin": 9100, + "indexScan": 9101, + "indexHttp": 9102, + "indexStreamInit": 9103, + "indexStreamCatchup": 9104, + "indexStreamMaint": 9105, + "indexHttps": 19102, + "capiSSL": 18092, + "capi": 8092, + "kvSSL": 11207, + "projector": 9999, + "kv": 11210, + "moxi": 11211, + "n1ql": 8093, + "n1qlSSL": 18093 + }, + "hostname": "cb-example-0001.cb-example.default.svc", + "alternateAddresses": { "external": { "hostname": "cb-example-0001.cb-example.marabine.co.uk" } } + }, + { + "services": { + "mgmt": 8091, + "mgmtSSL": 18091, + "cbas": 8095, + "cbasCc": 9111, + "cbasAdmin": 9110, + "cbasSSL": 18095, + "eventingAdminPort": 8096, + "eventingDebug": 9140, + "eventingSSL": 18096, + "fts": 8094, + "ftsSSL": 18094, + "indexAdmin": 9100, + "indexScan": 9101, + "indexHttp": 9102, + "indexStreamInit": 9103, + "indexStreamCatchup": 9104, + "indexStreamMaint": 9105, + "indexHttps": 19102, + "capiSSL": 18092, + "capi": 8092, + "kvSSL": 11207, + "projector": 9999, + "kv": 11210, + "moxi": 11211, + "n1ql": 8093, + "n1qlSSL": 18093 + }, + "hostname": "cb-example-0002.cb-example.default.svc", + "alternateAddresses": { "external": { "hostname": "cb-example-0002.cb-example.marabine.co.uk" } } + } + ], + "nodeLocator": "vbucket", + "uuid": "68cbc49af0da3aea408d4701e44a95f5", + "ddocs": { "uri": "/pools/default/buckets/default/ddocs" }, + "vBucketServerMap": { + "hashAlgorithm": "CRC", + "numReplicas": 1, + "serverList": [ "cb-example-0000.cb-example.default.svc:11210", "cb-example-0001.cb-example.default.svc:11210", "cb-example-0002.cb-example.default.svc:11210" ], + "vBucketMap": [ + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 1 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 0, 2 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 0 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 1, 2 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 0 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ], + [ 2, 1 ] + ] + }, + "bucketCapabilitiesVer": "", + "bucketCapabilities": [ "couchapi", "xattr", "dcp", "cbhello", "touch", "cccp", "xdcrCheckpointing", "nodesExt" ] +} diff --git a/Src/Couchbase.UnitTests/ResourceHelper.cs b/Src/Couchbase.UnitTests/ResourceHelper.cs index 55aff16ab..25cbe00b5 100644 --- a/Src/Couchbase.UnitTests/ResourceHelper.cs +++ b/Src/Couchbase.UnitTests/ResourceHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/Src/Couchbase/Configuration/Server/Serialization/AlternateAddressesConfig.cs b/Src/Couchbase/Configuration/Server/Serialization/AlternateAddressesConfig.cs index 422b31ed3..bd51cd5a7 100644 --- a/Src/Couchbase/Configuration/Server/Serialization/AlternateAddressesConfig.cs +++ b/Src/Couchbase/Configuration/Server/Serialization/AlternateAddressesConfig.cs @@ -12,6 +12,8 @@ public sealed class AlternateAddressesConfig /// [JsonProperty("external")] public ExternalAddressesConfig External { get; set; } + + public bool HasExternalAddress => External?.Hostname != null; } } diff --git a/Src/Couchbase/Configuration/Server/Serialization/BucketConfig.cs b/Src/Couchbase/Configuration/Server/Serialization/BucketConfig.cs index 32c8b200e..a563b0e24 100644 --- a/Src/Couchbase/Configuration/Server/Serialization/BucketConfig.cs +++ b/Src/Couchbase/Configuration/Server/Serialization/BucketConfig.cs @@ -60,7 +60,7 @@ public BucketConfig() public Node[] Nodes { get; set; } [JsonProperty("nodesExt")] - public NodeExt[] NodesExt { get; set; } + public NodeExt[] NodesExt { get; set; } = new NodeExt[0]; [JsonProperty("stats")] public Stats Stats { get; set; } @@ -209,6 +209,34 @@ public bool IsVBucketServerMapEqual(IBucketConfig other) { return VBucketServerMap.Equals(other.VBucketServerMap); } + + [OnDeserialized] + internal void OnDeserializedMethod(StreamingContext context) + { + ResolveHostName(); + VBucketServerMap.EnsureIPEndPointsAreLoaded(); + } + + public void ResolveHostName() + { + for (var i = 0; i < VBucketServerMap.ServerList.Length; i++) + { + var nodeExt = NodesExt.FirstOrDefault(x => x.Hostname != null && VBucketServerMap.ServerList[i].Contains(x.Hostname)); + if (nodeExt != null && nodeExt.HasAlternateAddress && nodeExt.AlternateAddresses.HasExternalAddress) + { + //The SSL port is resolved later + VBucketServerMap.ServerList[i] = nodeExt.AlternateAddresses.External.Hostname + ":" + nodeExt.Services.KV; + } + } + + foreach (var nodeExt in NodesExt) + { + if (nodeExt.HasAlternateAddress && nodeExt.AlternateAddresses.HasExternalAddress) + { + nodeExt.Hostname = nodeExt.AlternateAddresses.External.Hostname+ ":" + nodeExt.Services.KV; + } + } + } } } diff --git a/Src/Couchbase/Configuration/Server/Serialization/NodeExt.cs b/Src/Couchbase/Configuration/Server/Serialization/NodeExt.cs index 4feeb41d0..b02c98a3b 100644 --- a/Src/Couchbase/Configuration/Server/Serialization/NodeExt.cs +++ b/Src/Couchbase/Configuration/Server/Serialization/NodeExt.cs @@ -33,6 +33,8 @@ public NodeExt() [JsonProperty("alternateAddresses")] public AlternateAddressesConfig AlternateAddresses { get; set; } + + public bool HasAlternateAddress => AlternateAddresses != null && AlternateAddresses.HasExternalAddress; } } diff --git a/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs b/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs index 3dc0d49ce..63397d5ee 100644 --- a/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs +++ b/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs @@ -1,8 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Runtime.Serialization; using Couchbase.Utils; using Newtonsoft.Json; @@ -73,7 +72,7 @@ public override int GetHashCode() } // ReSharper disable once InconsistentNaming - private void EnsureIPEndPointsAreLoaded() + internal void EnsureIPEndPointsAreLoaded() { lock (_syncObj) { @@ -87,14 +86,6 @@ private void EnsureIPEndPointsAreLoaded() } } } - - [OnDeserialized] - internal void OnDeserializedMethod(StreamingContext context) - { - // If we're deserializing the configuration, go ahead and load the endpoints in advance - // https://issues.couchbase.com/browse/NCBC-1614 - EnsureIPEndPointsAreLoaded(); - } } } @@ -119,4 +110,4 @@ internal void OnDeserializedMethod(StreamingContext context) * * ************************************************************/ -#endregion \ No newline at end of file +#endregion From cdb1030d64a08de8a2ab17176d7013ad6658dae6 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Wed, 3 Jul 2019 17:31:30 -0700 Subject: [PATCH 24/70] NCBC-2017:Subsequent calls to MutateInBuilders cause fields to be set to null Motivation ---------- Ssubsequent execute calls to the same MutateInBuilder cause all of the fields being mutated in the MutateIn to be set to null. Modifications ------------- - Write unit tests for single and multi mutations - Override SingleDocSingularMutationBase.GetResultWithValue and do not assign value from GetValue to the specs value, the will always be null. Results ------- Subsequent calls to the same MutateInBuilder do not cause the value to be set to null. Change-Id: Ib16e340c58856b3abf60738563eccd98ab5320c1 Reviewed-on: http://review.couchbase.org/111577 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- .../CouchbaseBucket_SubDocument_Tests.cs | 57 +++++++++++++++++++ .../SubDocument/SubDocSingularMutationBase.cs | 46 +++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs index d065fe101..730cdcfce 100644 --- a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs +++ b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs @@ -1671,6 +1671,63 @@ public async Task Can_upsert_full_doc_body() } } + [Test] + [TestCase(true)] + [TestCase(false)] + public void MutateIn_SingleMutate_Execute(bool useMutation) + { + var bucket = GetBucket(useMutation); + var key = "Test_Multiple_SingleMutate_Execute"; + var document = new Document + { + Id = key, + Content = new + { + name = "Couchbase" + } + }; + + bucket.Upsert(document); + var mutator = bucket.MutateIn(key).Upsert("name", "Matt"); + + mutator.Execute(); + var result = bucket.Get(key); + Assert.AreEqual("Matt", result.Value.name.ToString()); + + mutator.Execute(); + result = bucket.Get(key); + Assert.AreEqual("Matt", result.Value.name.ToString()); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MutateIn_MultipleMutate_Execute(bool useMutation) + { + var bucket = GetBucket(useMutation); + + var key = "Test_Multiple_MultiMutate_Execute"; + var document = new Document + { + Id = key, + Content = new + { + name = "Couchbase" + } + }; + + bucket.Upsert(document); + var mutator = bucket.MutateIn(key).Upsert("name", "Matt").Upsert("name", "John"); + + mutator.Execute(); + var result = bucket.Get(key); + Assert.AreEqual("John", result.Value.name.ToString()); + + mutator.Execute(); + result = bucket.Get(key); + Assert.AreEqual("John", result.Value.name.ToString()); + } + [OneTimeTearDown] public void OneTimeTearDown() { diff --git a/Src/Couchbase/IO/Operations/SubDocument/SubDocSingularMutationBase.cs b/Src/Couchbase/IO/Operations/SubDocument/SubDocSingularMutationBase.cs index 5c19c4387..0afee61e6 100644 --- a/Src/Couchbase/IO/Operations/SubDocument/SubDocSingularMutationBase.cs +++ b/Src/Couchbase/IO/Operations/SubDocument/SubDocSingularMutationBase.cs @@ -1,4 +1,7 @@ +using System; +using System.Collections.Generic; using Couchbase.Core; +using Couchbase.Core.IO.SubDocument; using Couchbase.Core.Transcoders; using Couchbase.IO.Utils; using Couchbase.Utils; @@ -79,6 +82,49 @@ public override void ReadExtras(byte[] buffer) { TryReadMutationToken(buffer); } + + public override IOperationResult GetResultWithValue() + { + var result = new DocumentFragment(Builder); + try + { + var status = GetResponseStatus(); + result.Success = GetSuccess(); + result.Message = GetMessage(); + result.Status = GetParentStatus(status); + result.Cas = Header.Cas; + result.Exception = Exception; + + GetValue(); + CurrentSpec.Status = status; + result.Value = new List { CurrentSpec }; + + // Read MutationToken after GetValue(), which may fill it with a value + result.Token = MutationToken ?? DefaultMutationToken; + + //clean up and set to null + if (!result.IsNmv()) + { + Data.Dispose(); + Data = null; + } + } + catch (Exception e) + { + result.Exception = e; + result.Success = false; + result.Status = ResponseStatus.ClientFailure; + } + finally + { + if (Data != null && !result.IsNmv()) + { + Data.Dispose(); + } + } + + return result; + } } } From e0df02ca99373866b282c8c5f657f029d287e34e Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 9 Jul 2019 15:28:45 +0100 Subject: [PATCH 25/70] NCBC-2021: Update MultiMutation to correctly read mutation token and offsets MOTIVATION ---------- When executing a Subdoc multi-mutation that includes a counter op that returns a value with enhanced durability enabled, the mutation result is incorrectly read because the body offsets are not correctly handled. MODIFICATIONS ------------- - set OperationBase._bodyLength when writing the request body - set MutationToken after reading body - use BodyOffset not Extras offset to read response body - add unit tests to verify expected behaviour for both sync and async paths RESULT ------ SubDoc multimutations correctly read mutation tokens and manage body offsets when using a counter operation and enhanced durability is enabled. Change-Id: I07096d56182d5ba8790cd73af3f17ccb2d61d222 Reviewed-on: http://review.couchbase.org/111743 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- .../CouchbaseBucket_SubDocument_Tests.cs | 38 +++++++++++++++++++ .../Operations/SubDocument/MultiMutation.cs | 14 +++---- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs index 730cdcfce..97dab64bc 100644 --- a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs +++ b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Couchbase.Core; +using Couchbase.Core.Buckets; using Couchbase.Core.IO.SubDocument; using Couchbase.IntegrationTests.Utils; using Couchbase.IO; @@ -1728,6 +1729,43 @@ public void MutateIn_MultipleMutate_Execute(bool useMutation) Assert.AreEqual("John", result.Value.name.ToString()); } + [TestCase(true)] + [TestCase(false)] + public void MutateIn_Counter_WithUpsert_ReturnsSuccess(bool useMutation) + { + var bucket = GetBucket(useMutation); + var key = "MutateInAsync_Counter_WithValidPathAndCreateParentsFalse_ReturnsSuccess"; + bucket.Upsert(key, new { foo = "bar", bar = "foo", count=0 }); + + var builder = bucket.MutateIn(key) + .Counter("baz", 1, false) + .Upsert("foo", "bar2", false); + + var result = builder.Execute(); + + Assert.AreEqual(ResponseStatus.Success, result.Status); + Assert.AreEqual(useMutation, result.Token.IsSet); + Assert.AreEqual(1, result.Content("baz")); + } + + [TestCase(true)] + [TestCase(false)] + public async Task MutateInAsync_Counter_WithUpsert_ReturnsSuccess(bool useMutation) + { + var bucket = GetBucket(useMutation); + var key = "MutateInAsync_Counter_WithValidPathAndCreateParentsFalse_ReturnsSuccess"; + bucket.Upsert(key, new { foo = "bar", bar = "foo", count=0 }); + + var builder = bucket.MutateIn(key) + .Counter("baz", 1, false) + .Upsert("foo", "bar2", false); + + var result = await builder.ExecuteAsync(); + + Assert.AreEqual(ResponseStatus.Success, result.Status); + Assert.AreEqual(useMutation, result.Token.IsSet); + } + [OneTimeTearDown] public void OneTimeTearDown() { diff --git a/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs b/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs index 574e2ae8a..9a7a14262 100644 --- a/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs +++ b/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs @@ -25,9 +25,8 @@ public MultiMutation(string key, MutateInBuilder mutateInBuilder, IVBucket vB public override byte[] Write() { // create this first because we need to iterate builder for operation specs - var bodyBytes = CreateBody(); - - var totalLength = OperationHeader.Length + KeyLength + ExtrasLength + bodyBytes.Length; + var bodyLength = BodyLength; // getter triggers BodyBytes to be set + var totalLength = OperationHeader.Length + KeyLength + ExtrasLength + bodyLength; var buffer = AllocateBuffer(totalLength); WriteHeader(buffer); @@ -35,8 +34,7 @@ public override byte[] Write() WriteKey(buffer, OperationHeader.Length + ExtrasLength); // manually write body to buffer so we won't re-create it - System.Buffer.BlockCopy(bodyBytes, 0, buffer, OperationHeader.Length + ExtrasLength + KeyLength, bodyBytes.Length); - //WriteBody(buffer, OperationHeader.Length + ExtrasLength + KeyLength); + System.Buffer.BlockCopy(BodyBytes, 0, buffer, OperationHeader.Length + ExtrasLength + KeyLength, bodyLength); return buffer; } @@ -126,8 +124,8 @@ public override IOperationResult GetResultWithValue() result.Status = GetResponseStatus(); result.Cas = Header.Cas; result.Exception = Exception; - result.Token = MutationToken ?? DefaultMutationToken; result.Value = GetCommandValues(); + result.Token = MutationToken ?? DefaultMutationToken; //clean up and set to null if (!result.IsNmv()) @@ -164,12 +162,12 @@ public IList GetCommandValues() ReadExtras(response); //all mutations successful - if (response.Length == OperationHeader.Length + Header.FramingExtrasLength) + if (response.Length == OperationHeader.Length + Header.FramingExtrasLength + Header.ExtrasLength) { return _mutateCommands; } - var indexOffset = Header.ExtrasOffset; + var indexOffset = Header.BodyOffset; var statusOffset = indexOffset + 1; var valueLengthOffset = indexOffset + 3; var valueOffset = indexOffset + 7; From c24f8bb5be3aec7f939e888fae906788d6aa27e8 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Tue, 9 Jul 2019 15:24:56 -0700 Subject: [PATCH 26/70] NCBC-2025: Add stream timeout: timeouts are not supported on this stream Motivation ---------- Fixes a bug where in some platforms the HTTP Config streaming will fail with a "Timeouts are not supported on this stream" message because the CanTimeout property on the underlying stream returns true. Modifications ------------- - Set the Timeout property to infinite on the underlying stream in ConfigTreadState Result ------ The stream should not timeout even when open for a very long time. Change-Id: I35df2652f1b3f366556c1afdcb561157671a8003 Reviewed-on: http://review.couchbase.org/111767 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- .../Server/Providers/Streaming/ConfigThreadState.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Src/Couchbase/Configuration/Server/Providers/Streaming/ConfigThreadState.cs b/Src/Couchbase/Configuration/Server/Providers/Streaming/ConfigThreadState.cs index 98ebb3c91..9bdbefcd5 100644 --- a/Src/Couchbase/Configuration/Server/Providers/Streaming/ConfigThreadState.cs +++ b/Src/Couchbase/Configuration/Server/Providers/Streaming/ConfigThreadState.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Net; @@ -99,10 +99,13 @@ public void ListenForConfigChanges() httpClient.GetAsync(streamingUri, HttpCompletionOption.ResponseHeadersRead, _cancellationToken) .Result; + response.EnsureSuccessStatusCode(); using (var stream = response.Content.ReadAsStreamAsync().Result) { + stream.ReadTimeout = Timeout.Infinite; + //this will cancel the infinite wait below _cancellationToken.Register(stream.Dispose); @@ -186,4 +189,4 @@ public void ListenForConfigChanges() * * ************************************************************/ -#endregion \ No newline at end of file +#endregion From 12cae55dd867d56f7cd29c4f64d226219a265918 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Tue, 9 Jul 2019 19:52:05 -0700 Subject: [PATCH 27/70] NCBC-2026: ArgumentOutOfRangeException: Index was out of range in Unit Tests Motivation ---------- Fixes a bug where a race condition may make the shared CP empty while trying to access a connection by index. Modifications ------------- - Use the extension method ElementAtOrDefault instead of the indexer or accessing a pool item at a specific index - If the default (null) is returned, make another attempt at Acquire() Result ------ ArgumentOutOfRangeException's will not be thrown if the pool is temporarily empty. Change-Id: Ie584a1a09c22ef8390efd6d0544e4a03c6811c28 Reviewed-on: http://review.couchbase.org/111775 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- Src/Couchbase/IO/SharedConnectionPool.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Src/Couchbase/IO/SharedConnectionPool.cs b/Src/Couchbase/IO/SharedConnectionPool.cs index 3fa70f2a1..ef5133bc8 100644 --- a/Src/Couchbase/IO/SharedConnectionPool.cs +++ b/Src/Couchbase/IO/SharedConnectionPool.cs @@ -67,9 +67,13 @@ public override IConnection Acquire() // ReSharper disable once InconsistentlySynchronizedField if (_connections.Count >= Configuration.MaxSize) { - var connection = _connections[GetIndex()]; + var connection = _connections.ElementAtOrDefault(GetIndex()); try { + if (connection == null) + { + return Acquire(); + } Authenticate(connection); EnableEnhancedAuthentication(connection); return connection; @@ -90,7 +94,7 @@ public override IConnection Acquire() { var connection = CreateAndAuthConnection(); _connections.Add(connection); - return _connections[GetIndex()]; + return _connections?.ElementAtOrDefault(GetIndex()); } } From 9534fe3fc487ad3644457a904341f156ec401fdb Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Fri, 12 Jul 2019 14:59:53 -0700 Subject: [PATCH 28/70] NCBC-2028: Stream.Timeout cannot be set on all platforms Motivation ---------- This fixes a regression in NCBC-2025 where the Stream.Timeout is setting in the HTTP Streaming mechanism, however, this is not supported on all platforms and causes an exception if Stream.CanTimeout is false and Steam.Timeout is set. Modifications ------------- - Add conditional check to ensure Stream.CanTimeout is true before setting Stream.Timeout to infinite in ConfigThreadState.cs Result ------ If the platform does not support setting Stream.Timeout, then it will not be set to Timeout.Infinite. Change-Id: I92db4a1768b7360e35d7be8a338dd0a0306a656e Reviewed-on: http://review.couchbase.org/111934 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- .../Server/Providers/Streaming/ConfigThreadState.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase/Configuration/Server/Providers/Streaming/ConfigThreadState.cs b/Src/Couchbase/Configuration/Server/Providers/Streaming/ConfigThreadState.cs index 9bdbefcd5..6ef1f9467 100644 --- a/Src/Couchbase/Configuration/Server/Providers/Streaming/ConfigThreadState.cs +++ b/Src/Couchbase/Configuration/Server/Providers/Streaming/ConfigThreadState.cs @@ -109,7 +109,10 @@ public void ListenForConfigChanges() //this will cancel the infinite wait below _cancellationToken.Register(stream.Dispose); - stream.ReadTimeout = Timeout.Infinite; + if (stream.CanTimeout) + { + stream.ReadTimeout = Timeout.Infinite; + } using (var reader = new StreamReader(stream, Encoding.UTF8, false)) { From 87a6849f4cfebd9350f2ab5b9e0be84d71d0b4e2 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 23 Jul 2019 15:05:12 +0100 Subject: [PATCH 29/70] NCBC-2038: MultuMutation does not encode null value correctly MOTIVATION ---------- When executing a multimutation that includes setting a value to null should work but is failing because the value bytes are omitted from the Subdoc packet. MODIFICATIONS ------------- - use the configured transcoder to get the value even when the value is null - add integration tests to verify behaviour for both sync and async paths RESULT ------ Executing a multimutation that includes setting a property value to null now succeeds. Change-Id: I6f9902af22dfa1c559748ceb6e699783fafc526c Reviewed-on: http://review.couchbase.org/112362 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- .../CouchbaseBucket_SubDocument_Tests.cs | 40 +++++++++++++++++++ .../Operations/SubDocument/MultiMutation.cs | 4 +- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs index 97dab64bc..1c290f23d 100644 --- a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs +++ b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs @@ -1766,6 +1766,46 @@ public async Task MutateInAsync_Counter_WithUpsert_ReturnsSuccess(bool useMutati Assert.AreEqual(useMutation, result.Token.IsSet); } + [TestCase(true)] + [TestCase(false)] + public void MutateIn_with_multimutate_can_set_property_value_to_null(bool useMutation) + { + var bucket = GetBucket(useMutation); + const string key = "MutateIn_with_multimutate_can_set_property_value_to_null"; + + bucket.Upsert(key, new { }); + + bucket.MutateIn(key) + .Upsert("nullProperty", null) + .Execute(); + + var result = bucket.MutateIn(key) + .Upsert("nullProperty", null) + .Upsert("name", "MutatedName") + .Execute(); + + Assert.AreEqual(ResponseStatus.Success, result.Status); + Assert.AreEqual(useMutation, result.Token.IsSet); + } + + [TestCase(true)] + [TestCase(false)] + public async Task MutateInAsync_with_multimutate_can_set_property_value_to_null(bool useMutation) + { + var bucket = GetBucket(useMutation); + const string key = "MutateInAsync_with_multimutate_can_set_property_value_to_null"; + + await bucket.UpsertAsync(key, new { }); + + var result = await bucket.MutateIn(key) + .Upsert("nullProperty", null) + .Upsert("name", "MutatedName") + .ExecuteAsync(); + + Assert.AreEqual(ResponseStatus.Success, result.Status); + Assert.AreEqual(useMutation, result.Token.IsSet); + } + [OneTimeTearDown] public void OneTimeTearDown() { diff --git a/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs b/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs index 9a7a14262..5e8c08657 100644 --- a/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs +++ b/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs @@ -63,7 +63,7 @@ public override byte[] CreateBody() var opcode = (byte)mutate.OpCode; var flags = (byte) mutate.PathFlags; var pathLength = string.IsNullOrWhiteSpace(mutate.Path) ? 0 : Encoding.UTF8.GetByteCount(mutate.Path); - var fragment = mutate.Value == null ? new byte[0] : GetBytes(mutate); + var fragment = GetBytes(mutate); var spec = new byte[pathLength + 8]; Converter.FromByte(opcode, spec, 0); @@ -104,7 +104,7 @@ public override void WriteExtras(byte[] buffer, int offset) } } - byte[] GetBytes(OperationSpec spec) + private byte[] GetBytes(OperationSpec spec) { var bytes = Transcoder.Serializer.Serialize(spec.Value); if (spec.RemoveBrackets) From e25ad8e8c3c4c5b3ee4fb402a62b0fe0df5bea5d Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Mon, 22 Jul 2019 10:43:11 -0700 Subject: [PATCH 30/70] NCBC-2037: Setting CertAuthenticator in config doesn't set EnableCertificateAuthentication Motivation ---------- When using cert authentication and setting the ClientCOnfiguration.CertificateFactory func, UseSsl and EnableCertificateAuthentication are not set to true. Modifications ------------- Set UseSsl and EnableCertification to true in ClientConfiguration if the CertificateFactory func is set. Result ------ If the ClientConfiguratiob.CertifciateFactory is set, then UseSsl and EnableCertificationAuthentication will also be true. Change-Id: Ife0a3d7b0a620988c0d09a757f875c90b83d9513 Reviewed-on: http://review.couchbase.org/112298 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- .../Authentication/AuthenticatorTests.cs | 22 ++++++++++++++++++- .../Client/ClientConfiguration.cs | 11 +++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Src/Couchbase.UnitTests/Authentication/AuthenticatorTests.cs b/Src/Couchbase.UnitTests/Authentication/AuthenticatorTests.cs index b0a4756be..9dd3e17f8 100644 --- a/Src/Couchbase.UnitTests/Authentication/AuthenticatorTests.cs +++ b/Src/Couchbase.UnitTests/Authentication/AuthenticatorTests.cs @@ -1,7 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography.X509Certificates; using Couchbase.Authentication; +using Couchbase.Authentication.X509; using Couchbase.Configuration.Client; using Couchbase.Core; using Moq; @@ -148,6 +150,24 @@ public void Authenticate_Username_Password_Creates_PasswordAuthenticator() Assert.AreEqual(username, authenticator.Username); Assert.AreEqual(password, authenticator.Password); } + + [Test] + public void Setting_CertificateFactory_Enables_Certificate_Authentication() + { + var config = new ClientConfiguration + { + CertificateFactory = CertificateFactory.GetCertificatesFromStore(new CertificateStoreOptions + { + StoreLocation = StoreLocation.LocalMachine, + StoreName = StoreName.TrustedPeople, + X509FindType = X509FindType.FindByThumbprint, + FindValue = "" + }) + }; + + Assert.IsTrue(config.EnableCertificateAuthentication); + Assert.IsTrue(config.UseSsl); + } } } diff --git a/Src/Couchbase/Configuration/Client/ClientConfiguration.cs b/Src/Couchbase/Configuration/Client/ClientConfiguration.cs index d51817120..3d48d0cc4 100644 --- a/Src/Couchbase/Configuration/Client/ClientConfiguration.cs +++ b/Src/Couchbase/Configuration/Client/ClientConfiguration.cs @@ -52,6 +52,7 @@ public class ClientConfiguration private uint _operationLifespan; private bool _operationLifespanChanged; private bool _enableCertificateAuthentication; + private Func _certificateFactoryFunc; [Obsolete] private double _heartbeatConfigInterval; @@ -1420,7 +1421,15 @@ public bool EnableCertificateAuthentication /// /// Factory for retrieving X509 certificates from a store or off of the file system. /// - public Func CertificateFactory { get; set; } + public Func CertificateFactory + { + get => _certificateFactoryFunc; + set + { + _certificateFactoryFunc = value; + EnableCertificateAuthentication = _certificateFactoryFunc != null; + } + } #if NET452 /// From ef891335b522f62d61b032b686d14d96c0931670 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 30 Jul 2019 14:42:04 +0100 Subject: [PATCH 31/70] NCBC-2036: Calculate span duration using executing clock frequency MOTIVATION ---------- Calculating the duration of a span is based on a start and end time stamp using StopWatch.GetTimestamp which is expressed as a number of ticks (long). However, different hardware and operating systems expose different clock frequencies (number of ticks) which impacts the value returned and therefore the duration. A span duration should be expressed as microseconds, regardless of system hardware / operating system. MODIFICATIONS ------------- - add ConverToMicros method to TimeSpanExtensions that can calculate microseconds using the executing clock frequency - update Span.Duration to use ConvertToMicros function RESULT ------ Span durations are calculated consistently cross-platform where clock frequencies can differ. Change-Id: Ia43bbbf342353d99dd79be6d9ff813090c28b1e8 Reviewed-on: http://review.couchbase.org/112659 Tested-by: Build Bot Reviewed-by: Charles Dixon --- Src/Couchbase/Tracing/Span.cs | 3 ++- Src/Couchbase/Utils/TimeSpanExtensions.cs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase/Tracing/Span.cs b/Src/Couchbase/Tracing/Span.cs index ac17a240c..6e271049b 100644 --- a/Src/Couchbase/Tracing/Span.cs +++ b/Src/Couchbase/Tracing/Span.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Threading; +using Couchbase.Utils; using OpenTracing; using OpenTracing.Tag; @@ -95,7 +96,7 @@ public long Duration { if (_endTimestamp.HasValue) { - return (_endTimestamp.Value - _startTimestamp) / 10; + return TimeSpanExtensions.ConvertTicksToMicros(_endTimestamp.Value - _startTimestamp); } return 0; } diff --git a/Src/Couchbase/Utils/TimeSpanExtensions.cs b/Src/Couchbase/Utils/TimeSpanExtensions.cs index db16d8204..f4d8e64d3 100644 --- a/Src/Couchbase/Utils/TimeSpanExtensions.cs +++ b/Src/Couchbase/Utils/TimeSpanExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Text.RegularExpressions; using Couchbase.Logging; @@ -116,6 +117,22 @@ internal static bool TryConvertToMicros(object obj, out long duration) return true; } + + /// + /// The number of ticks per microsecond is based on executing system clock resolution frequency. + /// This is the conversion factor needed based on current frequency to get a consistent cross-platform value. + /// + public static readonly long TicksPerMicroSecondFactor = Stopwatch.Frequency / (1000L * 1000L); + + /// + /// Converts ticks to microseconds. + /// + /// The ticks to convert to microseconds. + /// The number of ticks expressed as a . + public static long ConvertTicksToMicros(long ticks) + { + return ticks / TicksPerMicroSecondFactor; + } } } From 0b7d32205d5ff45eef42bf46318e67489324eee0 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Fri, 2 Aug 2019 13:19:33 -0700 Subject: [PATCH 32/70] NCBC-2047: .NET SDK bottleneck in EnsureIPEndPointsAreLoaded Motivation ---------- VBucketServerMap.IPEndPoint lazy loads the IPEndPoint list, however, it locks before the check, so if already initialized this will lead to lock contention. Modifications ------------- - Check for nullity before entering lock to reduce contention. Result ------ The lock will only be taken if the IPEndPoint list must be initialized. Change-Id: Ic40a88c2f26c0865dfa07c5e3a611fb0a6f0afa3 Reviewed-on: http://review.couchbase.org/112872 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- .../Server/Serialization/VBucketServerMap.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs b/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs index 63397d5ee..e547960e7 100644 --- a/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs +++ b/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs @@ -74,14 +74,17 @@ public override int GetHashCode() // ReSharper disable once InconsistentNaming internal void EnsureIPEndPointsAreLoaded() { - lock (_syncObj) + if (_ipEndPoints == null || !_ipEndPoints.Any()) { - if (_ipEndPoints == null || !_ipEndPoints.Any()) + lock (_syncObj) { - _ipEndPoints = new List(); - foreach (var server in ServerList) + if (_ipEndPoints == null || !_ipEndPoints.Any()) { - _ipEndPoints.Add(IPEndPointExtensions.GetEndPoint(server)); + _ipEndPoints = new List(); + foreach (var server in ServerList) + { + _ipEndPoints.Add(IPEndPointExtensions.GetEndPoint(server)); + } } } } From f60e24a2c9adcec3d36eabeefe03a33803595150 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Mon, 9 Sep 2019 09:53:02 -0700 Subject: [PATCH 33/70] NCBC-2097: Ignore k8 cluster map parse test because URI's are unresolvable locally Motivation ---------- Ignore test because the k8 DNS lookup cannot be resolved locally. Modification ------------ - Add ignore attribute to BucketConfigTestsK8.Test_Parse Result ------ The test will no longer fail. Change-Id: I3e104f4c8c1f2785c0bae3dda14697300ebe8101 Reviewed-on: http://review.couchbase.org/114465 Tested-by: Build Bot Reviewed-by: Mike Goldsmith --- Src/Couchbase.UnitTests/Configuration/BucketConfigTests_K8.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Src/Couchbase.UnitTests/Configuration/BucketConfigTests_K8.cs b/Src/Couchbase.UnitTests/Configuration/BucketConfigTests_K8.cs index f534e3534..65011e88e 100644 --- a/Src/Couchbase.UnitTests/Configuration/BucketConfigTests_K8.cs +++ b/Src/Couchbase.UnitTests/Configuration/BucketConfigTests_K8.cs @@ -10,6 +10,7 @@ namespace Couchbase.UnitTests.Configuration public class BucketConfigTestsK8 { [Test] + [Ignore("The hostnames in the URI in the config file are not resolvable to IP.")] public void Test_Parse() { var json = ResourceHelper.ReadResource(@"Data\k8config2.json"); From 6935b30daa7daeaddc1b83c11f366225b3320ef1 Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Mon, 19 Aug 2019 11:44:39 -0700 Subject: [PATCH 34/70] NCBC-2057: Add additional log output for x509 cert authentication Motivation ---------- Cert auth is lacking core logging details which make it difficult to triage issues; this commit adds additional logging output to make it easier to solve problems associated with cert auth. Modifications ------------- - Add additional logging output to ConnectionPoolBase and SslConnection that shows the #of certs being sent to the server and the post authenticate state of the connection. Result ------ This will help isolate issues when triaging cert auth problems. Change-Id: I04101f2026aa804383ff8155ce967e4c3be2c15c Reviewed-on: http://review.couchbase.org/113516 Reviewed-by: Mike Goldsmith Tested-by: Build Bot --- Src/Couchbase/IO/ConnectionPoolBase.cs | 4 ++-- Src/Couchbase/IO/SslConnection.cs | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Src/Couchbase/IO/ConnectionPoolBase.cs b/Src/Couchbase/IO/ConnectionPoolBase.cs index 2f8512d6e..b3d94badd 100644 --- a/Src/Couchbase/IO/ConnectionPoolBase.cs +++ b/Src/Couchbase/IO/ConnectionPoolBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -190,7 +190,7 @@ protected void EnableEnhancedAuthentication(IConnection connection) if (!selectBucketResult.Success) { - Log.Error($"Failed to perform SelectBucket operation for '{Configuration.BucketName}'"); + Log.Error($"Failed to perform SelectBucket operation for '{Configuration.BucketName}'. Reason {selectBucketResult.Status}."); throw new AuthenticationException(ExceptionUtil.FailedUserAuthenticationMsg.WithParams(Configuration.BucketName)); } } diff --git a/Src/Couchbase/IO/SslConnection.cs b/Src/Couchbase/IO/SslConnection.cs index 436f5338a..b0076fc93 100644 --- a/Src/Couchbase/IO/SslConnection.cs +++ b/Src/Couchbase/IO/SslConnection.cs @@ -79,6 +79,16 @@ public override void Authenticate() throw new NullConfigException("If BucketConfiguration.EnableCertificateAuthentication is true, CertificateFactory cannot be null."); } var certs = Configuration.ClientConfiguration.CertificateFactory(); + Log.Debug("Sending {0} certificates to the server.", certs?.Count ?? 0); + + if (certs != null) + { + foreach (var cert in certs) + { + Log.Debug("Cert sent {0} - Thumbprint {1}", cert.FriendlyName, cert.Thumbprint); + } + } + _sslStream.AuthenticateAsClientAsync(targetHost, certs, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12, @@ -91,6 +101,10 @@ public override void Authenticate() } IsSecure = _sslStream.IsAuthenticated && _sslStream.IsSigned && _sslStream.IsEncrypted; + Log.Debug("IsAuthenticated {0} on {1}", _sslStream.IsAuthenticated, targetHost); + Log.Debug("IsSigned {0} on {1}", _sslStream.IsSigned, targetHost); + Log.Debug("IsEncrypted {0} on {1}", _sslStream.IsEncrypted, targetHost); + if (!IsSecure) { throw new AuthenticationException(ExceptionUtil.SslAuthenticationFailed); From 3f4213abd92dc4d59c00764ff0d00f06ed4ff26a Mon Sep 17 00:00:00 2001 From: jeffrymorris Date: Mon, 19 Aug 2019 14:49:31 -0700 Subject: [PATCH 35/70] NCBC-2058: Fast fail cert authentication when cert lookup returns empty list Motivation ---------- If no certificate can be found by the selected X509FindType and FindValue, throw an Authentication exception. Modifications ------------- - Throw a AuthenticationException when no cert is found in the Cert Store before authenticating the certificate. Result ------ If no cert can be found, the client will fail fast with an AuthenticationException. Change-Id: Ifd2e6f90f52a29127f63835b5cc6940d2da954e9 Reviewed-on: http://review.couchbase.org/113530 Reviewed-by: Mike Goldsmith Tested-by: Build Bot --- Src/Couchbase/IO/SslConnection.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Src/Couchbase/IO/SslConnection.cs b/Src/Couchbase/IO/SslConnection.cs index b0076fc93..9c1d5b449 100644 --- a/Src/Couchbase/IO/SslConnection.cs +++ b/Src/Couchbase/IO/SslConnection.cs @@ -79,9 +79,11 @@ public override void Authenticate() throw new NullConfigException("If BucketConfiguration.EnableCertificateAuthentication is true, CertificateFactory cannot be null."); } var certs = Configuration.ClientConfiguration.CertificateFactory(); - Log.Debug("Sending {0} certificates to the server.", certs?.Count ?? 0); - - if (certs != null) + if (certs == null || certs.Count == 0) + { + throw new AuthenticationException("No certificates matching the X509FindType and specified FindValue were found in the Certificate Store."); + } + if (Log.IsDebugEnabled) { foreach (var cert in certs) { @@ -89,6 +91,7 @@ public override void Authenticate() } } + Log.Debug("Sending {0} certificates to the server.", certs?.Count ?? 0); _sslStream.AuthenticateAsClientAsync(targetHost, certs, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12, From 043a88548fbe5580c1406fd97f197bb79a84ccd1 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Wed, 9 Oct 2019 15:26:44 +0100 Subject: [PATCH 36/70] NCBC-2146: SslConnection should not release itself Motivation ---------- When the SslConnection.Send is called, it releases itself from the ConnectionPool. The connection should not release itself and rely on the connection pool to deal with both acquiring and releases connection. Modifications ------------- - don't call ConnectionPool.Release during Send Result ------ The SslConnection does not release itself during Send. Change-Id: I6b69c2076cb4ed12704e2af223f07d2ddcb36128 Reviewed-on: http://review.couchbase.org/116146 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- Src/Couchbase/IO/SslConnection.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Src/Couchbase/IO/SslConnection.cs b/Src/Couchbase/IO/SslConnection.cs index 9c1d5b449..1a5502857 100644 --- a/Src/Couchbase/IO/SslConnection.cs +++ b/Src/Couchbase/IO/SslConnection.cs @@ -168,10 +168,6 @@ public override async Task SendAsync(byte[] request, Func Date: Mon, 14 Oct 2019 12:42:10 +0100 Subject: [PATCH 37/70] NCBC-2152: Only suppress SslConnection finalizer in DEBUG mode MOTIVATION ---------- The SslConnection has a finalizer method registered when running in DEBUG mode. However, garbace collection is suppressed for all modes. This can lead to objects not being cleaned-up correctly, which for the SslConneciton, could mean leaking TCP sockets. MODIFICATIONS ------------- - only call GC.SuppressFinalize when in DEBUG mode RESULT ------ SslConnection's finalizer is only suppressed when running in DEBUG mode. Change-Id: I5b6aa5cac9295290b28e7f79a2adcf795993fd1d Reviewed-on: http://review.couchbase.org/116369 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- Src/Couchbase/IO/SslConnection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Src/Couchbase/IO/SslConnection.cs b/Src/Couchbase/IO/SslConnection.cs index 1a5502857..adad1e8eb 100644 --- a/Src/Couchbase/IO/SslConnection.cs +++ b/Src/Couchbase/IO/SslConnection.cs @@ -277,7 +277,9 @@ private async Task SendAsync(byte[] buffer, CancellationToken cancellati public override void Dispose() { Dispose(true); +#if DEBUG GC.SuppressFinalize(this); +#endif } public void Dispose(bool disposing) From c05220d4bc9ca644a6a99b2cd3ff0276f6cbfffb Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Thu, 17 Oct 2019 11:17:43 +0100 Subject: [PATCH 38/70] NCBC-2159: Ensure awaiter is configured in SslConnection.Send MOTIVATION ---------- When moving from sync to async units of work, any task by default will block the current thread. This is not ideal in some scenarios and a task awaiter should be configured to avoid this. MODIFICATIONS ------------- - configure the awaiter used in SslConnection.Send when calling SendAsync RESULT ------ The awaiter used in the synchronous path is correctly configured when calling SendAsync. Change-Id: I40919dfd101711d3a8b6a6428850d9ad4259f7ee Reviewed-on: http://review.couchbase.org/116582 Tested-by: Build Bot Reviewed-by: Charles Dixon --- Src/Couchbase/IO/SslConnection.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Src/Couchbase/IO/SslConnection.cs b/Src/Couchbase/IO/SslConnection.cs index adad1e8eb..63855cb45 100644 --- a/Src/Couchbase/IO/SslConnection.cs +++ b/Src/Couchbase/IO/SslConnection.cs @@ -200,11 +200,10 @@ public override byte[] Send(byte[] buffer) try { - var task = SendAsync(buffer, cancellationTokenSource.Token); - - task.Wait(cancellationTokenSource.Token); - - return task.Result; + return SendAsync(buffer, cancellationTokenSource.Token) + .ContinueOnAnyContext() + .GetAwaiter() + .GetResult(); } catch (AggregateException ex) { From 52942c0e18a002815a13eb470a9d425224325c44 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 22 Oct 2019 13:23:07 +0100 Subject: [PATCH 39/70] NCBC-2163: Use sync operations in SslConnection.Send MOTIVATION ---------- It is generally considered bad practice hoping from sync to async code path, and it's very easy to block threads. Instead it is best to try and stay within sync operation paths. The SDK does not need to hop to async when executing sync opeations over SSL. MODIFICATIONS ------------- - refactor SslConneciton.Send to not use an async path and use SslStream directly - remove SendAsync method used by Send RESULT ------ The SDK no longer hops to an async path when executing synchronous operations using SSL. Change-Id: I53c9a8f5a4926971d589a9be1fecadce2f602c18 Reviewed-on: http://review.couchbase.org/116781 Reviewed-by: Jeffry Morris Tested-by: Build Bot --- Src/Couchbase/IO/SslConnection.cs | 93 ++++++++++--------------------- 1 file changed, 28 insertions(+), 65 deletions(-) diff --git a/Src/Couchbase/IO/SslConnection.cs b/Src/Couchbase/IO/SslConnection.cs index 63855cb45..87a812974 100644 --- a/Src/Couchbase/IO/SslConnection.cs +++ b/Src/Couchbase/IO/SslConnection.cs @@ -191,83 +191,46 @@ await callback(new SocketAsyncState } } - public override byte[] Send(byte[] buffer) + public override byte[] Send(byte[] request) { - using (new SynchronizationContextExclusion()) + try { - // Token will cancel automatically after timeout - var cancellationTokenSource = new CancellationTokenSource(Configuration.SendTimeout); - - try - { - return SendAsync(buffer, cancellationTokenSource.Token) - .ContinueOnAnyContext() - .GetAwaiter() - .GetResult(); - } - catch (AggregateException ex) + var opaque = Converter.ToUInt32(request, HeaderIndexFor.Opaque); + var state = new SocketAsyncState { - //TODO refactor logic - IsDead = true; - - if (ex.InnerException is TaskCanceledException) - { - // Timeout expired and cancellation token source was triggered - var opaque = Converter.ToUInt32(buffer, HeaderIndexFor.Opaque); - throw CreateTimeoutException(opaque); - } - else - { - // Rethrow the aggregate exception - throw; - } - } - } - } - - private async Task SendAsync(byte[] buffer, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var opaque = Converter.ToUInt32(buffer, HeaderIndexFor.Opaque); - var state = new SocketAsyncState - { - Data = MemoryStreamFactory.GetMemoryStream(), - Opaque = opaque, - ConnectionId = ContextId, - LocalEndpoint = LocalEndPoint.ToString(), - Timeout = Configuration.SendTimeout - }; - - await _sslStream.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ContinueOnAnyContext(); + Data = MemoryStreamFactory.GetMemoryStream(), + Opaque = opaque, + ConnectionId = ContextId, + LocalEndpoint = LocalEndPoint.ToString(), + Timeout = Configuration.SendTimeout + }; - state.SetIOBuffer(_buffer); + _sslStream.Write(request, 0, request.Length); - while (state.BytesReceived < state.BodyLength + OperationHeader.Length) - { - cancellationToken.ThrowIfCancellationRequested(); + state.SetIOBuffer(_buffer); + state.BytesReceived = _sslStream.Read(state.Buffer, state.BufferOffset, state.BufferLength); - var bytesReceived = await _sslStream - .ReadAsync(state.Buffer, state.BufferOffset, state.BufferLength, cancellationToken) - .ContinueOnAnyContext(); - state.BytesReceived += bytesReceived; + //write the received buffer to the state obj + state.Data.Write(state.Buffer, state.BufferOffset, state.BytesReceived); - if (state.BytesReceived == 0) + state.BodyLength = Converter.ToInt32(state.Buffer, state.BufferOffset + HeaderIndexFor.BodyLength); + while (state.BytesReceived < state.BodyLength + OperationHeader.Length) { - // No more bytes were received, go ahead and exit the loop - break; - } + var bufferLength = state.BufferLength - state.BytesSent < state.BufferLength + ? state.BufferLength - state.BytesSent + : state.BufferLength; - if (state.BodyLength == 0) - { - // Reading header, so get the BodyLength - state.BodyLength = Converter.ToInt32(state.Buffer, state.BufferOffset + HeaderIndexFor.Body); + state.BytesReceived += _sslStream.Read(state.Buffer, state.BufferOffset, bufferLength); + state.Data.Write(state.Buffer, state.BufferOffset, state.BytesReceived - (int)state.Data.Length); } - state.Data.Write(state.Buffer, state.BufferOffset, bytesReceived); + return state.Data.ToArray(); + } + catch (Exception) + { + IsDead = true; + throw; } - - return state.Data.ToArray(); } /// From 9814ee1f017aeb6cfc645096942f2451d6c90660 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Mon, 14 Oct 2019 13:20:25 +0100 Subject: [PATCH 40/70] NCBC-2148: Initialize connection pool connections in parallel MOTIVATION ---------- When a connection pool calls Initialize, it creates and adds connections to the pool. However, this is done serially and when the pool is large can take a long time. Creating these connections in parallel would improve connection pool initialize times considerably when using larger connection pools. MODIFICATIONS ------------- - use Parallel.For to run connection bootstrap in concurrently in ConnectionPool (ssl) and SharedConnectionPool (mux) RESULT ------ When a connection pool is initialized, it bootstraps the connections in parallel to improve startup time. Change-Id: I76941bd3bf589c7ece276be4ba21cd621959aca3 Reviewed-on: http://review.couchbase.org/116373 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- Src/Couchbase/IO/ConnectionPool.cs | 42 +++++++++++++----------- Src/Couchbase/IO/SharedConnectionPool.cs | 16 +++++---- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/Src/Couchbase/IO/ConnectionPool.cs b/Src/Couchbase/IO/ConnectionPool.cs index 24fe4eda4..82c15d6db 100644 --- a/Src/Couchbase/IO/ConnectionPool.cs +++ b/Src/Couchbase/IO/ConnectionPool.cs @@ -7,6 +7,7 @@ using Couchbase.Logging; using Couchbase.Configuration.Client; using Couchbase.IO.Converters; +using System.Threading.Tasks; namespace Couchbase.IO { @@ -83,30 +84,31 @@ public override void Initialize() EnableEnhancedAuthentication(connection); } - // create and configure connections to minsize - while (_refs.Count < Configuration.MinSize) - { - try + Task.WhenAll( + Enumerable.Range(1, Configuration.MaxSize - _refs.Count).Select(i => Task.Factory.StartNew(() => { - var connection = Factory(this, Converter, BufferAllocator); + try + { + var connection = Factory(this, Converter, BufferAllocator); - Authenticate(connection); - EnableEnhancedAuthentication(connection); + Authenticate(connection); + EnableEnhancedAuthentication(connection); - Log.Info("Initializing connection on [{0} | {1}] - {2} - Disposed: {3}", - EndPoint, connection.Identity, Identity, _disposed); + Log.Info("Initializing connection on [{0} | {1}] - {2} - Disposed: {3}", + EndPoint, connection.Identity, Identity, _disposed); - _store.Enqueue(connection); - _refs.TryAdd(connection.Identity, connection); - Interlocked.Increment(ref _count); - } - catch (Exception e) - { - Log.Info("Node {0} failed to initialize, reason: {1}", EndPoint, e); - InitializationFailed = true; - return; - } - } + _store.Enqueue(connection); + _refs.TryAdd(connection.Identity, connection); + Interlocked.Increment(ref _count); + } + catch (Exception e) + { + Log.Info("Node {0} failed to initialize, reason: {1}", EndPoint, e); + InitializationFailed = true; + } + return Task.FromResult(true); + }, TaskCreationOptions.LongRunning)) + ).ConfigureAwait(false).GetAwaiter().GetResult(); } } diff --git a/Src/Couchbase/IO/SharedConnectionPool.cs b/Src/Couchbase/IO/SharedConnectionPool.cs index ef5133bc8..a4705d6a3 100644 --- a/Src/Couchbase/IO/SharedConnectionPool.cs +++ b/Src/Couchbase/IO/SharedConnectionPool.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Threading.Tasks; using Couchbase.Configuration.Client; using Couchbase.IO.Converters; using Couchbase.Logging; @@ -143,12 +144,15 @@ public override void Initialize() { lock (_lockObj) { - var connectionsToCreate = Configuration.MaxSize - _connections.Count; - for (var i = 0; i < connectionsToCreate; i++) - { - var connection = CreateAndAuthConnection(); - _connections.Add(connection); - } + Task.WhenAll( + Enumerable.Range(1, Configuration.MaxSize - _connections.Count).Select(i => Task.Factory.StartNew(() => + { + var connection = CreateAndAuthConnection(); + _connections.Add(connection); + return Task.FromResult(true); + }, TaskCreationOptions.LongRunning)) + ).ConfigureAwait(false).GetAwaiter().GetResult(); + //auth the connection used to select the SASL type to use for auth foreach (var connection in _connections.Where(x=>!x.IsAuthenticated)) { From 3ea0c125fa3b0480cab78f79906fa50bc321c50b Mon Sep 17 00:00:00 2001 From: RXX8TDF Date: Wed, 13 Nov 2019 13:50:41 -0500 Subject: [PATCH 41/70] NCBC-2179: LookupIn does not correctly parse config returned by NMVB Motivation ---------- The sub document API multilookup operation does not correctly handle a Not My VBucket (NMVB) response from the server. The updated configuration from the server is not parsed correctly and the client's vbucket map is not updated. MultiLookup.GetValue attempts to interpret the beginning portion of the configuration JSON string as a body length (SDK version 2.7.13, MultiLookup.cs line 97) This results in spikes in memory usage as an extremely large and unneed byte array is allocated, and an ArgumentException when attempting to read past the end of the response buffer. The execption causes the response status to be changed from VBucketBelongsToAnotherServer to ClientFailure, which causes the configuration update logic to never be invoked. Modifications ------------- - Bypass converting the body bytes into OperationSpecs in MultiLookup.GetValue and instead read the config later in the operation lifetime. - Added a unit test and test against a NMVB packet. - Copied ResponsePackets.cs from Couchbase.Tests and used in Unit Test to verify. Result ------ The config will properly be read later and queued for config processing; a ClientError will not be encountered. Change-Id: I324e67bfb7a400c8488f884e3122875498972371 Reviewed-on: http://review.couchbase.org/117823 Tested-by: Build Bot Reviewed-by: Brett Lawson Reviewed-by: Matt Ingenthron --- .../Core/IO/Operations/MultiLookupTests.cs | 22 +- .../Data/ResponsePackets.cs | 544 ++++++++++++++++++ .../IO/Operations/SubDocument/MultiLookup.cs | 5 +- 3 files changed, 568 insertions(+), 3 deletions(-) create mode 100644 Src/Couchbase.UnitTests/Data/ResponsePackets.cs diff --git a/Src/Couchbase.UnitTests/Core/IO/Operations/MultiLookupTests.cs b/Src/Couchbase.UnitTests/Core/IO/Operations/MultiLookupTests.cs index 97203c5eb..498b1970a 100644 --- a/Src/Couchbase.UnitTests/Core/IO/Operations/MultiLookupTests.cs +++ b/Src/Couchbase.UnitTests/Core/IO/Operations/MultiLookupTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -7,8 +7,10 @@ using Couchbase.Core.IO.SubDocument; using Couchbase.Core.Serialization; using Couchbase.Core.Transcoders; +using Couchbase.IO; using Couchbase.IO.Converters; using Couchbase.IO.Operations.SubDocument; +using Couchbase.UnitTests.Data; using Moq; using NUnit.Framework; @@ -54,9 +56,25 @@ public void When_Success_Return_Fragment() var result = op.GetResultWithValue(); } - public class MyDoc + [Test] + public void When_NMVB_Do_Not_Read_OperationSpecs() { + var response = ResponsePackets.GET_WITH_NMV; + var mockedInvoker = new Mock(); + var builder = new LookupInBuilder(mockedInvoker.Object, () => new DefaultSerializer(), "mykey"); + + var op = new MultiLookup(builder.Key, builder, new VBucket(null, 1, 1, null, 0, null, "default"), + new DefaultTranscoder(new DefaultConverter()), 10); + + op.Read(response, null); + var result = op.GetResultWithValue(); + Assert.AreEqual(ResponseStatus.VBucketBelongsToAnotherServer, result.Status); + Assert.Null(result.Value); + } + + public class MyDoc + { } } } diff --git a/Src/Couchbase.UnitTests/Data/ResponsePackets.cs b/Src/Couchbase.UnitTests/Data/ResponsePackets.cs new file mode 100644 index 000000000..3b68b4e38 --- /dev/null +++ b/Src/Couchbase.UnitTests/Data/ResponsePackets.cs @@ -0,0 +1,544 @@ +namespace Couchbase.UnitTests.Data +{ + internal static class ResponsePackets + { + public static byte[] GET_OPAQUE_5_SUCCESS = + { + 129, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 1, 19, 196, 129, 206, 182, 55, 254, 229, 2, 0, 0, 9, 48 + }; + + public static byte[] GET_KEY_NOT_FOUND = + { + 129, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 78, 111, 116, 32, 102, 111, 117, + 110, 100 + }; + + public static byte[] GET_WITH_NMV = + { + 129, 4, 0, 0, 0, 0, 0, 7, 0, 0, 47, 57, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 123, 34, 114, 101, 118, 34, 58, + 49, 48, 52, 53, 44, 34, 110, 97, 109, 101, 34, 58, 34, 100, 101, 102, 97, 117, 108, 116, 34, 44, 34, 117, + 114, 105, 34, 58, 34, 47, 112, 111, 111, 108, 115, 47, 100, 101, 102, 97, 117, 108, 116, 47, 98, 117, 99, + 107, 101, 116, 115, 47, 100, 101, 102, 97, 117, 108, 116, 63, 98, 117, 99, 107, 101, 116, 95, 117, 117, 105, + 100, 61, 101, 57, 56, 97, 101, 51, 52, 48, 97, 57, 99, 57, 48, 55, 98, 51, 52, 50, 50, 97, 53, 51, 98, 48, + 98, 54, 49, 48, 49, 99, 99, 102, 34, 44, 34, 115, 116, 114, 101, 97, 109, 105, 110, 103, 85, 114, 105, 34, + 58, 34, 47, 112, 111, 111, 108, 115, 47, 100, 101, 102, 97, 117, 108, 116, 47, 98, 117, 99, 107, 101, 116, + 115, 83, 116, 114, 101, 97, 109, 105, 110, 103, 47, 100, 101, 102, 97, 117, 108, 116, 63, 98, 117, 99, 107, + 101, 116, 95, 117, 117, 105, 100, 61, 101, 57, 56, 97, 101, 51, 52, 48, 97, 57, 99, 57, 48, 55, 98, 51, 52, + 50, 50, 97, 53, 51, 98, 48, 98, 54, 49, 48, 49, 99, 99, 102, 34, 44, 34, 110, 111, 100, 101, 115, 34, 58, 91, + 123, 34, 99, 111, 117, 99, 104, 65, 112, 105, 66, 97, 115, 101, 34, 58, 34, 104, 116, 116, 112, 58, 47, 47, + 49, 57, 50, 46, 49, 54, 56, 46, 50, 55, 46, 49, 48, 49, 58, 56, 48, 57, 50, 47, 100, 101, 102, 97, 117, 108, + 116, 37, 50, 66, 101, 57, 56, 97, 101, 51, 52, 48, 97, 57, 99, 57, 48, 55, 98, 51, 52, 50, 50, 97, 53, 51, + 98, 48, 98, 54, 49, 48, 49, 99, 99, 102, 34, 44, 34, 104, 111, 115, 116, 110, 97, 109, 101, 34, 58, 34, 49, + 57, 50, 46, 49, 54, 56, 46, 50, 55, 46, 49, 48, 49, 58, 56, 48, 57, 49, 34, 44, 34, 112, 111, 114, 116, 115, + 34, 58, 123, 34, 112, 114, 111, 120, 121, 34, 58, 49, 49, 50, 49, 49, 44, 34, 100, 105, 114, 101, 99, 116, + 34, 58, 49, 49, 50, 49, 48, 125, 125, 44, 123, 34, 99, 111, 117, 99, 104, 65, 112, 105, 66, 97, 115, 101, 34, + 58, 34, 104, 116, 116, 112, 58, 47, 47, 49, 57, 50, 46, 49, 54, 56, 46, 50, 55, 46, 49, 48, 50, 58, 56, 48, + 57, 50, 47, 100, 101, 102, 97, 117, 108, 116, 37, 50, 66, 101, 57, 56, 97, 101, 51, 52, 48, 97, 57, 99, 57, + 48, 55, 98, 51, 52, 50, 50, 97, 53, 51, 98, 48, 98, 54, 49, 48, 49, 99, 99, 102, 34, 44, 34, 104, 111, 115, + 116, 110, 97, 109, 101, 34, 58, 34, 49, 57, 50, 46, 49, 54, 56, 46, 50, 55, 46, 49, 48, 50, 58, 56, 48, 57, + 49, 34, 44, 34, 112, 111, 114, 116, 115, 34, 58, 123, 34, 112, 114, 111, 120, 121, 34, 58, 49, 49, 50, 49, + 49, 44, 34, 100, 105, 114, 101, 99, 116, 34, 58, 49, 49, 50, 49, 48, 125, 125, 44, 123, 34, 99, 111, 117, 99, + 104, 65, 112, 105, 66, 97, 115, 101, 34, 58, 34, 104, 116, 116, 112, 58, 47, 47, 49, 57, 50, 46, 49, 54, 56, + 46, 50, 55, 46, 49, 48, 51, 58, 56, 48, 57, 50, 47, 100, 101, 102, 97, 117, 108, 116, 37, 50, 66, 101, 57, + 56, 97, 101, 51, 52, 48, 97, 57, 99, 57, 48, 55, 98, 51, 52, 50, 50, 97, 53, 51, 98, 48, 98, 54, 49, 48, 49, + 99, 99, 102, 34, 44, 34, 104, 111, 115, 116, 110, 97, 109, 101, 34, 58, 34, 49, 57, 50, 46, 49, 54, 56, 46, + 50, 55, 46, 49, 48, 51, 58, 56, 48, 57, 49, 34, 44, 34, 112, 111, 114, 116, 115, 34, 58, 123, 34, 112, 114, + 111, 120, 121, 34, 58, 49, 49, 50, 49, 49, 44, 34, 100, 105, 114, 101, 99, 116, 34, 58, 49, 49, 50, 49, 48, + 125, 125, 44, 123, 34, 99, 111, 117, 99, 104, 65, 112, 105, 66, 97, 115, 101, 34, 58, 34, 104, 116, 116, 112, + 58, 47, 47, 49, 57, 50, 46, 49, 54, 56, 46, 50, 55, 46, 49, 48, 52, 58, 56, 48, 57, 50, 47, 100, 101, 102, + 97, 117, 108, 116, 37, 50, 66, 101, 57, 56, 97, 101, 51, 52, 48, 97, 57, 99, 57, 48, 55, 98, 51, 52, 50, 50, + 97, 53, 51, 98, 48, 98, 54, 49, 48, 49, 99, 99, 102, 34, 44, 34, 104, 111, 115, 116, 110, 97, 109, 101, 34, + 58, 34, 49, 57, 50, 46, 49, 54, 56, 46, 50, 55, 46, 49, 48, 52, 58, 56, 48, 57, 49, 34, 44, 34, 112, 111, + 114, 116, 115, 34, 58, 123, 34, 112, 114, 111, 120, 121, 34, 58, 49, 49, 50, 49, 49, 44, 34, 100, 105, 114, + 101, 99, 116, 34, 58, 49, 49, 50, 49, 48, 125, 125, 93, 44, 34, 110, 111, 100, 101, 115, 69, 120, 116, 34, + 58, 91, 123, 34, 115, 101, 114, 118, 105, 99, 101, 115, 34, 58, 123, 34, 109, 103, 109, 116, 34, 58, 56, 48, + 57, 49, 44, 34, 99, 97, 112, 105, 34, 58, 56, 48, 57, 50, 44, 34, 109, 111, 120, 105, 34, 58, 49, 49, 50, 49, + 49, 44, 34, 107, 118, 34, 58, 49, 49, 50, 49, 48, 44, 34, 107, 118, 83, 83, 76, 34, 58, 49, 49, 50, 48, 55, + 44, 34, 99, 97, 112, 105, 83, 83, 76, 34, 58, 49, 56, 48, 57, 50, 44, 34, 109, 103, 109, 116, 83, 83, 76, 34, + 58, 49, 56, 48, 57, 49, 125, 44, 34, 104, 111, 115, 116, 110, 97, 109, 101, 34, 58, 34, 49, 57, 50, 46, 49, + 54, 56, 46, 50, 55, 46, 49, 48, 49, 34, 125, 44, 123, 34, 115, 101, 114, 118, 105, 99, 101, 115, 34, 58, 123, + 34, 109, 103, 109, 116, 34, 58, 56, 48, 57, 49, 44, 34, 99, 97, 112, 105, 34, 58, 56, 48, 57, 50, 44, 34, + 109, 111, 120, 105, 34, 58, 49, 49, 50, 49, 49, 44, 34, 107, 118, 34, 58, 49, 49, 50, 49, 48, 44, 34, 107, + 118, 83, 83, 76, 34, 58, 49, 49, 50, 48, 55, 44, 34, 99, 97, 112, 105, 83, 83, 76, 34, 58, 49, 56, 48, 57, + 50, 44, 34, 109, 103, 109, 116, 83, 83, 76, 34, 58, 49, 56, 48, 57, 49, 125, 44, 34, 104, 111, 115, 116, 110, + 97, 109, 101, 34, 58, 34, 49, 57, 50, 46, 49, 54, 56, 46, 50, 55, 46, 49, 48, 50, 34, 125, 44, 123, 34, 115, + 101, 114, 118, 105, 99, 101, 115, 34, 58, 123, 34, 109, 103, 109, 116, 34, 58, 56, 48, 57, 49, 44, 34, 99, + 97, 112, 105, 34, 58, 56, 48, 57, 50, 44, 34, 109, 111, 120, 105, 34, 58, 49, 49, 50, 49, 49, 44, 34, 107, + 118, 34, 58, 49, 49, 50, 49, 48, 44, 34, 107, 118, 83, 83, 76, 34, 58, 49, 49, 50, 48, 55, 44, 34, 99, 97, + 112, 105, 83, 83, 76, 34, 58, 49, 56, 48, 57, 50, 44, 34, 109, 103, 109, 116, 83, 83, 76, 34, 58, 49, 56, 48, + 57, 49, 125, 44, 34, 104, 111, 115, 116, 110, 97, 109, 101, 34, 58, 34, 49, 57, 50, 46, 49, 54, 56, 46, 50, + 55, 46, 49, 48, 51, 34, 125, 44, 123, 34, 115, 101, 114, 118, 105, 99, 101, 115, 34, 58, 123, 34, 109, 103, + 109, 116, 34, 58, 56, 48, 57, 49, 44, 34, 99, 97, 112, 105, 34, 58, 56, 48, 57, 50, 44, 34, 109, 111, 120, + 105, 34, 58, 49, 49, 50, 49, 49, 44, 34, 107, 118, 34, 58, 49, 49, 50, 49, 48, 44, 34, 107, 118, 83, 83, 76, + 34, 58, 49, 49, 50, 48, 55, 44, 34, 99, 97, 112, 105, 83, 83, 76, 34, 58, 49, 56, 48, 57, 50, 44, 34, 109, + 103, 109, 116, 83, 83, 76, 34, 58, 49, 56, 48, 57, 49, 125, 44, 34, 104, 111, 115, 116, 110, 97, 109, 101, + 34, 58, 34, 49, 57, 50, 46, 49, 54, 56, 46, 50, 55, 46, 49, 48, 52, 34, 125, 93, 44, 34, 110, 111, 100, 101, + 76, 111, 99, 97, 116, 111, 114, 34, 58, 34, 118, 98, 117, 99, 107, 101, 116, 34, 44, 34, 117, 117, 105, 100, + 34, 58, 34, 101, 57, 56, 97, 101, 51, 52, 48, 97, 57, 99, 57, 48, 55, 98, 51, 52, 50, 50, 97, 53, 51, 98, 48, + 98, 54, 49, 48, 49, 99, 99, 102, 34, 44, 34, 100, 100, 111, 99, 115, 34, 58, 123, 34, 117, 114, 105, 34, 58, + 34, 47, 112, 111, 111, 108, 115, 47, 100, 101, 102, 97, 117, 108, 116, 47, 98, 117, 99, 107, 101, 116, 115, + 47, 100, 101, 102, 97, 117, 108, 116, 47, 100, 100, 111, 99, 115, 34, 125, 44, 34, 118, 66, 117, 99, 107, + 101, 116, 83, 101, 114, 118, 101, 114, 77, 97, 112, 34, 58, 123, 34, 104, 97, 115, 104, 65, 108, 103, 111, + 114, 105, 116, 104, 109, 34, 58, 34, 67, 82, 67, 34, 44, 34, 110, 117, 109, 82, 101, 112, 108, 105, 99, 97, + 115, 34, 58, 51, 44, 34, 115, 101, 114, 118, 101, 114, 76, 105, 115, 116, 34, 58, 91, 34, 49, 57, 50, 46, 49, + 54, 56, 46, 50, 55, 46, 49, 48, 49, 58, 49, 49, 50, 49, 48, 34, 44, 34, 49, 57, 50, 46, 49, 54, 56, 46, 50, + 55, 46, 49, 48, 50, 58, 49, 49, 50, 49, 48, 34, 44, 34, 49, 57, 50, 46, 49, 54, 56, 46, 50, 55, 46, 49, 48, + 51, 58, 49, 49, 50, 49, 48, 34, 44, 34, 49, 57, 50, 46, 49, 54, 56, 46, 50, 55, 46, 49, 48, 52, 58, 49, 49, + 50, 49, 48, 34, 93, 44, 34, 118, 66, 117, 99, 107, 101, 116, 77, 97, 112, 34, 58, 91, 91, 48, 44, 49, 44, 50, + 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, + 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, + 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, + 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, + 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, + 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, + 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, + 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, + 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, + 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, + 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, + 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, + 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, + 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, + 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, + 48, 44, 49, 44, 50, 44, 51, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, + 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, + 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, + 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, + 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, + 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, + 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, + 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, + 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, 44, 51, 44, 50, 93, 44, 91, 48, 44, 49, + 44, 51, 44, 50, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, + 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, + 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, + 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, + 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, + 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, + 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, + 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, + 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, + 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, + 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 49, 44, 51, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, + 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, + 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, + 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, + 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, + 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, + 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, + 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, + 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, + 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, + 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, + 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, + 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, + 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, + 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, + 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, + 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, + 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, + 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, + 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, + 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, + 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, 93, 44, 91, 48, 44, 50, 44, 51, 44, 49, + 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, + 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, + 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, + 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, + 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, + 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, + 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, + 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, + 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, + 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, + 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, + 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, + 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, + 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, + 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, + 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, + 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 49, 44, 50, 93, 44, 91, 48, 44, 51, 44, 50, 44, + 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, + 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, + 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, + 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, + 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, + 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, + 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, + 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, + 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, + 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, + 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, + 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, + 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, + 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, + 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, + 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, + 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, + 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, + 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, + 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, + 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, + 44, 50, 44, 49, 93, 44, 91, 48, 44, 51, 44, 50, 44, 49, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, + 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, + 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, + 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, + 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, + 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, + 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, + 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, + 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, + 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, + 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, + 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, + 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, + 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, + 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, + 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, + 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, + 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, + 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, + 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, + 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, + 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, + 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, + 50, 44, 51, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, + 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, + 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, + 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, + 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, + 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, + 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, + 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, + 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, + 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, + 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, + 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, + 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, + 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, + 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, + 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, + 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, + 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, + 49, 44, 48, 44, 51, 44, 50, 93, 44, 91, 49, 44, 50, 44, 48, 44, 51, 93, 44, 91, 49, 44, 50, 44, 48, 44, 51, + 93, 44, 91, 49, 44, 50, 44, 48, 44, 51, 93, 44, 91, 49, 44, 50, 44, 48, 44, 51, 93, 44, 91, 49, 44, 50, 44, + 48, 44, 51, 93, 44, 91, 49, 44, 50, 44, 48, 44, 51, 93, 44, 91, 49, 44, 50, 44, 48, 44, 51, 93, 44, 91, 49, + 44, 50, 44, 48, 44, 51, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, + 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, + 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, + 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, + 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, + 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, + 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, + 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, + 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, + 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, + 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, + 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, + 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, + 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, + 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, + 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 50, 44, 51, 44, 48, 93, 44, 91, 49, 44, 51, + 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, + 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, + 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, + 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, + 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, + 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, + 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, + 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, + 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, + 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, + 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, + 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, + 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, + 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, + 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, 44, 91, 49, 44, 51, 44, 48, 44, 50, 93, + 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, + 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, + 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, + 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, + 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, + 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, + 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, + 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, + 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, + 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, + 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, + 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, + 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, + 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, + 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, + 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, + 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, + 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, + 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, + 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, + 44, 91, 49, 44, 51, 44, 50, 44, 48, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, + 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, + 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, + 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, + 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, + 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, + 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, + 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, + 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, + 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, + 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, + 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, + 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, + 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, + 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, + 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, + 50, 44, 48, 44, 49, 44, 51, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, + 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, + 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, + 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, + 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, + 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, + 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, + 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, + 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, + 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, + 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, + 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, + 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, + 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, + 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, + 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, 48, 44, 51, 44, 49, 93, 44, 91, 50, 44, + 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, + 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, + 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, + 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, + 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, + 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, + 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, + 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, + 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, + 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, + 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, + 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, + 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, + 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, + 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, + 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, 48, 44, 51, 93, 44, 91, 50, 44, 49, 44, + 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, + 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, + 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, + 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, + 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, + 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, + 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, + 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, + 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, + 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, + 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, + 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, + 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, + 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, + 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, + 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 49, 44, 51, 44, 48, 93, 44, 91, 50, 44, 51, 44, 48, 44, + 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, + 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, + 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, + 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, + 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, + 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, + 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, + 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, + 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, + 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, + 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, + 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, + 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, + 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, + 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, + 44, 51, 44, 48, 44, 49, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, + 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, + 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, + 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, + 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, + 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, + 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, + 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, + 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, + 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, + 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, + 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, + 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, + 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, + 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, + 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, 44, 49, 44, 48, 93, 44, 91, 50, 44, 51, + 44, 49, 44, 48, 93, 44, 91, 51, 44, 48, 44, 49, 44, 50, 93, 44, 91, 51, 44, 48, 44, 49, 44, 50, 93, 44, 91, + 51, 44, 48, 44, 49, 44, 50, 93, 44, 91, 51, 44, 48, 44, 49, 44, 50, 93, 44, 91, 51, 44, 48, 44, 49, 44, 50, + 93, 44, 91, 51, 44, 48, 44, 49, 44, 50, 93, 44, 91, 51, 44, 48, 44, 49, 44, 50, 93, 44, 91, 51, 44, 48, 44, + 49, 44, 50, 93, 44, 91, 51, 44, 48, 44, 49, 44, 50, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, + 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, + 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, + 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, + 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, + 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, + 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, + 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, + 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, + 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, + 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, + 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, + 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, + 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, + 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, + 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, + 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, + 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, + 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, + 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, + 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, + 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, + 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, + 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, + 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, + 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 48, 44, 50, 44, 49, 93, 44, 91, 51, 44, 49, 44, 48, 44, + 50, 93, 44, 91, 51, 44, 49, 44, 48, 44, 50, 93, 44, 91, 51, 44, 49, 44, 48, 44, 50, 93, 44, 91, 51, 44, 49, + 44, 48, 44, 50, 93, 44, 91, 51, 44, 49, 44, 48, 44, 50, 93, 44, 91, 51, 44, 49, 44, 48, 44, 50, 93, 44, 91, + 51, 44, 49, 44, 48, 44, 50, 93, 44, 91, 51, 44, 49, 44, 48, 44, 50, 93, 44, 91, 51, 44, 49, 44, 48, 44, 50, + 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, + 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, + 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, + 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, + 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, + 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, + 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, + 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, + 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, + 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, + 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, + 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, + 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, + 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, + 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, + 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, + 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, + 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, + 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, + 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, + 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, + 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, + 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, + 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, + 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, + 49, 44, 50, 44, 48, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, + 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, + 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, + 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, + 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, + 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, + 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, + 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, + 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, + 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, + 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, + 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, + 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, + 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, + 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, + 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, + 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, + 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, + 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, 44, 49, 93, 44, 91, 51, 44, 50, 44, 48, + 44, 49, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, + 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, + 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, + 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, + 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, + 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, + 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, + 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, + 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, + 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, + 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, + 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, + 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, + 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, + 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, + 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, + 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, + 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, + 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, 44, 91, 51, 44, 50, 44, 49, 44, 48, 93, + 93, 125, 44, 34, 98, 117, 99, 107, 101, 116, 67, 97, 112, 97, 98, 105, 108, 105, 116, 105, 101, 115, 86, 101, + 114, 34, 58, 34, 34, 44, 34, 98, 117, 99, 107, 101, 116, 67, 97, 112, 97, 98, 105, 108, 105, 116, 105, 101, + 115, 34, 58, 91, 34, 99, 98, 104, 101, 108, 108, 111, 34, 44, 34, 116, 111, 117, 99, 104, 34, 44, 34, 99, + 111, 117, 99, 104, 97, 112, 105, 34, 44, 34, 99, 99, 99, 112, 34, 44, 34, 120, 100, 99, 114, 67, 104, 101, + 99, 107, 112, 111, 105, 110, 116, 105, 110, 103, 34, 44, 34, 110, 111, 100, 101, 115, 69, 120, 116, 34, 93, + 125 + }; + + public static byte[] ADD_SUCCESS = + { + 129, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 19, 207, 137, 17, 74, 90, 5, 189, + }; + + public static byte[] Add_FAILED_KEY_EXISTS = + { + 129, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 19, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 68, 97, 116, 97, 32, 101, 120, + 105, 115, 116, 115, 32, 102, 111, 114, 32, 107, 101, 121 + }; + + public static byte[] GET_WITH_LOCK_SUCCESS = + { + 129, 148, 0, 0, 4, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 1, 19, 208, 102, 30, 252, 132, 113, 64 + , 4, 0, 0, 18, 123, 39, 110, 97, 109, 101, 39, 58, 39, 118, 97, 108, 117, 101, 39, 125 + }; + + public static byte[] REMOVE_KEYNOTFOUND = + { + 129, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 78, 111, 116, 32, 102, 111, 117, + 110, 100 + }; + + public static byte[] REMOVE_SUCCESS = + { + 129, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 19, 212, 177, 187, 223, 17, 80, 176 + }; + + public static byte[] REPLACE_KEYNOTFOUND = + { + 129, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 78, 111, 116, 32, 102, 111, 117, + 110, 100 + }; + + public static byte[] REPLACE_SUCCESS = + { + 129, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 125, 22, 190, 196, 0, 0 + }; + + public static byte[] UPSERT_NOKEY_SUCCESS = + { + 129, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 131, 64, 79, 242, 0, 0 + }; + + public static byte[] UPSERT_KEYEXISTS_SUCCESS = + { + 129, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 131, 64, 79, 242, 0, 0 + }; + + public static byte[] INSERT_KEYEXISTS = + { + 129, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 19, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 68, 97, 116, 97, 32, 101, 120, + 105, 115, 116, 115, 32, 102, 111, 114, 32, 107, 101, 121 + }; + + public static byte[] INSERT_SUCCESS = + { + 129, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 129, 210, 91, 59, 0, 0 + }; + + public static byte[] OBSERVE_KEYEXISTS = + { + 129, 146, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 87, 104, 101, 110, 95, 75, 101, 121, 95, 70, 111, 117, 110, 100, 95, 69, 120, 105, 115, 116, 65, 115, 121, 110, 99, 95, 82, 101, 116, 117, 114, 110, 115, 95, 84, 114, 117, 101, 128, 0, 0, 0, 0, 0, 0, 0, 0, }; + + public static byte[] OBSERVE_KEY_DNE = + { + 129, 146, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 87, 104, 101, 110, 95, 75, 101, 121, 95, 70, 111, 117, 110, 100, 95, 69, 120, 105, 115, 116, 65, 115, 121, 110, 99, 95, 82, 101, 116, 117, 114, 110, 115, 95, 84, 114, 117, 101, 1, 19, 219, 104, 74, 210, 236, 59, 229, + }; + } +} diff --git a/Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs b/Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs index a70883f40..1e157dbea 100644 --- a/Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs +++ b/Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs @@ -86,6 +86,10 @@ public override OperationCode OperationCode public override T GetValue() { + //Fix for NCBC-2179 Do not attempt to parse body if response is not my vbucket + if (Header.Status == ResponseStatus.VBucketBelongsToAnotherServer) + return default(T); + var response = Data.ToArray(); var statusOffset = Header.BodyOffset; var valueLengthOffset = statusOffset + 2; @@ -102,7 +106,6 @@ public override T GetValue() command.Status = (ResponseStatus)Converter.ToUInt16(response, statusOffset); command.ValueIsJson = payLoad.IsJson(0, bodyLength - 1); command.Bytes = payLoad; - statusOffset = valueOffset + bodyLength; valueLengthOffset = statusOffset + 2; valueOffset = statusOffset + 6; From 0817338a17641f95d07d7142d82c0d9e38770fa4 Mon Sep 17 00:00:00 2001 From: Brett Lawson Date: Fri, 22 Nov 2019 15:42:06 -0800 Subject: [PATCH 42/70] NCBC-2192: MultiMutation does not encode remove value correctly MOTIVATION ---------- We incorrectly encodes a null value as the literal string `null` in the case of a remove command. This causes the server to reject the operation. MODIFICATIONS ------------- Special case the remove command to avoid serializing null values to JSON and instead simply leave the value empty. RESULT ------ Executing multi-mutation removes now succeeds. Change-Id: I70f2cdb643527134337bfeb55ae4249cd16bba0d Reviewed-on: http://review.couchbase.org/118394 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- .../CouchbaseBucket_SubDocument_Tests.cs | 22 +++++++++++++++++++ .../Operations/SubDocument/MultiMutation.cs | 8 +++++++ 2 files changed, 30 insertions(+) diff --git a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs index 1c290f23d..5dae78d29 100644 --- a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs +++ b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs @@ -1806,6 +1806,28 @@ public async Task MutateInAsync_with_multimutate_can_set_property_value_to_null( Assert.AreEqual(useMutation, result.Token.IsSet); } + [TestCase(true)] + [TestCase(false)] + public async Task MutateInAsync_with_multimutate_remove_works(bool useMutation) + { + var bucket = GetBucket(useMutation); + const string key = "MutateInAsync_with_multimutate_remove_works"; + + await bucket.UpsertAsync(key, new { + attr1 = "foo", + attr2 = "bar", + attr3 = "baz" + }); + + var result = await bucket.MutateIn(key) + .Upsert("attr2") + .Upsert("attr3") + .ExecuteAsync(); + + Assert.AreEqual(ResponseStatus.Success, result.Status); + Assert.AreEqual(useMutation, result.Token.IsSet); + } + [OneTimeTearDown] public void OneTimeTearDown() { diff --git a/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs b/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs index 5e8c08657..d2d3b8644 100644 --- a/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs +++ b/Src/Couchbase/IO/Operations/SubDocument/MultiMutation.cs @@ -106,6 +106,14 @@ public override void WriteExtras(byte[] buffer, int offset) private byte[] GetBytes(OperationSpec spec) { + if (spec.OpCode == OperationCode.SubDelete) + { + // The sub-document delete command does not accept a value, and thus + // should not serialize anything for the byte value. This prevents + // the case where a delete encodes a literal `null` into the value. + return new byte[0]; + } + var bytes = Transcoder.Serializer.Serialize(spec.Value); if (spec.RemoveBrackets) { From cac6a8151385eae4e7a6bf200e7002cd20f4af7a Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Tue, 3 Dec 2019 12:30:12 -0800 Subject: [PATCH 43/70] NCBC-2198: Create additional Sub-Document test to verify NCBC-2038 Motivation ---------- Additional validation test based on feedback from inital issue raiser. Modifications ------------ - Adds test to CouchbaseBucket_SubDocument_Tests.cs Result ------ Test succeeds. Change-Id: Ic6dfebb149bd46defde6ad7197bce1e84a7130b2 Reviewed-on: http://review.couchbase.org/118827 Tested-by: Build Bot Reviewed-by: Matt Ingenthron --- .../CouchbaseBucket_SubDocument_Tests.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs index 5dae78d29..2c4c74766 100644 --- a/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs +++ b/Src/Couchbase.IntegrationTests/CouchbaseBucket_SubDocument_Tests.cs @@ -1828,6 +1828,41 @@ public async Task MutateInAsync_with_multimutate_remove_works(bool useMutation) Assert.AreEqual(useMutation, result.Token.IsSet); } + [TestCase(true)] + [TestCase(false)] + public async Task MutateInAsync_With_Nulls_Works(bool useMutation) + { + var bucket = GetBucket(useMutation); + string docId = "MutateInAsync_With_Nulls_Works_UseMutation-" + useMutation; + + dynamic content = new + { + attr1 = "foo", + attr2 = "bar", + attr3 = "baz" + }; + + await bucket.InsertAsync(docId, content); + + IDocumentFragment fragment = await bucket.MutateIn(docId).Remove("attr1").ExecuteAsync(); // ok + Assert.IsTrue(fragment.Success); + + fragment = await bucket.MutateIn(docId) // success after NCBC-2038 + .Remove("attr2") + .Remove("attr3") + .ExecuteAsync(); + Assert.IsTrue(fragment.Success); + + fragment = await bucket.MutateIn(docId).Insert("attr4", "qux").ExecuteAsync(); // ok + Assert.IsTrue(fragment.Success); + + fragment = await bucket.MutateIn(docId) // ok + .Insert("attr5", "fooz") + .Insert("attr6", "barz") + .ExecuteAsync(); + Assert.IsTrue(fragment.Success); + } + [OneTimeTearDown] public void OneTimeTearDown() { From 6857f5598cb361ad3df4d62dba28dece23e4b753 Mon Sep 17 00:00:00 2001 From: Matthew Feerick Date: Thu, 9 Jan 2020 12:22:42 +0000 Subject: [PATCH 44/70] NCBC-2235 GetResultWithValue now calls GetMultiValues instead of GetValue. Motivation ---------- Fixes a bug where MultiLookup would fail if more than one lookup spec was added. Modifications ------------- Ensure that a List is returned of a single value. Result ------ MultipleLookupIn operations specs can be chained. Change-Id: I3704f85a927a70b89485634a41d2fd296b5b5f89 Reviewed-on: http://review.couchbase.org/120248 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs b/Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs index 1e157dbea..67558daf6 100644 --- a/Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs +++ b/Src/Couchbase/IO/Operations/SubDocument/MultiLookup.cs @@ -84,11 +84,11 @@ public override OperationCode OperationCode get { return OperationCode.MultiLookup; } } - public override T GetValue() + public IList GetMultiValues() { //Fix for NCBC-2179 Do not attempt to parse body if response is not my vbucket if (Header.Status == ResponseStatus.VBucketBelongsToAnotherServer) - return default(T); + return null; var response = Data.ToArray(); var statusOffset = Header.BodyOffset; @@ -112,7 +112,8 @@ public override T GetValue() if (valueOffset > response.Length) break; } - return (T)_lookupCommands; + + return _lookupCommands; } public override IOperationResult GetResultWithValue() @@ -126,7 +127,7 @@ public override IOperationResult GetResultWithValue() result.Cas = Header.Cas; result.Exception = Exception; result.Token = MutationToken ?? DefaultMutationToken; - result.Value = (IList) GetValue(); + result.Value = GetMultiValues(); //clean up and set to null if (!result.IsNmv()) From 9a37aa3fa1060bb4eff587665eae57ff6fb6ea61 Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Tue, 3 Dec 2019 15:16:13 -0800 Subject: [PATCH 45/70] NCBC-2200: Thrown SendTimeoutExpiredException does not close the connection Motivation ---------- Ensure that when an exception occurs in the connection that socket/connection is handled gracefully. Modifications ------------- - Make the connection go through the discconect logic. Change-Id: If03218c7462ac8bd1db51d41936621052a29c8af Reviewed-on: http://review.couchbase.org/121695 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- Src/Couchbase/IO/MultiplexingConnection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase/IO/MultiplexingConnection.cs b/Src/Couchbase/IO/MultiplexingConnection.cs index 8fb62a233..f7a6fbd3c 100644 --- a/Src/Couchbase/IO/MultiplexingConnection.cs +++ b/Src/Couchbase/IO/MultiplexingConnection.cs @@ -137,7 +137,8 @@ public override byte[] Send(byte[] request) if (!didComplete) { - throw CreateTimeoutException(opaque); + var ex = CreateTimeoutException(opaque); + HandleDisconnect(ex); } return response; From 4c60b53a54b8b0805647fecb85f276f2e4ad3d98 Mon Sep 17 00:00:00 2001 From: Jeffry Morris Date: Wed, 5 Feb 2020 02:29:05 +0000 Subject: [PATCH 46/70] Revert "NCBC-2148: Initialize connection pool connections in parallel" This reverts commit 9814ee1f017aeb6cfc645096942f2451d6c90660. Reason for revert: See https://issues.couchbase.com/browse/NCBC-2342 Change-Id: Ic8614f8a853603ae69e2c83191055fa9706abfb2 Reviewed-on: http://review.couchbase.org/121691 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- Src/Couchbase/IO/ConnectionPool.cs | 42 +++++++++++------------- Src/Couchbase/IO/SharedConnectionPool.cs | 16 ++++----- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/Src/Couchbase/IO/ConnectionPool.cs b/Src/Couchbase/IO/ConnectionPool.cs index 82c15d6db..24fe4eda4 100644 --- a/Src/Couchbase/IO/ConnectionPool.cs +++ b/Src/Couchbase/IO/ConnectionPool.cs @@ -7,7 +7,6 @@ using Couchbase.Logging; using Couchbase.Configuration.Client; using Couchbase.IO.Converters; -using System.Threading.Tasks; namespace Couchbase.IO { @@ -84,31 +83,30 @@ public override void Initialize() EnableEnhancedAuthentication(connection); } - Task.WhenAll( - Enumerable.Range(1, Configuration.MaxSize - _refs.Count).Select(i => Task.Factory.StartNew(() => + // create and configure connections to minsize + while (_refs.Count < Configuration.MinSize) + { + try { - try - { - var connection = Factory(this, Converter, BufferAllocator); + var connection = Factory(this, Converter, BufferAllocator); - Authenticate(connection); - EnableEnhancedAuthentication(connection); + Authenticate(connection); + EnableEnhancedAuthentication(connection); - Log.Info("Initializing connection on [{0} | {1}] - {2} - Disposed: {3}", - EndPoint, connection.Identity, Identity, _disposed); + Log.Info("Initializing connection on [{0} | {1}] - {2} - Disposed: {3}", + EndPoint, connection.Identity, Identity, _disposed); - _store.Enqueue(connection); - _refs.TryAdd(connection.Identity, connection); - Interlocked.Increment(ref _count); - } - catch (Exception e) - { - Log.Info("Node {0} failed to initialize, reason: {1}", EndPoint, e); - InitializationFailed = true; - } - return Task.FromResult(true); - }, TaskCreationOptions.LongRunning)) - ).ConfigureAwait(false).GetAwaiter().GetResult(); + _store.Enqueue(connection); + _refs.TryAdd(connection.Identity, connection); + Interlocked.Increment(ref _count); + } + catch (Exception e) + { + Log.Info("Node {0} failed to initialize, reason: {1}", EndPoint, e); + InitializationFailed = true; + return; + } + } } } diff --git a/Src/Couchbase/IO/SharedConnectionPool.cs b/Src/Couchbase/IO/SharedConnectionPool.cs index a4705d6a3..ef5133bc8 100644 --- a/Src/Couchbase/IO/SharedConnectionPool.cs +++ b/Src/Couchbase/IO/SharedConnectionPool.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Net; -using System.Threading.Tasks; using Couchbase.Configuration.Client; using Couchbase.IO.Converters; using Couchbase.Logging; @@ -144,15 +143,12 @@ public override void Initialize() { lock (_lockObj) { - Task.WhenAll( - Enumerable.Range(1, Configuration.MaxSize - _connections.Count).Select(i => Task.Factory.StartNew(() => - { - var connection = CreateAndAuthConnection(); - _connections.Add(connection); - return Task.FromResult(true); - }, TaskCreationOptions.LongRunning)) - ).ConfigureAwait(false).GetAwaiter().GetResult(); - + var connectionsToCreate = Configuration.MaxSize - _connections.Count; + for (var i = 0; i < connectionsToCreate; i++) + { + var connection = CreateAndAuthConnection(); + _connections.Add(connection); + } //auth the connection used to select the SASL type to use for auth foreach (var connection in _connections.Where(x=>!x.IsAuthenticated)) { From 62756837d7a029ce0b762e3edef29c4600b7b6a4 Mon Sep 17 00:00:00 2001 From: Nathan Ekstrom Date: Wed, 5 Feb 2020 15:03:07 -0700 Subject: [PATCH 47/70] NCBC-2361: Don't export fuzziness or prefix_length when values less than 1 Motivation ------------- Currently Couchbase 6.5 does not accept a fuzziness value of 0 while Couchbase 6.0 did. As it stands the 2.7 SDK cannot make a search for an exact value in FTS. This stops the code from outputting "fuzziness":0 or "prefix_length":0 allowing the search to succeed. Modifications ------------- - Changed unit tests to match new oputput - Don't export fuzziness or prefix_length when less than 1. Change-Id: I9f72bb83f5d1667851214af23da7fadb72314cc4 Reviewed-on: http://review.couchbase.org/122217 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- Src/Couchbase.UnitTests/Search/BooleanQueryTests.cs | 6 ------ Src/Couchbase.UnitTests/Search/ConjunctionQueryTests.cs | 2 -- Src/Couchbase.UnitTests/Search/DisjunctionQueryTests.cs | 2 -- Src/Couchbase/Search/Queries/Simple/TermQuery.cs | 6 ++++-- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Src/Couchbase.UnitTests/Search/BooleanQueryTests.cs b/Src/Couchbase.UnitTests/Search/BooleanQueryTests.cs index 944d2325e..f65af14ee 100644 --- a/Src/Couchbase.UnitTests/Search/BooleanQueryTests.cs +++ b/Src/Couchbase.UnitTests/Search/BooleanQueryTests.cs @@ -61,8 +61,6 @@ public void Export_Returns_Valid_Json_For_Must() new { term = "hotel", - prefix_length = 0, - fuzziness = 0, field = "type" } } @@ -90,8 +88,6 @@ public void Export_Returns_Valid_Json_For_MustNot() new { term = "hotel", - prefix_length = 0, - fuzziness = 0, field = "type" } } @@ -119,8 +115,6 @@ public void Export_Returns_Valid_Json_For_Should() new { term = "hotel", - prefix_length = 0, - fuzziness = 0, field = "type" } } diff --git a/Src/Couchbase.UnitTests/Search/ConjunctionQueryTests.cs b/Src/Couchbase.UnitTests/Search/ConjunctionQueryTests.cs index c3481a29a..8cb5ec475 100644 --- a/Src/Couchbase.UnitTests/Search/ConjunctionQueryTests.cs +++ b/Src/Couchbase.UnitTests/Search/ConjunctionQueryTests.cs @@ -42,8 +42,6 @@ public void Export_ReturnsValidJson() new { term = "hotel", - prefix_length = 0, - fuzziness = 0, field = "type" } } diff --git a/Src/Couchbase.UnitTests/Search/DisjunctionQueryTests.cs b/Src/Couchbase.UnitTests/Search/DisjunctionQueryTests.cs index a9a6e5cc8..ceae6adec 100644 --- a/Src/Couchbase.UnitTests/Search/DisjunctionQueryTests.cs +++ b/Src/Couchbase.UnitTests/Search/DisjunctionQueryTests.cs @@ -43,8 +43,6 @@ public void Export_ReturnsValidJson() new { term = "hotel", - prefix_length = 0, - fuzziness = 0, field = "type" } } diff --git a/Src/Couchbase/Search/Queries/Simple/TermQuery.cs b/Src/Couchbase/Search/Queries/Simple/TermQuery.cs index 988995f0a..0c67e229f 100755 --- a/Src/Couchbase/Search/Queries/Simple/TermQuery.cs +++ b/Src/Couchbase/Search/Queries/Simple/TermQuery.cs @@ -59,8 +59,10 @@ public override JObject Export() { var json = base.Export(); json.Add(new JProperty("term", _term)); - json.Add(new JProperty("prefix_length", _prefixLength)); - json.Add(new JProperty("fuzziness", _fuzziness)); + if(_prefixLength > 0) + json.Add(new JProperty("prefix_length", _prefixLength)); + if(_fuzziness > 0) + json.Add(new JProperty("fuzziness", _fuzziness)); if (!string.IsNullOrEmpty(_field)) { From 11aa2fd9a17f3ca7f4a387063c1d2aabe7c08c94 Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Mon, 20 Apr 2020 14:57:29 -0700 Subject: [PATCH 48/70] NCBC-246: Ping not recording failures for non-KV services Motivation ---------- When using PingReport, if a noop call fails, the exception is handled by the XxxClient and not percolated to the try/catch handler of the PingReportProvider. This causes the PingReport to incorrectly flag a failed call as successful. Modifications ------------- - Check the IXxxResult.Exception field for each XxxClient call; if it is non-null, the call failed and can be marked as so by the PingReport. - Augmented unit tests to compare results sent by server Result ------ Failed ping reports for non-KV services are correctly marked as failed. Change-Id: Ia943ad9a901e0cf832b237b20c4051b742947f7b Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/126240 Reviewed-by: Tested-by: Build Bot --- .../DiagnosticsReportProviderTests.cs | 83 ++++++++++++++++++- .../Monitoring/DiagnosticsReportProvider.cs | 38 +++++++-- 2 files changed, 114 insertions(+), 7 deletions(-) diff --git a/Src/Couchbase.UnitTests/Core/Monitoring/DiagnosticsReportProviderTests.cs b/Src/Couchbase.UnitTests/Core/Monitoring/DiagnosticsReportProviderTests.cs index 9529e4e78..147942964 100644 --- a/Src/Couchbase.UnitTests/Core/Monitoring/DiagnosticsReportProviderTests.cs +++ b/Src/Couchbase.UnitTests/Core/Monitoring/DiagnosticsReportProviderTests.cs @@ -1,10 +1,14 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Net; +using System.Net.Sockets; using Couchbase.Configuration; using Couchbase.Core; using Couchbase.Core.Monitoring; using Couchbase.IO; +using Couchbase.IO.Operations; +using Couchbase.N1QL; using Couchbase.Utils; using Moq; using Newtonsoft.Json; @@ -66,7 +70,7 @@ public void Can_Create_DiagnosticsReport() } [Test] - public void Can_Create_Ping_Report() + public void Can_Create_Ping_Report_Failed() { var reportId = Guid.NewGuid().ToString(); @@ -99,12 +103,87 @@ public void Can_Create_Ping_Report() s.AnalyticsClient.LastActivity == analyticsLastActivity ); + Mock.Get(connection).Setup(x=>x.Send(It.IsAny())).Throws(); + + Mock.Get(server).Setup(x => x.QueryClient.Query(It.IsAny())) + .Returns(new QueryResult + { + Exception = new Exception() + }); + var configInfo = Mock.Of(c => c.Servers == new List { server } ); var report = DiagnosticsReportProvider.CreatePingReport(reportId, configInfo, new[] { ServiceType.KeyValue, ServiceType.Query}); Assert.IsNotNull(report); + + foreach (var endpointDiagnostic in report.Services.Values.SelectMany(service => service)) + { + Assert.AreEqual(ServiceState.Error, endpointDiagnostic.State); + } + + Assert.AreEqual(reportId, report.Id); + Assert.AreEqual(1, report.Version); + Assert.AreEqual(ClientIdentifier.GetClientDescription(), report.Sdk); + } + + + [Test] + public void Can_Create_Ping_Report_Success() + { + var reportId = Guid.NewGuid().ToString(); + + var kvLastActivity = DateTime.UtcNow; + var viewLastActivity = kvLastActivity.AddSeconds(-5); + var queryLastActivity = kvLastActivity.AddSeconds(-10); + var searchLastActivity = kvLastActivity.AddSeconds(-15); + var analyticsLastActivity = kvLastActivity.AddSeconds(-20); + + var connection = Mock.Of(c => + c.LastActivity == kvLastActivity && + c.IsConnected == true && + c.IsAuthenticated == true + ); + + var connectionPool = Mock.Of(p => + p.Connections == new List { connection } + ); + + var server = Mock.Of(s => + s.IsDataNode == true && + s.ConnectionPool == connectionPool && + s.IsViewNode == true && + s.ViewClient.LastActivity == viewLastActivity && + s.IsQueryNode == true && + s.QueryClient.LastActivity == queryLastActivity && + s.IsSearchNode == true && + s.SearchClient.LastActivity == searchLastActivity && + s.IsAnalyticsNode == true && + s.AnalyticsClient.LastActivity == analyticsLastActivity + ); + + //just assume nothing is thrown, however, server could return back a status + Mock.Get(connection).Setup(x => x.Send(It.IsAny())).Returns(new byte[] {0x00}); + + Mock.Get(server).Setup(x => x.QueryClient.Query(It.IsAny())) + .Returns(new QueryResult + { + Success = true, + HttpStatusCode = HttpStatusCode.OK + }); + + var configInfo = Mock.Of(c => + c.Servers == new List { server } + ); + + var report = DiagnosticsReportProvider.CreatePingReport(reportId, configInfo, new[] { ServiceType.KeyValue, ServiceType.Query }); + Assert.IsNotNull(report); + + foreach (var endpointDiagnostic in report.Services.Values.SelectMany(service => service)) + { + Assert.AreEqual(ServiceState.Ok, endpointDiagnostic.State); + } Assert.AreEqual(reportId, report.Id); Assert.AreEqual(1, report.Version); Assert.AreEqual(ClientIdentifier.GetClientDescription(), report.Sdk); diff --git a/Src/Couchbase/Core/Monitoring/DiagnosticsReportProvider.cs b/Src/Couchbase/Core/Monitoring/DiagnosticsReportProvider.cs index 30d7230fe..7223e1454 100644 --- a/Src/Couchbase/Core/Monitoring/DiagnosticsReportProvider.cs +++ b/Src/Couchbase/Core/Monitoring/DiagnosticsReportProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -85,7 +85,14 @@ internal static Dictionary> GetEndpoin var endpoint = CreateEndpointHealth(config.BucketName, now, server.ViewClient, server.EndPoint); if (ping) { - RecordLatency(endpoint, () => server.ViewClient.Execute(viewQuery)); + RecordLatency(endpoint, () => + { + var result = server.ViewClient.Execute(viewQuery); + if (result.Exception != null) + { + throw result.Exception; + } + }); } return endpoint; @@ -102,7 +109,14 @@ internal static Dictionary> GetEndpoin var endpoint = CreateEndpointHealth(config.BucketName, now, server.QueryClient, server.EndPoint); if (ping) { - RecordLatency(endpoint, () => server.QueryClient.Query(n1qlQuery)); + RecordLatency(endpoint, () => + { + var result = server.QueryClient.Query(n1qlQuery); + if (result.Exception != null) + { + throw result.Exception; + } + }); } return endpoint; @@ -120,7 +134,14 @@ internal static Dictionary> GetEndpoin var endpoint = CreateEndpointHealth(config.BucketName, now, server.SearchClient, server.EndPoint); if (ping) { - RecordLatency(endpoint, () => server.SearchClient.Query(searchQuery)); + RecordLatency(endpoint, () => + { + var result = server.SearchClient.Query(searchQuery); + if (result.Exception != null) + { + throw result.Exception; + } + }); } return endpoint; @@ -137,7 +158,14 @@ internal static Dictionary> GetEndpoin var endpoint = CreateEndpointHealth(config.BucketName, now, server.AnalyticsClient, server.EndPoint); if (ping) { - RecordLatency(endpoint, () => server.AnalyticsClient.Query(analyticsRequest)); + RecordLatency(endpoint, () => + { + var result = server.AnalyticsClient.Query(analyticsRequest); + if (result.Exception != null) + { + throw result.Exception; + } + }); } return endpoint; From 22628a63c2323cd9affdba58b227371e373798a6 Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Fri, 1 May 2020 16:39:49 -0700 Subject: [PATCH 49/70] NCBC-1945:SDK client fails to apply new rev map with HTTP bootstrap Motivation ---------- While use HttpStreamingProvider, the client fails to pick up new configs coming from the server. Modifications ------------- - Remove line setting timeout as the property is only supported on certain .NET Framwork versions. Result ------ The http config handler will process new configs from the server. Change-Id: I985ef9f7271bb39c41617c3b6deab15f9f4ea9c4 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/127122 Tested-by: Build Bot Reviewed-by: --- .../Server/Providers/Streaming/ConfigThreadState.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Src/Couchbase/Configuration/Server/Providers/Streaming/ConfigThreadState.cs b/Src/Couchbase/Configuration/Server/Providers/Streaming/ConfigThreadState.cs index 6ef1f9467..efbd6f94c 100644 --- a/Src/Couchbase/Configuration/Server/Providers/Streaming/ConfigThreadState.cs +++ b/Src/Couchbase/Configuration/Server/Providers/Streaming/ConfigThreadState.cs @@ -104,8 +104,6 @@ public void ListenForConfigChanges() using (var stream = response.Content.ReadAsStreamAsync().Result) { - stream.ReadTimeout = Timeout.Infinite; - //this will cancel the infinite wait below _cancellationToken.Register(stream.Dispose); From fa73817dfa54db011b189b0b7896e38ba84907ab Mon Sep 17 00:00:00 2001 From: RiPont Date: Wed, 6 May 2020 07:43:28 -0700 Subject: [PATCH 50/70] NCBC-2423: Change Log.Debug to Log.Warn for TCP KeepAlive NotSupported Motivation ---------- Help troubleshoot possible issues affecting cloud platforms. Modifications ------------- - Log.Debug changed to Log.Warn Results ------- - SetKeepAlives now results in a Warning logged on platforms that don't support it. Change-Id: I96f4da1cb22e2df08d47953922657a882ad31986 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/129879 Reviewed-by: Jeffry Morris Tested-by: Build Bot --- Src/Couchbase/IO/Utils/SocketExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Src/Couchbase/IO/Utils/SocketExtensions.cs b/Src/Couchbase/IO/Utils/SocketExtensions.cs index 1c7c8bc10..8beeb1732 100644 --- a/Src/Couchbase/IO/Utils/SocketExtensions.cs +++ b/Src/Couchbase/IO/Utils/SocketExtensions.cs @@ -1,4 +1,4 @@ -// http://blogs.msdn.com/b/pfxteam/archive/2011/12/15/10248293.aspx +// http://blogs.msdn.com/b/pfxteam/archive/2011/12/15/10248293.aspx using System; using System.Net.Sockets; @@ -36,7 +36,7 @@ public static void SetKeepAlives(this Socket socket, bool on, uint time, uint in catch (PlatformNotSupportedException) { // Can't set on non-Windows platforms, ignore error - Log.Debug("Skipping Socket.IOControl for keep alives, not supported on this platform"); + Log.Warn("Skipping Socket.IOControl for keep alives, not supported on this platform"); } } @@ -175,4 +175,4 @@ public static SocketAwaitable SendAsync(this SocketAwaitable awaitable) * * ************************************************************/ -#endregion \ No newline at end of file +#endregion From 4db0ba50826075cfdc6584b410b7b52fefe0776b Mon Sep 17 00:00:00 2001 From: RiPont Date: Wed, 6 May 2020 17:06:53 -0700 Subject: [PATCH 51/70] NCBC-2423: Change Log.Debug to Log.Warn for TCP KeepAlive NotSupported Motivation ---------- Help troubleshoot possible issues affecting cloud platforms. Modifications ------------- - Log.Debug changed to Log.Warn Results ------- - SetKeepAlives now results in a Warning logged on platforms that don't support it. Change-Id: Ie75c28e348774551a2d7e8fab7e94d7a8d91a04c Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/127478 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- Src/Couchbase/IO/Utils/SocketExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/Couchbase/IO/Utils/SocketExtensions.cs b/Src/Couchbase/IO/Utils/SocketExtensions.cs index 8beeb1732..75cdb8815 100644 --- a/Src/Couchbase/IO/Utils/SocketExtensions.cs +++ b/Src/Couchbase/IO/Utils/SocketExtensions.cs @@ -35,7 +35,7 @@ public static void SetKeepAlives(this Socket socket, bool on, uint time, uint in } catch (PlatformNotSupportedException) { - // Can't set on non-Windows platforms, ignore error + // Can't set on non-Windows platforms, warn on error Log.Warn("Skipping Socket.IOControl for keep alives, not supported on this platform"); } } From 815dc8cf77d0b66ce1818ed6c9ae0e3be8b92f4e Mon Sep 17 00:00:00 2001 From: RiPont Date: Mon, 15 Jun 2020 13:48:42 -0700 Subject: [PATCH 52/70] NCBC-2554: Remove "authzid" from SaslPlain auth data. Motivation: Per CBSE-8479, authzid handling isn't being done in CB Server 6.5, so the client shouldn't be sending it. Modifications: - Remove leading username from AuthData, as authzid is optional - Per IETF RFC 4616, leave leading UTF8NULL character Results: When using TLS, authzid is no longer sent, only authcid. Change-Id: I6b2ead2cb49f6b8f566a97f119beaab48c1d1136 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/130590 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- .../Authentication/SASL/PlainTextMechanism.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Src/Couchbase/Authentication/SASL/PlainTextMechanism.cs b/Src/Couchbase/Authentication/SASL/PlainTextMechanism.cs index aa019effe..8cd402a52 100644 --- a/Src/Couchbase/Authentication/SASL/PlainTextMechanism.cs +++ b/Src/Couchbase/Authentication/SASL/PlainTextMechanism.cs @@ -96,12 +96,14 @@ public bool Authenticate(IConnection connection, string username, string passwor static string GetAuthData(string userName, string passWord) { - const string empty = "\0"; + // see https://tools.ietf.org/html/rfc4616#section-2 + const string utf8Null = "\0"; var sb = new StringBuilder(); + + // authzid is optional, and not included at this time. + sb.Append(utf8Null); sb.Append(userName); - sb.Append(empty); - sb.Append(userName); - sb.Append(empty); + sb.Append(utf8Null); sb.Append(passWord); return sb.ToString(); } From 28309259f37163f7861ce0ade3fc584408f206f1 Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Mon, 20 Jul 2020 18:56:02 -0700 Subject: [PATCH 53/70] NCBC-2606: If node initially fails to initialize flip the circuit breaker Motivation ---------- If a node fails to initialize, load the node into the nodes list but flip the circuit breaker (isDown) so that it will try to "heal" itself. The older behavior was to simply not load the failed node and wait for a new configuration, however, this can lead to state where the client keeps recieving NMVB and then thinks it has already caught up revision. Modification ------------ - Catch any SocketException thrown by initialize and add the dead node to the nodes list. - Remove IO access from PooledIOService ctor as they were throwing a SocketException occasionally and messing up the initialization of the pool and other objects Result ------ The node will be loaded in the dead state and any requests to it will result in NodeUnavailbleException until the heartbeat can succesfully send and recieve a NOOP. Change-Id: I1b778aac48e1c9732d789c88e41d1e4360baaf71 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/132922 Tested-by: Build Bot Reviewed-by: --- .../IO/Services/PooledIOServiceTests.cs | 2 ++ Src/Couchbase/Configuration/ConfigContextBase.cs | 5 ++--- .../Configuration/CouchbaseConfigContext.cs | 2 +- Src/Couchbase/IO/ConnectionPool.cs | 5 ++--- Src/Couchbase/IO/Services/PooledIOService.cs | 13 ------------- Src/Couchbase/IO/SharedConnectionPool.cs | 7 +++---- 6 files changed, 10 insertions(+), 24 deletions(-) diff --git a/Src/Couchbase.UnitTests/IO/Services/PooledIOServiceTests.cs b/Src/Couchbase.UnitTests/IO/Services/PooledIOServiceTests.cs index a5c192061..1d18719a3 100644 --- a/Src/Couchbase.UnitTests/IO/Services/PooledIOServiceTests.cs +++ b/Src/Couchbase.UnitTests/IO/Services/PooledIOServiceTests.cs @@ -196,6 +196,8 @@ public void Connection_acquired_during_construction_should_be_released_back_to_p inUse = false; }); + mockConnectionPool.Setup(x => x.Connections).Returns(() => { return new [] {connection.Object}; }); + // create io service var ioService = new PooledIOService(mockConnectionPool.Object); diff --git a/Src/Couchbase/Configuration/ConfigContextBase.cs b/Src/Couchbase/Configuration/ConfigContextBase.cs index bf16f04b5..e700188f0 100644 --- a/Src/Couchbase/Configuration/ConfigContextBase.cs +++ b/Src/Couchbase/Configuration/ConfigContextBase.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Sockets; using Couchbase.N1QL; namespace Couchbase.Configuration @@ -456,11 +457,9 @@ protected IIOService CreateIOService(PoolConfiguration poolConfiguration, IPEndP { var connectionPool = ConnectionPoolFactory(poolConfiguration, endpoint); connectionPool.SaslMechanism = SaslFactory(UserName, Password, connectionPool, Transcoder); - - var ioService = IOServiceFactory(connectionPool); connectionPool.Initialize(); - return ioService; + return IOServiceFactory(connectionPool); } protected void SwapServers(Dictionary servers) diff --git a/Src/Couchbase/Configuration/CouchbaseConfigContext.cs b/Src/Couchbase/Configuration/CouchbaseConfigContext.cs index d50b67324..db71ede5b 100644 --- a/Src/Couchbase/Configuration/CouchbaseConfigContext.cs +++ b/Src/Couchbase/Configuration/CouchbaseConfigContext.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; diff --git a/Src/Couchbase/IO/ConnectionPool.cs b/Src/Couchbase/IO/ConnectionPool.cs index 24fe4eda4..1925f23a7 100644 --- a/Src/Couchbase/IO/ConnectionPool.cs +++ b/Src/Couchbase/IO/ConnectionPool.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -75,8 +75,7 @@ public override void Initialize() { lock (_lock) { - // make sure all existing connections are authenticated and enable - // enhanved auth when required + // make sure all existing connections are authenticated and enable enhanced auth when required foreach (var connection in _refs.Values) { Authenticate(connection); diff --git a/Src/Couchbase/IO/Services/PooledIOService.cs b/Src/Couchbase/IO/Services/PooledIOService.cs index 97f667385..bfe7ff82a 100644 --- a/Src/Couchbase/IO/Services/PooledIOService.cs +++ b/Src/Couchbase/IO/Services/PooledIOService.cs @@ -31,16 +31,6 @@ public PooledIOService(IConnectionPool connectionPool) { Log.Debug("Creating PooledIOService {0}", Identity); ConnectionPool = connectionPool; - - var connection = connectionPool.Connections.FirstOrDefault() ?? connectionPool.Acquire(); - try - { - CheckEnabledServerFeatures(connection); - } - finally - { - connectionPool.Release(connection); - } } /// @@ -53,9 +43,6 @@ public PooledIOService(IConnectionPool connectionPool, ISaslMechanism saslMechan Log.Debug("Creating PooledIOService {0}", Identity); ConnectionPool = connectionPool; SaslMechanism = saslMechanism; - - var conn = connectionPool.Acquire(); - CheckEnabledServerFeatures(conn); } /// diff --git a/Src/Couchbase/IO/SharedConnectionPool.cs b/Src/Couchbase/IO/SharedConnectionPool.cs index ef5133bc8..ad5623f43 100644 --- a/Src/Couchbase/IO/SharedConnectionPool.cs +++ b/Src/Couchbase/IO/SharedConnectionPool.cs @@ -149,6 +149,7 @@ public override void Initialize() var connection = CreateAndAuthConnection(); _connections.Add(connection); } + InitializationFailed = false; //auth the connection used to select the SASL type to use for auth foreach (var connection in _connections.Where(x=>!x.IsAuthenticated)) { @@ -159,6 +160,7 @@ public override void Initialize() } catch (Exception e) { + InitializationFailed = true; Log.Debug($"Connection creation or authentication failed for {connection?.ConnectionId}", e); connection?.Dispose(); } @@ -181,10 +183,7 @@ public override void Dispose() _connections.ForEach(x => { x.Dispose(); - if (Owner != null) - { - Owner.CheckOnline(x.IsDead); - } + Owner?.CheckOnline(x.IsDead); }); _connections.Clear(); } From 83b77321d10035d30001776044ee49da7b773826 Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Tue, 28 Jul 2020 16:02:15 -0700 Subject: [PATCH 54/70] NCBC-2615: Potential unexpected response code on timeout Motivation ---------- If an operation times out, the current connection is terminated and the status defaults to "NONE"; this patch correctly sets the status type to TransportFailure (it could be a timeout or a Socket exception). Modification ------------ - Set status to TransportFailure when buffer is null in header - Make the exception type be SendTimeoutExpiredException Result ------ This code path now returns debug information that correctly correlates with the failure reason. Change-Id: I4657dd887ea27908c90dd1979329abf74ac3d980 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/133403 Reviewed-by: Tested-by: Build Bot --- Src/Couchbase/IO/Operations/OperationBase.cs | 8 ++++++++ Src/Couchbase/IO/Operations/OperationHeaderExtensions.cs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase/IO/Operations/OperationBase.cs b/Src/Couchbase/IO/Operations/OperationBase.cs index aa2fe99b3..91669b464 100644 --- a/Src/Couchbase/IO/Operations/OperationBase.cs +++ b/Src/Couchbase/IO/Operations/OperationBase.cs @@ -137,6 +137,14 @@ public void Read(byte[] buffer, OperationHeader header, ErrorCode errorCode = nu Data.Write(buffer, 0, buffer.Length); LengthReceived += buffer.Length; } + else + { + var message = $"The response buffer is null indicating that the operation with opaque [{Opaque}] did not complete. " + + "This is usually caused by a network transport failure of some kind causing the connection to close or " + + "a timeout waiting for the response. Further investigation within the logs may determine the exact cause. "; + + Exception = new SendTimeoutExpiredException(message); + } } [Obsolete] diff --git a/Src/Couchbase/IO/Operations/OperationHeaderExtensions.cs b/Src/Couchbase/IO/Operations/OperationHeaderExtensions.cs index 0c2643682..1b16ffcea 100644 --- a/Src/Couchbase/IO/Operations/OperationHeaderExtensions.cs +++ b/Src/Couchbase/IO/Operations/OperationHeaderExtensions.cs @@ -37,7 +37,7 @@ internal static OperationHeader CreateHeader(this byte[] buffer, ErrorMap errorM if (buffer == null || buffer.Length < OperationHeader.Length) { errorCode = null; - return new OperationHeader {Status = ResponseStatus.None}; + return new OperationHeader {Status = ResponseStatus.TransportFailure}; } int keyLength, framingExtrasLength; From ba9794b43f537d0996ab3c1da04b536ff54a8d6e Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Mon, 27 Jul 2020 17:55:49 -0700 Subject: [PATCH 55/70] NCBC-2613: Ensure exception thrown by config thread is handled Motivation ---------- While processing a cluster map configuration, an exception can be thrown which will break the dedicated processing loop on the queue. This fix ensures that the exception is not thrown, but handled and logged. The effect is that the processing loop on the cluster map queue will continue to process cluster maps. Modifictions ------------ - If config.Name is null ignore the config. - If an exception is thrown, handle it within the config loop Result ------ If an exception is thrown, the config queue will continue to process new configs. Change-Id: If218261ef5be501ad922a81156cbab7c5eb4b335 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/133335 Tested-by: Build Bot Reviewed-by: --- .../CarrierPublication/CarrierPublicationProvider.cs | 5 +++++ Src/Couchbase/Core/ClusterController.cs | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs b/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs index b467bcf4c..065bb6eee 100644 --- a/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs +++ b/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs @@ -181,6 +181,11 @@ public override bool RegisterObserver(IConfigObserver observer) /// if set to true [force]. public override void UpdateConfig(IBucketConfig bucketConfig, bool force = false) { + if (bucketConfig.Name == null) + { + Log.Debug("Config bucket name is null."); + return; + } IConfigObserver configObserver; if (ConfigObservers != null && ConfigObservers.TryGetValue(bucketConfig.Name, out configObserver)) { diff --git a/Src/Couchbase/Core/ClusterController.cs b/Src/Couchbase/Core/ClusterController.cs index 3e3d4e11c..d713f59ba 100644 --- a/Src/Couchbase/Core/ClusterController.cs +++ b/Src/Couchbase/Core/ClusterController.cs @@ -113,7 +113,16 @@ internal void ProcessConfig() //will make logs verbose Log.Trace("{0}", JsonConvert.SerializeObject(config)); - provider.UpdateConfig(config); + try + { + provider.UpdateConfig(config); + } + catch (Exception e) + { + var message = + $"An error has occurred while attempting to apply a cluster map configuration for rev#{config.Rev}."; + Log.Debug(message, e); + } } } } From 26d8dac979f5ced002f0451450dbb4874e7e29ed Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Thu, 30 Jul 2020 10:02:26 -0700 Subject: [PATCH 56/70] NCBC-2607: make VBucketServerMap.EnsureIPEndPointsAreLoaded thread-safe Motivation ---------- A change to improve lock contention introduced a bug where a InvalidOperationException is thrown because the enumeration can be modified by another thread. Modification ------------ - Remove Any() call and replace with Count - Use Select/ToList instead of manually creating the list and adding the items. Result ------ InvalidOperationException will not be thrown by VBucketServerMap.EnsureIPEndPoints if a thread is writing to the list while another thread is modifying the list. Change-Id: I7b787b41effd49815d9b67144bfaa597256d0acd Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/133532 Reviewed-by: Tested-by: Build Bot --- .../Server/Serialization/VBucketServerMap.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs b/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs index e547960e7..1bf67c6f8 100644 --- a/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs +++ b/Src/Couchbase/Configuration/Server/Serialization/VBucketServerMap.cs @@ -74,17 +74,13 @@ public override int GetHashCode() // ReSharper disable once InconsistentNaming internal void EnsureIPEndPointsAreLoaded() { - if (_ipEndPoints == null || !_ipEndPoints.Any()) + if (_ipEndPoints.Count == 0) { lock (_syncObj) { - if (_ipEndPoints == null || !_ipEndPoints.Any()) + if (_ipEndPoints.Count == 0) { - _ipEndPoints = new List(); - foreach (var server in ServerList) - { - _ipEndPoints.Add(IPEndPointExtensions.GetEndPoint(server)); - } + _ipEndPoints = ServerList.Select(x => IPEndPointExtensions.GetEndPoint(x)).ToList(); } } } From 3874b4862bcbfd0dbb3461a83c72bc6709209753 Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Wed, 5 Aug 2020 18:57:06 -0700 Subject: [PATCH 57/70] NCBC-2624: 'Not connected to any bucket' / NO_BUCKET error on insert Motivation ---------- Fixes a regression introduced by NCBC-2606 where 'Not connected to any bucket' and NO_BUCKET may be returned by the server because SelectBucket is not called on a Connection. Modifications ------------- - Add an IOServiceBase.Initialize method and check if the connection's server features have been enabled. Result ------ Operations should complete as expected. Change-Id: Ib0c594ac12f5075ca2d1abfcd2e2728ee01e6a29 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/133869 Tested-by: Build Bot Reviewed-by: --- Src/Couchbase.Tests/Fakes/FakeIOStrategy.cs | 6 +++++- .../IO/Services/FakeIOService.cs | 9 ++++++-- .../Configuration/ConfigContextBase.cs | 5 ++++- .../CarrierPublicationProvider.cs | 4 ---- Src/Couchbase/IO/ConnectionPoolBase.cs | 1 + Src/Couchbase/IO/IIOService.cs | 6 ++++-- Src/Couchbase/IO/Services/IOServiceBase.cs | 21 +++++++++++++++++++ 7 files changed, 42 insertions(+), 10 deletions(-) diff --git a/Src/Couchbase.Tests/Fakes/FakeIOStrategy.cs b/Src/Couchbase.Tests/Fakes/FakeIOStrategy.cs index 6a77042d9..3aa389689 100644 --- a/Src/Couchbase.Tests/Fakes/FakeIOStrategy.cs +++ b/Src/Couchbase.Tests/Fakes/FakeIOStrategy.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Runtime.ExceptionServices; using System.Security.Authentication; @@ -256,6 +256,10 @@ public void Dispose() } public ErrorMap ErrorMap { get; protected set; } + public void Initialize() + { + throw new NotImplementedException(); + } public bool SupportsEnhancedDurability { get; protected set; } diff --git a/Src/Couchbase.Tests/IO/Services/FakeIOService.cs b/Src/Couchbase.Tests/IO/Services/FakeIOService.cs index ce1be0eb1..5e4e771e9 100644 --- a/Src/Couchbase.Tests/IO/Services/FakeIOService.cs +++ b/Src/Couchbase.Tests/IO/Services/FakeIOService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -143,6 +143,11 @@ public ErrorMap ErrorMap get { throw new NotImplementedException(); } } + public void Initialize() + { + throw new NotImplementedException(); + } + public bool SupportsEnhancedDurability { get { throw new NotImplementedException(); } @@ -186,4 +191,4 @@ public bool SupportsKvErrorMap * * ************************************************************/ -#endregion \ No newline at end of file +#endregion diff --git a/Src/Couchbase/Configuration/ConfigContextBase.cs b/Src/Couchbase/Configuration/ConfigContextBase.cs index e700188f0..b08067d44 100644 --- a/Src/Couchbase/Configuration/ConfigContextBase.cs +++ b/Src/Couchbase/Configuration/ConfigContextBase.cs @@ -459,7 +459,10 @@ protected IIOService CreateIOService(PoolConfiguration poolConfiguration, IPEndP connectionPool.SaslMechanism = SaslFactory(UserName, Password, connectionPool, Transcoder); connectionPool.Initialize(); - return IOServiceFactory(connectionPool); + var ioService = IOServiceFactory(connectionPool); + ioService.Initialize(); + + return ioService; } protected void SwapServers(Dictionary servers) diff --git a/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs b/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs index 065bb6eee..988ef706d 100644 --- a/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs +++ b/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs @@ -56,7 +56,6 @@ public override IConfigInfo GetConfig(string bucketName, string username, string try { var poolConfig = bucketConfiguration.ClonePoolConfiguration(server); - var connectionPool = ConnectionPoolFactory(poolConfig, endPoint); var saslMechanism = SaslFactory(username, password, connectionPool, Transcoder); connectionPool.SaslMechanism = saslMechanism; @@ -64,9 +63,6 @@ public override IConfigInfo GetConfig(string bucketName, string username, string // setup IO service, this does SASL negotiation & hello ioService = IOServiceFactory(connectionPool); - // finish initialising connection pool ready to be used - connectionPool.Initialize(); - var operation = new Config(Transcoder, ClientConfig.DefaultOperationLifespan, endPoint); IOperationResult operationResult; diff --git a/Src/Couchbase/IO/ConnectionPoolBase.cs b/Src/Couchbase/IO/ConnectionPoolBase.cs index b3d94badd..2b7370f12 100644 --- a/Src/Couchbase/IO/ConnectionPoolBase.cs +++ b/Src/Couchbase/IO/ConnectionPoolBase.cs @@ -174,6 +174,7 @@ protected void EnableEnhancedAuthentication(IConnection connection) { if (SupportsEnhancedAuthentication && !connection.CheckedForEnhancedAuthentication) // only execute this if RBAC is enabled on the cluster { + Log.Debug("Calling SelectBucket on node {node}.", connection.EndPoint); if (string.IsNullOrWhiteSpace(Configuration.BucketName)) { const string bucketNameIsEmptyMessage = "BucketName cannot be empty when sending SelectBucket operation"; diff --git a/Src/Couchbase/IO/IIOService.cs b/Src/Couchbase/IO/IIOService.cs index be28c72dd..bdf5acb99 100644 --- a/Src/Couchbase/IO/IIOService.cs +++ b/Src/Couchbase/IO/IIOService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Threading.Tasks; using Couchbase.Authentication.SASL; @@ -141,6 +141,8 @@ public interface IIOService : IDisposable /// The Error Map that is used to map error codes from the server to error messages. /// ErrorMap ErrorMap { get; } + + void Initialize(); } } @@ -165,4 +167,4 @@ public interface IIOService : IDisposable * * ************************************************************/ -#endregion \ No newline at end of file +#endregion diff --git a/Src/Couchbase/IO/Services/IOServiceBase.cs b/Src/Couchbase/IO/Services/IOServiceBase.cs index 1a4082d60..e5098bb7e 100644 --- a/Src/Couchbase/IO/Services/IOServiceBase.cs +++ b/Src/Couchbase/IO/Services/IOServiceBase.cs @@ -101,6 +101,27 @@ public abstract class IOServiceBase : IIOService /// public ErrorMap ErrorMap { get; internal set; } + public void Initialize() + { + IConnection connection = null; + try + { + connection = ConnectionPool.Connections.FirstOrDefault() ?? ConnectionPool.Acquire(); + CheckEnabledServerFeatures(connection); + ConnectionPool.Release(connection); + + } + catch (Exception e) + { + Log.Warn(e); + if (connection != null) + { + connection.IsDead = true; + ConnectionPool.Release(connection); + } + } + } + protected ITracer Tracer => ConnectionPool.Configuration.ClientConfiguration.Tracer; public bool SupportsServerDuration { get; internal set; } From 8f544e853293501bfa603a0ca8b183641f0d103b Mon Sep 17 00:00:00 2001 From: Steven Mitcham Date: Mon, 8 Jun 2020 12:02:33 -0500 Subject: [PATCH 58/70] NCBC-2616: Ensure that memory stream is open after dispose of writer Motivation ---------- Fixes a bug where the stream writer is causing the recyclable memory stream to be disposed prior to the ToArray call being made. Change-Id: I01290acd66ae59f35d611d0991635ed1a4004d0f Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/132621 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- .../Couchbase.UnitTests.csproj | 1 + .../Serialization/DefaultSerializerTests.cs | 25 +++++++++++++++++++ .../Core/Serialization/DefaultSerializer.cs | 8 ++++-- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 Src/Couchbase.UnitTests/Serialization/DefaultSerializerTests.cs diff --git a/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj b/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj index 5322431b4..afea0a2b2 100644 --- a/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj +++ b/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj @@ -31,6 +31,7 @@ + diff --git a/Src/Couchbase.UnitTests/Serialization/DefaultSerializerTests.cs b/Src/Couchbase.UnitTests/Serialization/DefaultSerializerTests.cs new file mode 100644 index 000000000..20ce7c12b --- /dev/null +++ b/Src/Couchbase.UnitTests/Serialization/DefaultSerializerTests.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Couchbase.Core.Serialization; +using Couchbase.IO; +using Microsoft.IO; +using NUnit.Framework; + +namespace Couchbase.UnitTests.Serialization +{ + [TestFixture] + public class DefaultSerializerTests + { + [Test] + public void DefaultSerializer_Serializer_WorksWithRecyclableMemoryStream() + { + RecyclableMemoryStreamManager manager = new RecyclableMemoryStreamManager(2, 4, 4192 * 16); + MemoryStreamFactory.SetFactoryFunc(() => manager.GetStream()); + var serializer = new DefaultSerializer(); + var output = serializer.Serialize(new {Value = "Foo"}); + } + } +} diff --git a/Src/Couchbase/Core/Serialization/DefaultSerializer.cs b/Src/Couchbase/Core/Serialization/DefaultSerializer.cs index 195d0d44c..d2576669c 100644 --- a/Src/Couchbase/Core/Serialization/DefaultSerializer.cs +++ b/Src/Couchbase/Core/Serialization/DefaultSerializer.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Text; using Couchbase.IO; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -55,6 +56,9 @@ private static IContractResolver GetDefaultContractResolver() #region Fields + private static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + private const int DefaultBufferSize = 1024; + private JsonSerializerSettings _deserializationSettings; private DeserializationOptions _deserializationOptions; @@ -171,7 +175,7 @@ public byte[] Serialize(object obj) { using (var ms = MemoryStreamFactory.GetMemoryStream()) { - using (var sw = new StreamWriter(ms)) + using (var sw = new StreamWriter(ms, UTF8NoBOM, DefaultBufferSize, true)) { using (var jr = new JsonTextWriter(sw)) { From 1db7324cfaf665505d912df9f51dc5639fa8c1cb Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Sun, 16 Aug 2020 11:25:59 -0700 Subject: [PATCH 59/70] NCBC-2629: Add Raw parameter to SearchParams Motivation ---------- Add method that accepts "raw" parameters to SearchParams. Change-Id: I387cb58f513106cc53ce807f5b9beb73348fc909 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/134409 Tested-by: Build Bot Reviewed-by: --- .../Search/SearchParamsTests.cs | 27 ++++++++++++++++++- Src/Couchbase/Search/SearchParams.cs | 20 +++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Src/Couchbase.UnitTests/Search/SearchParamsTests.cs b/Src/Couchbase.UnitTests/Search/SearchParamsTests.cs index 7131a468a..d24aecc17 100644 --- a/Src/Couchbase.UnitTests/Search/SearchParamsTests.cs +++ b/Src/Couchbase.UnitTests/Search/SearchParamsTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Couchbase.N1QL; using Couchbase.Search; @@ -178,6 +178,31 @@ public void Sort_JObject_To_Output_Json() Assert.AreEqual(expected, result); } + + [Test] + public void Raw_To_Output_Json() + { + var searchParams = new SearchParams(); + searchParams.Raw("raw1", "abc"); + searchParams.Raw("raw2", new JObject + { + new JProperty("value", 6) + }); + + var result = searchParams.ToJson().ToString(Formatting.None); + + var expected = JsonConvert.SerializeObject(new + { + ctl = new { timeout = 75000 }, + raw1 = "abc", + raw2 = new + { + value = 6 + } + }, Formatting.None); + + Assert.AreEqual(expected, result); + } } } diff --git a/Src/Couchbase/Search/SearchParams.cs b/Src/Couchbase/Search/SearchParams.cs index c54d02986..73f71cab8 100644 --- a/Src/Couchbase/Search/SearchParams.cs +++ b/Src/Couchbase/Search/SearchParams.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Couchbase.N1QL; @@ -23,6 +23,18 @@ public class SearchParams : ISearchParams private TimeSpan _timeOut = new TimeSpan(0, 0, 0, 0, 75000); private ScanConsistency? _scanConsistency; private readonly JArray _sort = new JArray(); + private Dictionary _rawParameters = new Dictionary(); + + public ISearchParams Raw(string name, object value) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Parameter name cannot be null or empty."); + } + + _rawParameters.Add(name, value); + return this; + } /// /// Limits the number of matching results from a returned result-set. @@ -243,6 +255,12 @@ public JObject ToJson() { parameters.Add(new JProperty("sort", _sort)); } + + foreach (var rawParameter in _rawParameters) + { + parameters.Add(new JProperty(rawParameter.Key, rawParameter.Value)); + } + return parameters; } From e4ec84c47532c303b55ed0fc9ae37198f525c116 Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Fri, 6 Nov 2020 11:33:12 -0800 Subject: [PATCH 60/70] NCBC-2704: Bootstrap fails to authenticate with cluster NCBC-2691: NullConfigException: NodeLocator is not defined Motivation ---------- This is a regression in NCBC-2606; SELECT_BUCKET is needs to be called before enabling authentication or else it will be skipped causing the GCCCP cluster map to be returned by the server instead of CCCP. This happens in servers >= 6.5 which support fetching a config before SELECT_BUCKET is called. Modifications ------------- Call IOServiceBase.Initialize() directly after creating the IO service. Result ------ Bootstraps as expected on all server versions supported. Change-Id: I4b39fd4273dcb1dab44fa8970b46c50b29029373 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/139753 Reviewed-by: Brant Burnett Reviewed-by: Tested-by: Build Bot --- .../Providers/CarrierPublication/CarrierPublicationProvider.cs | 1 + Src/Couchbase/IO/Services/IOServiceBase.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs b/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs index 988ef706d..b82303425 100644 --- a/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs +++ b/Src/Couchbase/Configuration/Server/Providers/CarrierPublication/CarrierPublicationProvider.cs @@ -62,6 +62,7 @@ public override IConfigInfo GetConfig(string bucketName, string username, string // setup IO service, this does SASL negotiation & hello ioService = IOServiceFactory(connectionPool); + ioService.Initialize(); var operation = new Config(Transcoder, ClientConfig.DefaultOperationLifespan, endPoint); diff --git a/Src/Couchbase/IO/Services/IOServiceBase.cs b/Src/Couchbase/IO/Services/IOServiceBase.cs index e5098bb7e..aa5e8b903 100644 --- a/Src/Couchbase/IO/Services/IOServiceBase.cs +++ b/Src/Couchbase/IO/Services/IOServiceBase.cs @@ -109,7 +109,6 @@ public void Initialize() connection = ConnectionPool.Connections.FirstOrDefault() ?? ConnectionPool.Acquire(); CheckEnabledServerFeatures(connection); ConnectionPool.Release(connection); - } catch (Exception e) { From 02563387820739d5d9a74d6952de182241a1dbc0 Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Fri, 6 Nov 2020 17:51:58 -0800 Subject: [PATCH 61/70] NCBC-2703: Key too long kills connection Motivation ---------- If a key is used that is exceeds the server maximum, the server kills the connection and leads to other operations timing out. This patch verifies the key-size before sending the operation and throws a ArgumentOutOfRangeException if it exceeds 250 bytes. Modifications ------------- - Add IOperation.Validate() method and implementation that checks key size - Add call to verify in IRequestExecuter implementations before sending it. Result ------ If the key exceeds the max size of 250 bytes it will fast-fail on the client via an ArgumentInvalidException. Change-Id: I05b65ba8b99d3cd2c282b617fc98017fdf7829b9 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/139765 Tested-by: Build Bot Reviewed-by: --- .../Buckets/CouchbaseRequestExecutorTests.cs | 44 +++++++++++++++++++ .../Core/Buckets/CouchbaseRequestExecuter.cs | 24 ++++++++++ .../Core/Buckets/MemcachedRequestExecuter.cs | 12 +++++ .../Core/Buckets/RequestExecuterBase.cs | 6 +++ Src/Couchbase/IO/Operations/IOperation.cs | 4 +- Src/Couchbase/IO/Operations/OperationBase.cs | 9 ++++ 6 files changed, 98 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase.UnitTests/Core/Buckets/CouchbaseRequestExecutorTests.cs b/Src/Couchbase.UnitTests/Core/Buckets/CouchbaseRequestExecutorTests.cs index 46c9e03dc..a43617499 100644 --- a/Src/Couchbase.UnitTests/Core/Buckets/CouchbaseRequestExecutorTests.cs +++ b/Src/Couchbase.UnitTests/Core/Buckets/CouchbaseRequestExecutorTests.cs @@ -15,6 +15,7 @@ using Couchbase.IO; using Couchbase.IO.Operations; using Couchbase.IO.Operations.EnhancedDurability; +using Couchbase.UnitTests.IO.Operations; using Couchbase.UnitTests.IO.Operations.Subdocument; using Couchbase.Utils; using Couchbase.Views; @@ -524,6 +525,49 @@ public void SendWithDurability_T_does_not_dispatch_observe_when_replicate_and_pe mockServer.Verify(x => x.Send(It.IsAny>()), Times.Never); } + + const string keyTooBig = + "componentLoad::11467#e2dNmmCWZe+UM7m0qCPrYLn7nPGu9oi3QDUD6/8oGDFsMDxoOyvAWBf3YqVN/vU6tCWZhHqproDTS8/F+U9OFEQAapyUrJvZXrF7i" + + "NAVBZw2S4sH/Ux8a/yM3e5zKUwxGqoDO52+pGrH4qNl84Rxir0c8yFBSTalFTd1TGkshIz8v0twRuBFqbTj+jRkWPSw76G5OcoJ4gjKGJm7jFIuMhhKbQoY3NB" + + "Cpop2WgE7jNSGH74ZKbGGqF4/QrYGpt67ZFP3MZf1P1zlllA+70j3knVpfqkNGs4SQXDbMdyq1DfKP+Ogj4j6j75CM+QTbm6WR/JGWhVFzzGmuaUGF/sGFwumBOy" + + "fI4FT0Ox+KbnczB/aw+RX52bxY5XiVQ80fsP6Jr53QTg++4uuh9iQHSWbOGEkTdZL2fdGPjdBkzxlLW3zbkqc0RV2rKUgqiijgNNTx+fbQiRvkUGiQPlD7c9Oxdy5" + + "zl937+MggrP1MdxTUCt806prNj4q/MqniZCCGQ0vvh5ixKXCbAVjP2UV9SzINxgwobadrVGugvO1HSUt8yFFnIODQ47wHss6jCswBW9uzEr3nLn0hCnNxQ4YgRwl+3" + + "G6HmqAB2wvtAVibjnugqcok8/VeQRJtFmH8+SE1hQb"; + + [Test] + public void When_Key_Exceeds_250Bytes_IOperationT_Throw_InvalidArgumentException() + { + var mockController = new Mock(); + mockController.Setup(x => x.Configuration).Returns(new ClientConfiguration()); + + var mockConfig = new Mock(); + mockConfig.Setup(x => x.IsDataCapable).Returns(false); + mockConfig.Setup(x => x.ClientConfig).Returns(new ClientConfiguration()); + + var pending = new ConcurrentDictionary(); + var mockBuilder = new Mock>(); + var executor = new CouchbaseRequestExecuter(mockController.Object, mockConfig.Object, "default", pending); + + Assert.Throws((() => executor.SendWithRetry(new FakeSubDocumentOperation(mockBuilder.Object, keyTooBig, null, new DefaultTranscoder(), 0)))); + } + + + [Test] + public void When_Key_Exceeds_250Bytes_IOperation_Throw_InvalidArgumentException() + { + var mockController = new Mock(); + mockController.Setup(x => x.Configuration).Returns(new ClientConfiguration()); + + var mockConfig = new Mock(); + mockConfig.Setup(x => x.IsDataCapable).Returns(false); + mockConfig.Setup(x => x.ClientConfig).Returns(new ClientConfiguration()); + + var pending = new ConcurrentDictionary(); + var mockBuilder = new Mock>(); + var executor = new CouchbaseRequestExecuter(mockController.Object, mockConfig.Object, "default", pending); + + Assert.Throws((() => executor.SendWithRetry(new FakeOperationWithRequiredKey(keyTooBig, null,new DefaultTranscoder(), 0)))); + } } } diff --git a/Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs b/Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs index 7d18f1230..ce37474e5 100644 --- a/Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs +++ b/Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs @@ -105,6 +105,9 @@ public IServer GetServer(string key, uint revision, out IVBucket vBucket) /// The cluster does not support Data services. public override IOperationResult SendWithDurability(IOperation operation, bool deletion, ReplicateTo replicateTo, PersistTo persistTo) { + //Validate key length + operation.Validate(); + using (Tracer.StartParentScope(operation, ConfigInfo.BucketName, true)) { IOperationResult result; @@ -195,6 +198,9 @@ public override IOperationResult SendWithDurability(IOperation operatio /// The cluster does not support Data services. public override IOperationResult SendWithDurability(IOperation operation, bool deletion, ReplicateTo replicateTo, PersistTo persistTo) { + //Validate key length + operation.Validate(); + using (Tracer.StartParentScope(operation, ConfigInfo.BucketName, true)) { IOperationResult result; @@ -293,6 +299,9 @@ public override async Task> SendWithDurabilityAsync(IOper bool deletion, ReplicateTo replicateTo, PersistTo persistTo, TaskCompletionSource> tcs = null, CancellationTokenSource cts = null) { + //Validate key length + operation.Validate(); + using (Tracer.StartParentScope(operation, ConfigInfo.BucketName, true)) { tcs = tcs ?? new TaskCompletionSource>(); @@ -428,6 +437,9 @@ public override async Task SendWithDurabilityAsync(IOperation ReplicateTo replicateTo, PersistTo persistTo, TaskCompletionSource tcs = null, CancellationTokenSource cts = null) { + //Validate key length + operation.Validate(); + using (Tracer.StartParentScope(operation, ConfigInfo.BucketName, true)) { var config = ConfigInfo.ClientConfig.BucketConfigs[BucketName]; @@ -557,6 +569,9 @@ public override async Task SendWithDurabilityAsync(IOperation /// The cluster does not support Data services. public override IOperationResult SendWithRetry(IOperation operation) { + //Validate key length + operation.Validate(); + //Is the cluster configured for Data services? if (!ConfigInfo.IsDataCapable) { @@ -635,6 +650,9 @@ public override IOperationResult SendWithRetry(IOperation operation) /// The cluster does not support Data services. public override IOperationResult SendWithRetry(IOperation operation) { + //Validate key length + operation.Validate(); + //Is the cluster configured for Data services? if (!ConfigInfo.IsDataCapable) { @@ -717,6 +735,9 @@ public override async Task> SendWithRetryAsync(IOperation TaskCompletionSource> tcs = null, CancellationTokenSource cts = null) { + //Validate key length + operation.Validate(); + using (Tracer.StartParentScope(operation, ConfigInfo.BucketName)) { tcs = tcs ?? new TaskCompletionSource>(); @@ -771,6 +792,9 @@ public override async Task SendWithRetryAsync(IOperation opera TaskCompletionSource tcs = null, CancellationTokenSource cts = null) { + //Validate key length + operation.Validate(); + using (Tracer.StartParentScope(operation, ConfigInfo.BucketName)) { tcs = tcs ?? new TaskCompletionSource(); diff --git a/Src/Couchbase/Core/Buckets/MemcachedRequestExecuter.cs b/Src/Couchbase/Core/Buckets/MemcachedRequestExecuter.cs index e6835c16d..2c2f54b37 100644 --- a/Src/Couchbase/Core/Buckets/MemcachedRequestExecuter.cs +++ b/Src/Couchbase/Core/Buckets/MemcachedRequestExecuter.cs @@ -50,6 +50,9 @@ private IServer GetServer(string key) /// An with the status of the request. public override IOperationResult SendWithRetry(IOperation operation) { + //Validate key length + operation.Validate(); + using (Tracer.StartParentScope(operation, ConfigInfo.BucketName)) { IOperationResult operationResult = new OperationResult @@ -110,6 +113,9 @@ public override IOperationResult SendWithRetry(IOperation operation) /// An with the status of the request. public override IOperationResult SendWithRetry(IOperation operation) { + //Validate key length + operation.Validate(); + using (Tracer.StartParentScope(operation, ConfigInfo.BucketName)) { IOperationResult operationResult = new OperationResult @@ -178,6 +184,9 @@ public override async Task> SendWithRetryAsync(IOperation TaskCompletionSource> tcs = null, CancellationTokenSource cts = null) { + //Validate key length + operation.Validate(); + using (Tracer.StartParentScope(operation, ConfigInfo.BucketName)) { tcs = tcs ?? new TaskCompletionSource>(); @@ -226,6 +235,9 @@ public override async Task SendWithRetryAsync(IOperation opera TaskCompletionSource tcs = null, CancellationTokenSource cts = null) { + //Validate key length + operation.Validate(); + using (Tracer.StartParentScope(operation, ConfigInfo.BucketName)) { tcs = tcs ?? new TaskCompletionSource(); diff --git a/Src/Couchbase/Core/Buckets/RequestExecuterBase.cs b/Src/Couchbase/Core/Buckets/RequestExecuterBase.cs index f16c968e7..8c6fb8773 100644 --- a/Src/Couchbase/Core/Buckets/RequestExecuterBase.cs +++ b/Src/Couchbase/Core/Buckets/RequestExecuterBase.cs @@ -367,6 +367,9 @@ public virtual Task SendWithDurabilityAsync(IOperation operati /// public IOperationResult ReadFromReplica(ReplicaRead operation) { + //Validate key length + operation.Validate(); + //Is the cluster configured for Data services? if (!ConfigInfo.IsDataCapable) throw new ServiceNotSupportedException("The cluster does not support Data services."); @@ -428,6 +431,9 @@ public IOperationResult ReadFromReplica(ReplicaRead operation) /// public async Task> ReadFromReplicaAsync(ReplicaRead operation) { + //Validate key length + operation.Validate(); + var tcs = new TaskCompletionSource>(); var cts = new CancellationTokenSource(OperationLifeSpan); cts.CancelAfter(OperationLifeSpan); diff --git a/Src/Couchbase/IO/Operations/IOperation.cs b/Src/Couchbase/IO/Operations/IOperation.cs index 9de57717f..b12339d7b 100644 --- a/Src/Couchbase/IO/Operations/IOperation.cs +++ b/Src/Couchbase/IO/Operations/IOperation.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Couchbase.Configuration.Server.Serialization; using Couchbase.Core; using Couchbase.Core.Transcoders; @@ -96,6 +96,8 @@ public interface IOperation string BucketName { get; set; } int GetRetryTimeout(int defaultTimeout); + + void Validate(); } } diff --git a/Src/Couchbase/IO/Operations/OperationBase.cs b/Src/Couchbase/IO/Operations/OperationBase.cs index 91669b464..a81fbc484 100644 --- a/Src/Couchbase/IO/Operations/OperationBase.cs +++ b/Src/Couchbase/IO/Operations/OperationBase.cs @@ -22,6 +22,7 @@ internal abstract class OperationBase : IOperation { private readonly ILog _log = LogManager.GetLogger(); + private const int MaxKeylength = 250; private bool _timedOut; protected IByteConverter Converter; protected Flags Flags; @@ -528,6 +529,14 @@ public int GetRetryTimeout(int defaultTimeout) return ErrorCode.GetNextInterval(Attempts, defaultTimeout); } + public virtual void Validate() + { + if (KeyLength > MaxKeylength) + { + throw new ArgumentOutOfRangeException(nameof(KeyLength), "Keys must be 250 bytes or smaller."); + } + } + protected void TryReadMutationToken(byte[] buffer) { if (buffer.Length >= 40 && VBucket != null) From d33dbb4a1178a3f029b01f97f5e1bd4d333fc448 Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Fri, 6 Nov 2020 18:08:27 -0800 Subject: [PATCH 62/70] NCBC-2287: ListAppend, MapAdd, QueuePush create short TTL documents Motivation ---------- Fixes a bug where the timeout was used as the expiration thus the doc would expire quickly. Change-Id: Iec18aab94c524b43df0436b189de20e50504d567 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/139766 Tested-by: Build Bot Reviewed-by: --- Src/Couchbase/CouchbaseBucket.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/Couchbase/CouchbaseBucket.cs b/Src/Couchbase/CouchbaseBucket.cs index d1294f5af..8b895e4d6 100644 --- a/Src/Couchbase/CouchbaseBucket.cs +++ b/Src/Couchbase/CouchbaseBucket.cs @@ -7824,7 +7824,7 @@ private IResult ExecuteAndCreateDocumentIfMissing(Func> if (result.Exception is DocumentDoesNotExistException && createDoc) { // try and insert the document - var insertResult = Insert(key, defaultValue, timeout); + var insertResult = Insert(key, defaultValue); if (insertResult.Success) { return new DefaultResult @@ -7871,7 +7871,7 @@ private async Task ExecuteAndCreateDocumentIfMissingAsync(Func Date: Mon, 11 Jan 2021 18:15:01 -0800 Subject: [PATCH 63/70] NCBC-2768: Improve precision of UnixMillisecondsConverter Motivation ---------- The UnixMillisecondsConverter had precision to the tens position; this patch improves it to the one hundred-thousands position. There is still some rounding beyond that. Modifications ------------- - Refactor ReadJson() to use ticks in conversion Result ------ Improves precision when using UnixMillisecondsConverter. Change-Id: I0175700b79abccf864a8b1552e5371517fb7dd4d Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/143229 Tested-by: Build Bot Reviewed-by: --- .../Core/Serialization/UnixMillisecondsConverter.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Src/Couchbase/Core/Serialization/UnixMillisecondsConverter.cs b/Src/Couchbase/Core/Serialization/UnixMillisecondsConverter.cs index 7a6921c73..0bf514b94 100644 --- a/Src/Couchbase/Core/Serialization/UnixMillisecondsConverter.cs +++ b/Src/Couchbase/Core/Serialization/UnixMillisecondsConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using Newtonsoft.Json; namespace Couchbase.Core.Serialization @@ -25,10 +25,10 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist { return null; } - else - { - return UnixEpoch.AddMilliseconds(Convert.ToDouble(reader.Value)); - } + + var dbl = Convert.ToDouble(reader.Value); + var ticks = (long)(dbl * TimeSpan.TicksPerMillisecond); + return new DateTime(UnixEpoch.Ticks + ticks, DateTimeKind.Utc); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) From 9661ce8817aa2e500a7f5042509501c160589d00 Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Tue, 9 Mar 2021 17:49:42 -0800 Subject: [PATCH 64/70] NCBC-2840: Mismatch between seconds/milliseconds with K/V timeouts Motivation ---------- Fixes a bug where a milliseconds timeout is compared to seconds. Modifications ------------- - Make TimespanExtensions.GetMilliseconds method - Make TimespanExtensions.GetSeconds obsolete - Update any code using GetSeconds to GetMilliseconds - Update unit tests Result ------ The timeout comparison correctly compares milliseconds. Change-Id: If1bd7c822c23d648ef7d12277769d7792cecc5f7 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/148125 Tested-by: Build Bot Reviewed-by: --- .../CouchbaseBucketTests.cs | 2 +- .../DecrementAsyncTests.cs | 18 +- .../CouchbaseBucketTests/DecrementTests.cs | 18 +- .../IncrementAsyncTests.cs | 18 +- .../CouchbaseBucketTests/IncrementTests.cs | 18 +- Src/Couchbase/CouchbaseBucket.cs | 164 +++++++++--------- .../SubDocument/SubDocSingularBase.cs | 2 +- Src/Couchbase/MemcachedBucket.cs | 78 ++++----- Src/Couchbase/Utils/TimeSpanExtensions.cs | 13 +- 9 files changed, 171 insertions(+), 160 deletions(-) diff --git a/Src/Couchbase.UnitTests/CouchbaseBucketTests.cs b/Src/Couchbase.UnitTests/CouchbaseBucketTests.cs index bad179e97..0dd791b95 100644 --- a/Src/Couchbase.UnitTests/CouchbaseBucketTests.cs +++ b/Src/Couchbase.UnitTests/CouchbaseBucketTests.cs @@ -138,7 +138,7 @@ public async Task UpsertAsyncOverloadPassesExpirationAndCasParametersAsExpected( await bucket.UpsertAsync("key", new { }, TimeSpan.FromSeconds(10), ReplicateTo.One, PersistTo.One, TimeSpan.FromSeconds(5)); mockRequestExecutor.Verify(x => x.SendWithDurabilityAsync( - It.Is>(set => set.Expires == 10 && set.Cas == 0 && set.Timeout == 5), + It.Is>(set => set.Expires == 10 && set.Cas == 0 && set.Timeout == 5000), false, ReplicateTo.One, PersistTo.One, diff --git a/Src/Couchbase.UnitTests/CouchbaseBucketTests/DecrementAsyncTests.cs b/Src/Couchbase.UnitTests/CouchbaseBucketTests/DecrementAsyncTests.cs index 8e452b0a0..f9886d301 100644 --- a/Src/Couchbase.UnitTests/CouchbaseBucketTests/DecrementAsyncTests.cs +++ b/Src/Couchbase.UnitTests/CouchbaseBucketTests/DecrementAsyncTests.cs @@ -45,7 +45,7 @@ public async Task DecrementAsync_With_Key_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -77,7 +77,7 @@ public async Task DecrementAsync_With_Timeout_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(10, operation.Timeout); + Assert.AreEqual(10000, operation.Timeout); } [Test] @@ -109,7 +109,7 @@ public async Task DecrementAsync_With_Delta_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -141,7 +141,7 @@ public async Task DecrementAsync_With_DeltaTimeout_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(10, operation.Timeout); + Assert.AreEqual(10000, operation.Timeout); } [Test] @@ -173,7 +173,7 @@ public async Task DecrementAsync_With_DeltaInitial_ExecutesCorrectOperation() Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -205,7 +205,7 @@ public async Task DecrementAsync_With_DeltaInitialExpirationTime_ExecutesCorrect Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -237,7 +237,7 @@ public async Task DecrementAsync_With_DeltaInitialExpiration_ExecutesCorrectOper Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -269,7 +269,7 @@ public async Task DecrementAsync_With_DeltaInitialExpirationTimeTimeout_Executes Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(20, operation.Timeout); + Assert.AreEqual(20000, operation.Timeout); } [Test] @@ -301,7 +301,7 @@ public async Task DecrementAsync_With_DeltaInitialExpirationTimeout_ExecutesCorr Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(20, operation.Timeout); + Assert.AreEqual(20000, operation.Timeout); } } } diff --git a/Src/Couchbase.UnitTests/CouchbaseBucketTests/DecrementTests.cs b/Src/Couchbase.UnitTests/CouchbaseBucketTests/DecrementTests.cs index 4918cba4c..cfa583f8c 100644 --- a/Src/Couchbase.UnitTests/CouchbaseBucketTests/DecrementTests.cs +++ b/Src/Couchbase.UnitTests/CouchbaseBucketTests/DecrementTests.cs @@ -42,7 +42,7 @@ public void Decrement_With_Key_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -73,7 +73,7 @@ public void Decrement_With_Timeout_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(10, operation.Timeout); + Assert.AreEqual(10000, operation.Timeout); } [Test] @@ -104,7 +104,7 @@ public void Decrement_With_Delta_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -135,7 +135,7 @@ public void Decrement_With_DeltaTimeout_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(10, operation.Timeout); + Assert.AreEqual(10000, operation.Timeout); } [Test] @@ -166,7 +166,7 @@ public void Decrement_With_DeltaInitial_ExecutesCorrectOperation() Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -197,7 +197,7 @@ public void Decrement_With_DeltaInitialExpirationTime_ExecutesCorrectOperation() Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -228,7 +228,7 @@ public void Decrement_With_DeltaInitialExpiration_ExecutesCorrectOperation() Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -259,7 +259,7 @@ public void Decrement_With_DeltaInitialExpirationTimeTimeout_ExecutesCorrectOper Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(20, operation.Timeout); + Assert.AreEqual(20000, operation.Timeout); } [Test] @@ -290,7 +290,7 @@ public void Decrement_With_DeltaInitialExpirationTimeout_ExecutesCorrectOperatio Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(20, operation.Timeout); + Assert.AreEqual(20000, operation.Timeout); } } } diff --git a/Src/Couchbase.UnitTests/CouchbaseBucketTests/IncrementAsyncTests.cs b/Src/Couchbase.UnitTests/CouchbaseBucketTests/IncrementAsyncTests.cs index 062c053eb..3d2f17a3a 100644 --- a/Src/Couchbase.UnitTests/CouchbaseBucketTests/IncrementAsyncTests.cs +++ b/Src/Couchbase.UnitTests/CouchbaseBucketTests/IncrementAsyncTests.cs @@ -46,7 +46,7 @@ public async Task IncrementAsync_With_Key_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -78,7 +78,7 @@ public async Task IncrementAsync_With_Timeout_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(10, operation.Timeout); + Assert.AreEqual(10000, operation.Timeout); } [Test] @@ -110,7 +110,7 @@ public async Task IncrementAsync_With_Delta_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -142,7 +142,7 @@ public async Task IncrementAsync_With_DeltaTimeout_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(10, operation.Timeout); + Assert.AreEqual(10000, operation.Timeout); } [Test] @@ -174,7 +174,7 @@ public async Task IncrementAsync_With_DeltaInitial_ExecutesCorrectOperation() Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -206,7 +206,7 @@ public async Task IncrementAsync_With_DeltaInitialExpirationTime_ExecutesCorrect Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -238,7 +238,7 @@ public async Task IncrementAsync_With_DeltaInitialExpiration_ExecutesCorrectOper Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -270,7 +270,7 @@ public async Task IncrementAsync_With_DeltaInitialExpirationTimeTimeout_Executes Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(20, operation.Timeout); + Assert.AreEqual(20000, operation.Timeout); } [Test] @@ -302,7 +302,7 @@ public async Task IncrementAsync_With_DeltaInitialExpirationTimeout_ExecutesCorr Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(20, operation.Timeout); + Assert.AreEqual(20000, operation.Timeout); } } } diff --git a/Src/Couchbase.UnitTests/CouchbaseBucketTests/IncrementTests.cs b/Src/Couchbase.UnitTests/CouchbaseBucketTests/IncrementTests.cs index a39e4353e..54e8a780c 100644 --- a/Src/Couchbase.UnitTests/CouchbaseBucketTests/IncrementTests.cs +++ b/Src/Couchbase.UnitTests/CouchbaseBucketTests/IncrementTests.cs @@ -43,7 +43,7 @@ public void Increment_With_Key_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -74,7 +74,7 @@ public void Increment_With_Timeout_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(10, operation.Timeout); + Assert.AreEqual(10000, operation.Timeout); } [Test] @@ -105,7 +105,7 @@ public void Increment_With_Delta_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -136,7 +136,7 @@ public void Increment_With_DeltaTimeout_ExecutesCorrectOperation() Assert.AreEqual(1, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(10, operation.Timeout); + Assert.AreEqual(10000, operation.Timeout); } [Test] @@ -167,7 +167,7 @@ public void Increment_With_DeltaInitial_ExecutesCorrectOperation() Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(0, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -198,7 +198,7 @@ public void Increment_With_DeltaInitialExpirationTime_ExecutesCorrectOperation() Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -229,7 +229,7 @@ public void Increment_With_DeltaInitialExpiration_ExecutesCorrectOperation() Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(2, operation.Timeout); + Assert.AreEqual(2500, operation.Timeout); } [Test] @@ -260,7 +260,7 @@ public void Increment_With_DeltaInitialExpirationTimeTimeout_ExecutesCorrectOper Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(20, operation.Timeout); + Assert.AreEqual(20000, operation.Timeout); } [Test] @@ -291,7 +291,7 @@ public void Increment_With_DeltaInitialExpirationTimeout_ExecutesCorrectOperatio Assert.AreEqual(4, operation.Initial); Assert.AreEqual("bucket", operation.BucketName); Assert.AreEqual(10, operation.Expires); - Assert.AreEqual(20, operation.Timeout); + Assert.AreEqual(20000, operation.Timeout); } } } diff --git a/Src/Couchbase/CouchbaseBucket.cs b/Src/Couchbase/CouchbaseBucket.cs index 8b895e4d6..b8f13d4e7 100644 --- a/Src/Couchbase/CouchbaseBucket.cs +++ b/Src/Couchbase/CouchbaseBucket.cs @@ -193,7 +193,7 @@ public IOperationResult Append(string key, string value) public IOperationResult Append(string key, string value, TimeSpan timeout) { CheckDisposed(); - var operation = new Append(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Append(key, value, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -220,7 +220,7 @@ public bool Exists(string key) /// public bool Exists(string key, TimeSpan timeout) { - var observe = new Observe(key, null, _transcoder, timeout.GetSeconds()) + var observe = new Observe(key, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -280,7 +280,7 @@ public async Task ExistsAsync(string key) public async Task ExistsAsync(string key, TimeSpan timeout) { CheckDisposed(); - var observe = new Observe(key, null, _transcoder, timeout.GetSeconds()) + var observe = new Observe(key, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -331,7 +331,7 @@ internal async Task> _ExistsAsync(string key) public Task> AppendAsync(string key, string value, TimeSpan timeout) { CheckDisposed(); - var operation = new Append(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Append(key, value, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -362,7 +362,7 @@ public IOperationResult Append(string key, byte[] value) public IOperationResult Append(string key, byte[] value, TimeSpan timeout) { CheckDisposed(); - var operation = new Append(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Append(key, value, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -403,7 +403,7 @@ public Task> AppendAsync(string key, byte[] value) public Task> AppendAsync(string key, byte[] value, TimeSpan timeout) { CheckDisposed(); - var operation = new Append(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Append(key, value, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -473,7 +473,7 @@ public IOperationResult Touch(string key, TimeSpan expiration) /// An with no value. public IOperationResult Touch(string key, TimeSpan expiration, TimeSpan timeout) { - var touch = new Touch(key, null, _transcoder, timeout.GetSeconds()) + var touch = new Touch(key, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration.ToTtl(), BucketName = Name @@ -494,7 +494,7 @@ public Task TouchAsync(string key, TimeSpan expiration) public Task TouchAsync(string key, TimeSpan expiration, TimeSpan timeout) { - var touch = new Touch(key, null, _transcoder, timeout.GetSeconds()) + var touch = new Touch(key, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration.ToTtl(), BucketName = Name @@ -691,7 +691,7 @@ public IOperationResult Decrement(string key, ulong delta, ulong initial, public IOperationResult Decrement(string key, ulong delta, ulong initial, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Decrement(key, initial, delta, null, _transcoder, timeout.GetSeconds()) + var operation = new Decrement(key, initial, delta, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name, Expires = expiration @@ -718,7 +718,7 @@ public IOperationResult Decrement(string key, ulong delta, ulong initial, public Task> DecrementAsync(string key, ulong delta, ulong initial, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Decrement(key, initial, delta, null, _transcoder, timeout.GetSeconds()) + var operation = new Decrement(key, initial, delta, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name, Expires = expiration @@ -850,7 +850,7 @@ public Task> DecrementAsync(string key, ulong delta, ulo public Task> DecrementAsync(string key, ulong delta, ulong initial, TimeSpan expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Decrement(key, initial, delta, null, _transcoder, timeout.GetSeconds()) + var operation = new Decrement(key, initial, delta, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name, Expires = expiration.ToTtl() @@ -971,7 +971,7 @@ public IOperationResult Get(string key) /// public IOperationResult Get(string key, TimeSpan timeout) { - var operation = new Get(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Get(key, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -981,7 +981,7 @@ public IOperationResult Get(string key, TimeSpan timeout) public Task> GetFromReplicaAsync(string key, TimeSpan timeout) { //the vbucket will be set in the IRequestExecuter - passing nulls should be refactored in the future - var operation = new ReplicaRead(key, null, _transcoder, timeout.GetSeconds()); + var operation = new ReplicaRead(key, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.ReadFromReplicaAsync(operation); } @@ -1137,7 +1137,7 @@ public Task> GetAsync(string key) public Task> GetAsync(string key, TimeSpan timeout) { CheckDisposed(); - var operation = new Get(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Get(key, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -1264,7 +1264,7 @@ public IOperationResult GetFromReplica(string key) public IOperationResult GetFromReplica(string key, TimeSpan timeout) { //the vbucket will be set in the IRequestExecuter - passing nulls should be refactored in the future - var operation = new ReplicaRead(key, null, _transcoder, timeout.GetSeconds()); + var operation = new ReplicaRead(key, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.ReadFromReplica(operation); } @@ -1313,7 +1313,7 @@ public IOperationResult GetWithLock(string key, uint expiration) { expiration = defaultExpiration; } - var getl = new GetL(key, null, _transcoder, GlobalTimeout.GetSeconds()) + var getl = new GetL(key, null, _transcoder, GlobalTimeout.GetMilliseconds()) { Expiration = expiration, BucketName = Name @@ -1329,7 +1329,7 @@ public Task> GetAndLockAsync(string key, uint expiration, { expiration = defaultExpiration; } - var getl = new GetL(key, null, _transcoder, timeout.GetSeconds()) + var getl = new GetL(key, null, _transcoder, timeout.GetMilliseconds()) { Expiration = expiration, BucketName = Name @@ -1386,7 +1386,7 @@ public IOperationResult GetAndLock(string key, uint expiration, TimeSpan t { expiration = defaultExpiration; } - var getl = new GetL(key, null, _transcoder, timeout.GetSeconds()) + var getl = new GetL(key, null, _transcoder, timeout.GetMilliseconds()) { Expiration = expiration, BucketName = Name @@ -1443,7 +1443,7 @@ public Task> GetWithLockAsync(string key, uint expiration { expiration = defaultExpiration; } - var getl = new GetL(key, null, _transcoder, GlobalTimeout.GetSeconds()) + var getl = new GetL(key, null, _transcoder, GlobalTimeout.GetMilliseconds()) { Expiration = expiration, BucketName = Name @@ -1521,7 +1521,7 @@ public Task> GetAndLockAsync(string key, TimeSpan expirat public Task UnlockAsync(string key, ulong cas, TimeSpan timeout) { CheckDisposed(); - var operation = new Unlock(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Unlock(key, null, _transcoder, timeout.GetMilliseconds()) { Cas = cas, BucketName = Name @@ -1679,7 +1679,7 @@ public IOperationResult Increment(string key, ulong delta, ulong initial, public IOperationResult Increment(string key, ulong delta, ulong initial, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Increment(key, initial, delta, null, _transcoder, timeout.GetSeconds()) + var operation = new Increment(key, initial, delta, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name, Expires = expiration @@ -1707,7 +1707,7 @@ public IOperationResult Increment(string key, ulong delta, ulong initial, public Task> IncrementAsync(string key, ulong delta, ulong initial, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Increment(key, initial, delta, null, _transcoder, timeout.GetSeconds()) + var operation = new Increment(key, initial, delta, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name, Expires = expiration @@ -1989,7 +1989,7 @@ public async Task> InsertAsync(IDocument document, Repl public IOperationResult Insert(string key, T value) { CheckDisposed(); - var operation = new Add(key, value, null, _transcoder, GlobalTimeout.GetSeconds()) + var operation = new Add(key, value, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name }; @@ -2030,7 +2030,7 @@ public IOperationResult Insert(string key, T value, uint expiration) public IOperationResult Insert(string key, T value, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Add(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Add(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, BucketName = Name @@ -2056,7 +2056,7 @@ public IOperationResult Insert(string key, T value, uint expiration, TimeS public Task> InsertAsync(string key, T value, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Add(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Add(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, BucketName = Name @@ -2282,7 +2282,7 @@ public IOperationResult Insert(string key, T value, ReplicateTo replicateT public IOperationResult Insert(string key, T value, ReplicateTo replicateTo, PersistTo persistTo, TimeSpan timeout) { CheckDisposed(); - var operation = new Add(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Add(key, value, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -2304,7 +2304,7 @@ public IOperationResult Insert(string key, T value, ReplicateTo replicateT public Task> InsertAsync(string key, T value, ReplicateTo replicateTo, PersistTo persistTo, TimeSpan timeout) { CheckDisposed(); - var operation = new Add(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Add(key, value, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -2350,7 +2350,7 @@ public IOperationResult Insert(string key, T value, uint expiration, Repli TimeSpan timeout) { CheckDisposed(); - var operation = new Add(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Add(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, BucketName = Name @@ -2379,7 +2379,7 @@ public Task> InsertAsync(string key, T value, uint expira TimeSpan timeout) { CheckDisposed(); - var operation = new Add(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Add(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, BucketName = Name @@ -2433,7 +2433,7 @@ public IOperationResult Insert(string key, T value, TimeSpan expiration, R public Task> InsertAsync(string key, T value) { CheckDisposed(); - var operation = new Add(key, value, null, _transcoder, GlobalTimeout.GetSeconds()) + var operation = new Add(key, value, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name }; @@ -2667,7 +2667,7 @@ public async Task ObserveAsync(string key, ulong cas, bool dele { CheckDisposed(); var config = _configInfo.ClientConfig.BucketConfigs[Name]; - var observer = new KeyObserver(_pending, _configInfo, _clusterController, config.ObserveInterval, (int)timeout.GetSeconds()); + var observer = new KeyObserver(_pending, _configInfo, _clusterController, config.ObserveInterval, (int)timeout.GetMilliseconds()); using (var cts = new CancellationTokenSource(config.ObserveTimeout)) { var result = await observer.ObserveAsync(key, cas, deletion, replicateTo, persistTo, cts).ContinueOnAnyContext(); @@ -2713,7 +2713,7 @@ public ObserveResponse Observe(string key, ulong cas, bool deletion, ReplicateTo CheckDisposed(); var config = _configInfo.ClientConfig.BucketConfigs[Name]; - var observer = new KeyObserver(_pending, _configInfo, _clusterController, config.ObserveInterval, (int)timeout.GetSeconds()); + var observer = new KeyObserver(_pending, _configInfo, _clusterController, config.ObserveInterval, (int)timeout.GetMilliseconds()); return observer.Observe(key, cas, deletion, replicateTo, persistTo) ? ObserveResponse.DurabilitySatisfied @@ -2766,7 +2766,7 @@ public IOperationResult Prepend(string key, string value) public IOperationResult Prepend(string key, string value, TimeSpan timeout) { CheckDisposed(); - var operation = new Prepend(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Prepend(key, value, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -2796,7 +2796,7 @@ public IOperationResult GetAndTouch(string key, TimeSpan expiration) /// public IOperationResult GetAndTouch(string key, TimeSpan expiration, TimeSpan timeout) { - var operation = new GetT(key, null, _transcoder, timeout.GetSeconds()) + var operation = new GetT(key, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration.ToTtl(), BucketName = Name @@ -2827,7 +2827,7 @@ public Task> GetAndTouchAsync(string key, TimeSpan expira /// public Task> GetAndTouchAsync(string key, TimeSpan expiration, TimeSpan timeout) { - var operation = new GetT(key, null, _transcoder, timeout.GetSeconds()) + var operation = new GetT(key, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration.ToTtl(), BucketName = Name @@ -2885,7 +2885,7 @@ public Task> GetAndTouchDocumentAsync(string key, TimeSpan public Task> PrependAsync(string key, string value, TimeSpan timeout) { CheckDisposed(); - var operation = new Prepend(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Prepend(key, value, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -2915,7 +2915,7 @@ public IOperationResult Prepend(string key, byte[] value) public IOperationResult Prepend(string key, byte[] value, TimeSpan timeout) { CheckDisposed(); - var operation = new Prepend(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Prepend(key, value, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -2956,7 +2956,7 @@ public Task> PrependAsync(string key, byte[] value) public Task> PrependAsync(string key, byte[] value, TimeSpan timeout) { CheckDisposed(); - var operation = new Prepend(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Prepend(key, value, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -3129,7 +3129,7 @@ public IOperationResult Remove(string key, TimeSpan timeout) public Task RemoveAsync(string key, TimeSpan timeout) { CheckDisposed(); - var operation = new Delete(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Delete(key, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -3158,7 +3158,7 @@ public IOperationResult Remove(string key, ulong cas) /// public IOperationResult Remove(string key, ulong cas, TimeSpan timeout) { - var operation = new Delete(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Delete(key, null, _transcoder, timeout.GetMilliseconds()) { Cas = cas }; @@ -3218,7 +3218,7 @@ public IOperationResult Remove(IDocument document, ReplicateTo replicateTo public Task RemoveAsync(string key, ulong cas, TimeSpan timeout) { CheckDisposed(); - var operation = new Delete(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Delete(key, null, _transcoder, timeout.GetMilliseconds()) { Cas = cas, BucketName = Name @@ -3291,7 +3291,7 @@ public IOperationResult Remove(string key, ulong cas, ReplicateTo replicateTo, T public Task RemoveAsync(IDocument document, ReplicateTo replicateTo, TimeSpan timeout) { CheckDisposed(); - var operation = new Delete(document.Id, null, _transcoder, timeout.GetSeconds()) + var operation = new Delete(document.Id, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -3340,7 +3340,7 @@ public IOperationResult Remove(IDocument document, ReplicateTo replicateTo public Task RemoveAsync(string key, ulong cas, ReplicateTo replicateTo, TimeSpan timeout) { CheckDisposed(); - var operation = new Delete(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Delete(key, null, _transcoder, timeout.GetMilliseconds()) { Cas = cas, BucketName = Name @@ -3372,7 +3372,7 @@ public IOperationResult Remove(string key, ReplicateTo replicateTo, PersistTo pe /// public IOperationResult Remove(string key, ReplicateTo replicateTo, PersistTo persistTo, TimeSpan timeout) { - var operation = new Delete(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Delete(key, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -3392,7 +3392,7 @@ public IOperationResult Remove(string key, ReplicateTo replicateTo, PersistTo pe public Task RemoveAsync(string key, ReplicateTo replicateTo, PersistTo persistTo, TimeSpan timeout) { CheckDisposed(); - var operation = new Delete(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Delete(key, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -3425,7 +3425,7 @@ public IOperationResult Remove(string key, ulong cas, ReplicateTo replicateTo, P /// public IOperationResult Remove(string key, ulong cas, ReplicateTo replicateTo, PersistTo persistTo, TimeSpan timeout) { - var operation = new Delete(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Delete(key, null, _transcoder, timeout.GetMilliseconds()) { Cas = cas, BucketName = Name @@ -3447,7 +3447,7 @@ public IOperationResult Remove(string key, ulong cas, ReplicateTo replicateTo, P public Task RemoveAsync(string key, ulong cas, ReplicateTo replicateTo, PersistTo persistTo, TimeSpan timeout) { CheckDisposed(); - var operation = new Delete(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Delete(key, null, _transcoder, timeout.GetMilliseconds()) { Cas = cas, BucketName = Name @@ -3550,7 +3550,7 @@ public IDictionary Remove(IList keys, Parallel for (var i = range.Item1; i < range.Item2; i++) { var key = keys[i]; - var result = Remove(key, timeout.GetSeconds()); + var result = Remove(key, timeout.GetMilliseconds()); results.TryAdd(key, result); } }); @@ -3626,7 +3626,7 @@ public Task RemoveAsync(IDocument document, ReplicateTo public Task RemoveAsync(IDocument document, ReplicateTo replicateTo, PersistTo persistTo, TimeSpan timeout) { CheckDisposed(); - var operation = new Delete(document.Id, null, _transcoder, timeout.GetSeconds()) + var operation = new Delete(document.Id, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -3877,7 +3877,7 @@ public async Task> ReplaceAsync(IDocument document, Rep /// An object implementing the interface. public IOperationResult Replace(string key, T value) { - var operation = new Replace(key, value, null, _transcoder, GlobalTimeout.GetSeconds()) + var operation = new Replace(key, value, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name }; @@ -3910,7 +3910,7 @@ public Task> ReplaceAsync(string key, T value, TimeSpan e /// An object implementing the interface. public IOperationResult Replace(string key, T value, ulong cas) { - var operation = new Replace(key, value, cas, null, _transcoder, GlobalTimeout.GetSeconds()) + var operation = new Replace(key, value, cas, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name }; @@ -3950,7 +3950,7 @@ public IOperationResult Replace(string key, T value, uint expiration) /// public IOperationResult Replace(string key, T value, uint expiration, TimeSpan timeout) { - var operation = new Replace(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Replace(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, BucketName = Name @@ -4044,7 +4044,7 @@ public IOperationResult Replace(string key, T value, ulong cas, uint expir /// public IOperationResult Replace(string key, T value, ulong cas, uint expiration, TimeSpan timeout) { - var operation = new Replace(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Replace(key, value, null, _transcoder, timeout.GetMilliseconds()) { Cas = cas, Expires = expiration, @@ -4072,7 +4072,7 @@ public IOperationResult Replace(string key, T value, ulong cas, uint expir public Task> ReplaceAsync(string key, T value, ulong cas, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Replace(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Replace(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, Cas = cas, @@ -4433,7 +4433,7 @@ public IOperationResult Replace(string key, T value, ulong cas, uint expir /// public IOperationResult Replace(string key, T value, ulong cas, uint expiration, ReplicateTo replicateTo, PersistTo persistTo, TimeSpan timeout) { - var operation = new Replace(key, value, cas, null, _transcoder, timeout.GetSeconds()) + var operation = new Replace(key, value, cas, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, BucketName = Name @@ -4465,7 +4465,7 @@ public Task> ReplaceAsync(string key, T value, ulong cas, { CheckDisposed(); var operation = new IO.Operations.Replace(key, value, null, _transcoder, - timeout.GetSeconds()) + timeout.GetMilliseconds()) { Expires = expiration, Cas = cas, @@ -4676,7 +4676,7 @@ public Task> ReplaceAsync(IDocument document, Replicate public Task> ReplaceAsync(string key, T value) { CheckDisposed(); - var operation = new Replace(key, value, null, _transcoder, GlobalTimeout.GetSeconds()) + var operation = new Replace(key, value, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name }; @@ -4895,7 +4895,7 @@ public IOperationResult Unlock(string key, ulong cas) /// public IOperationResult Unlock(string key, ulong cas, TimeSpan timeout) { - var unlock = new Unlock(key, null, _transcoder, timeout.GetSeconds()) + var unlock = new Unlock(key, null, _transcoder, timeout.GetMilliseconds()) { Cas = cas, BucketName = Name @@ -4969,7 +4969,7 @@ public Task[]> UpsertAsync(List> documents, R public IOperationResult Upsert(string key, T value) { CheckDisposed(); - var operation = new Set(key, value, null, _transcoder, GlobalTimeout.GetSeconds()) + var operation = new Set(key, value, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name }; @@ -5132,7 +5132,7 @@ public IOperationResult Upsert(string key, T value, ulong cas, uint expira public IOperationResult Upsert(string key, T value, ulong cas, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Set(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Set(key, value, null, _transcoder, timeout.GetMilliseconds()) { Cas = cas, Expires = expiration, @@ -5144,7 +5144,7 @@ public IOperationResult Upsert(string key, T value, ulong cas, uint expira public Task> UpsertAsync(string key, T value, ulong cas, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Set(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Set(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, Cas = cas, @@ -5376,7 +5376,7 @@ public IOperationResult Upsert(string key, T value, ReplicateTo replicateT public IOperationResult Upsert(string key, T value, ReplicateTo replicateTo, PersistTo persistTo, TimeSpan timeout) { CheckDisposed(); - var operation = new Set(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Set(key, value, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; @@ -5425,7 +5425,7 @@ public Task> UpsertAsync(string key, T value, ulong cas, CheckDisposed(); var operation = new IO.Operations.Set(key, value, null, _transcoder, - timeout.GetSeconds()) + timeout.GetMilliseconds()) { Expires = expiration, Cas = cas, @@ -5534,7 +5534,7 @@ public IOperationResult Upsert(string key, T value, ulong cas, uint expira PersistTo persistTo, TimeSpan timeout) { CheckDisposed(); - var operation = new Set(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Set(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, Cas = cas, @@ -5889,7 +5889,7 @@ public Task[]> UpsertAsync(List> documents, R public Task> UpsertAsync(string key, T value) { CheckDisposed(); - var operation = new Set(key, value, null, _transcoder, GlobalTimeout.GetSeconds()); + var operation = new Set(key, value, null, _transcoder, GlobalTimeout.GetMilliseconds()); return _requestExecuter.SendWithRetryAsync(operation); } @@ -6047,7 +6047,7 @@ public IOperationResult Upsert(string key, T value, uint expiration, Repli TimeSpan timeout) { CheckDisposed(); - var operation = new Set(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Set(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, BucketName = Name @@ -6346,34 +6346,34 @@ private SubDocSingularMutationBase OptimizeSingleMutation(MutateInBuilder< switch (spec.OpCode) { case OperationCode.SubArrayAddUnique: - return new SubArrayAddUnique(builder, builder.Key, null, _transcoder, GlobalTimeout.GetSeconds()) + return new SubArrayAddUnique(builder, builder.Key, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name, Expires = builder.Expiry.ToTtl()}; case OperationCode.SubArrayInsert: - return new SubArrayInsert(builder, builder.Key, null, _transcoder, GlobalTimeout.GetSeconds()) + return new SubArrayInsert(builder, builder.Key, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name, Expires = builder.Expiry.ToTtl()}; case OperationCode.SubArrayPushFirst: - return new SubArrayPushFirst(builder, builder.Key, null, _transcoder, GlobalTimeout.GetSeconds()) + return new SubArrayPushFirst(builder, builder.Key, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name, Expires = builder.Expiry.ToTtl()}; case OperationCode.SubArrayPushLast: - return new SubArrayPushLast(builder, builder.Key, null, _transcoder, GlobalTimeout.GetSeconds()) + return new SubArrayPushLast(builder, builder.Key, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name, Expires = builder.Expiry.ToTtl()}; case OperationCode.SubCounter: - return new SubCounter(builder, builder.Key, null, _transcoder, GlobalTimeout.GetSeconds()) + return new SubCounter(builder, builder.Key, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name, Expires = builder.Expiry.ToTtl()}; case OperationCode.SubDelete: - return new SubDocDelete(builder, builder.Key, null, _transcoder, GlobalTimeout.GetSeconds()) + return new SubDocDelete(builder, builder.Key, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name, Expires = builder.Expiry.ToTtl()}; case OperationCode.SubDictAdd: - return new SubDocDictAdd(builder, builder.Key, null, _transcoder, GlobalTimeout.GetSeconds()) + return new SubDocDictAdd(builder, builder.Key, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name, Expires = builder.Expiry.ToTtl()}; case OperationCode.SubDictUpsert: - return new SubDocDictUpsert(builder, builder.Key, null, _transcoder, GlobalTimeout.GetSeconds()) + return new SubDocDictUpsert(builder, builder.Key, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name, Expires = builder.Expiry.ToTtl()}; case OperationCode.SubReplace: - return new SubDocReplace(builder, builder.Key, null, _transcoder, GlobalTimeout.GetSeconds()) + return new SubDocReplace(builder, builder.Key, null, _transcoder, GlobalTimeout.GetMilliseconds()) { BucketName = Name, Expires = builder.Expiry.ToTtl()}; case OperationCode.Set: - return new SubDocUpsert(builder, null, _transcoder, GlobalTimeout.GetSeconds()); + return new SubDocUpsert(builder, null, _transcoder, GlobalTimeout.GetMilliseconds()); default: throw new NotSupportedException("Opcode is not supported for MutateInBuilder."); } @@ -6407,7 +6407,7 @@ public IDocumentFragment Invoke(IMutateInBuilder builder) } var timeout = builder.Timeout.HasValue ? builder.Timeout.Value: GlobalTimeout; - var multiMutate = new MultiMutation(builder.Key, theBuilder, null, _transcoder, timeout.GetSeconds()) + var multiMutate = new MultiMutation(builder.Key, theBuilder, null, _transcoder, timeout.GetMilliseconds()) { Expires = builder.Expiry.ToTtl() }; @@ -6431,7 +6431,7 @@ public async Task> InvokeAsync(IMutateInBuilder build } var timeout = builder.Timeout.HasValue ? builder.Timeout.Value : GlobalTimeout; - var multiMutate = new MultiMutation(builder.Key, theBuilder, null, _transcoder, timeout.GetSeconds()) + var multiMutate = new MultiMutation(builder.Key, theBuilder, null, _transcoder, timeout.GetMilliseconds()) { Expires = builder.Expiry.ToTtl() }; @@ -6446,9 +6446,9 @@ private SubDocSingularLookupBase OptimizeSingleLookup(LookupInBuilder b switch (spec.OpCode) { case OperationCode.SubGet: - return new SubGet(builder, builder.Key, null, _transcoder, timeout.GetSeconds()) { BucketName = Name }; + return new SubGet(builder, builder.Key, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; case OperationCode.SubExist: - return new SubExists(builder, builder.Key, null, _transcoder, timeout.GetSeconds()) { BucketName = Name }; + return new SubExists(builder, builder.Key, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; case OperationCode.SubGetCount: return new SubGetCount(builder, builder.Key, null, _transcoder, _operationLifespanTimeout) { BucketName = Name }; default: @@ -6474,7 +6474,7 @@ public IDocumentFragment Invoke(ILookupInBuilder builder) //this is a multi operation var timeout = builder.Timeout.HasValue ? builder.Timeout.Value : GlobalTimeout; - var multiLookup = new MultiLookup(builder.Key, theBuilder, null, _transcoder, timeout.GetSeconds()) { BucketName = Name }; + var multiLookup = new MultiLookup(builder.Key, theBuilder, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; return (DocumentFragment) _requestExecuter.SendWithRetry(multiLookup); } @@ -6495,7 +6495,7 @@ public async Task> InvokeAsync(ILookupInBuilder build } var timeout = builder.Timeout.HasValue ? builder.Timeout.Value : GlobalTimeout; - var multiMutate = new MultiLookup(builder.Key, theBuilder, null, _transcoder, timeout.GetSeconds()) { BucketName = Name }; + var multiMutate = new MultiLookup(builder.Key, theBuilder, null, _transcoder, timeout.GetMilliseconds()) { BucketName = Name }; return (DocumentFragment)await _requestExecuter.SendWithRetryAsync(multiMutate).ContinueOnAnyContext(); } diff --git a/Src/Couchbase/IO/Operations/SubDocument/SubDocSingularBase.cs b/Src/Couchbase/IO/Operations/SubDocument/SubDocSingularBase.cs index 8344ed6d7..c6072a427 100644 --- a/Src/Couchbase/IO/Operations/SubDocument/SubDocSingularBase.cs +++ b/Src/Couchbase/IO/Operations/SubDocument/SubDocSingularBase.cs @@ -41,7 +41,7 @@ private void SetTimeoutIfNotDefault(ISubDocBuilder builder) { if (builder.Timeout.HasValue) { - Timeout = builder.Timeout.Value.GetSeconds(); + Timeout = builder.Timeout.Value.GetMilliseconds(); } } diff --git a/Src/Couchbase/MemcachedBucket.cs b/Src/Couchbase/MemcachedBucket.cs index dec349fb0..392d4d49e 100644 --- a/Src/Couchbase/MemcachedBucket.cs +++ b/Src/Couchbase/MemcachedBucket.cs @@ -362,7 +362,7 @@ public IOperationResult Touch(string key, TimeSpan expiration) /// An with no value. public IOperationResult Touch(string key, TimeSpan expiration, TimeSpan timeout) { - var touch = new Touch(key, null, _transcoder, timeout.GetSeconds()) + var touch = new Touch(key, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration.ToTtl() }; @@ -391,7 +391,7 @@ public Task TouchAsync(string key, TimeSpan expiration) /// public Task TouchAsync(string key, TimeSpan expiration, TimeSpan timeout) { - var touch = new Touch(key, null, _transcoder, timeout.GetSeconds()) + var touch = new Touch(key, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration.ToTtl() }; @@ -515,7 +515,7 @@ public IOperationResult Upsert(string key, T value, uint expiration) /// public IOperationResult Upsert(string key, T value, uint expiration, TimeSpan timeout) { - var operation = new Set(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Set(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration }; @@ -570,7 +570,7 @@ public IOperationResult Upsert(string key, T value, TimeSpan expiration) /// public IOperationResult Upsert(string key, T value, TimeSpan expiration, TimeSpan timeout) { - var operation = new Set(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Set(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration.ToTtl() }; @@ -603,7 +603,7 @@ public Task> UpsertAsync(string key, T value, TimeSpan ex /// An object implementing the interface. public IOperationResult Upsert(string key, T value, ulong cas) { - var operation = new Set(key, value, null, _transcoder, GlobalTimeout.GetSeconds()) + var operation = new Set(key, value, null, _transcoder, GlobalTimeout.GetMilliseconds()) { Cas = cas }; @@ -642,7 +642,7 @@ public IOperationResult Upsert(string key, T value, ulong cas, uint expira /// public IOperationResult Upsert(string key, T value, ulong cas, uint expiration, TimeSpan timeout) { - var operation = new Set(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Set(key, value, null, _transcoder, timeout.GetMilliseconds()) { Cas = cas, Expires = expiration @@ -669,7 +669,7 @@ public IOperationResult Upsert(string key, T value, ulong cas, uint expira public Task> UpsertAsync(string key, T value, ulong cas, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Set(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Set(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, Cas = cas @@ -1265,7 +1265,7 @@ public Task> ReplaceAsync(string key, T value, TimeSpan e /// An object implementing the interface. public IOperationResult Replace(string key, T value, ulong cas) { - var operation = new Replace(key, value, cas, null, _transcoder, GlobalTimeout.GetSeconds()); + var operation = new Replace(key, value, cas, null, _transcoder, GlobalTimeout.GetMilliseconds()); return _requestExecuter.SendWithRetry(operation); } @@ -1295,7 +1295,7 @@ public Task> ReplaceAsync(IDocument document, Replicate /// An object implementing the interface. public IOperationResult Replace(string key, T value) { - var operation = new Replace(key, value, null, _transcoder, GlobalTimeout.GetSeconds()); + var operation = new Replace(key, value, null, _transcoder, GlobalTimeout.GetMilliseconds()); return _requestExecuter.SendWithRetry(operation); } @@ -1329,7 +1329,7 @@ public IOperationResult Replace(string key, T value, uint expiration) /// public IOperationResult Replace(string key, T value, uint expiration, TimeSpan timeout) { - var operation = new Replace(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Replace(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration }; @@ -1693,7 +1693,7 @@ public IOperationResult Replace(string key, T value, ulong cas, uint expir /// public IOperationResult Replace(string key, T value, ulong cas, uint expiration, TimeSpan timeout) { - var operation = new Replace(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Replace(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, Cas = cas @@ -1720,7 +1720,7 @@ public IOperationResult Replace(string key, T value, ulong cas, uint expir public Task> ReplaceAsync(string key, T value, ulong cas, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Replace(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Replace(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration, Cas = cas @@ -1900,7 +1900,7 @@ public Task> InsertAsync(IDocument document, ReplicateT /// An object implementing the interface. public IOperationResult Insert(string key, T value) { - var operation = new Add(key, value, null, _transcoder, GlobalTimeout.GetSeconds()); + var operation = new Add(key, value, null, _transcoder, GlobalTimeout.GetMilliseconds()); return _requestExecuter.SendWithRetry(operation); } @@ -1934,7 +1934,7 @@ public IOperationResult Insert(string key, T value, uint expiration) /// public IOperationResult Insert(string key, T value, uint expiration, TimeSpan timeout) { - var operation = new Add(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Add(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration }; @@ -1959,7 +1959,7 @@ public IOperationResult Insert(string key, T value, uint expiration, TimeS public Task> InsertAsync(string key, T value, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Add(key, value, null, _transcoder, timeout.GetSeconds()) + var operation = new Add(key, value, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration }; @@ -2296,7 +2296,7 @@ public IOperationResult Remove(string key) /// public IOperationResult Remove(string key, TimeSpan timeout) { - var operation = new Delete(key, null, _transcoder, timeout.GetSeconds()); + var operation = new Delete(key, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.SendWithRetry(operation); } @@ -2311,7 +2311,7 @@ public IOperationResult Remove(string key, TimeSpan timeout) public Task RemoveAsync(string key, TimeSpan timeout) { CheckDisposed(); - var operation = new Delete(key, null, _transcoder, timeout.GetSeconds()); + var operation = new Delete(key, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.SendWithRetryAsync(operation); } @@ -2337,7 +2337,7 @@ public IOperationResult Remove(string key, ulong cas) /// public IOperationResult Remove(string key, ulong cas, TimeSpan timeout) { - var operation = new Delete(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Delete(key, null, _transcoder, timeout.GetMilliseconds()) { Cas = cas }; @@ -2685,7 +2685,7 @@ public IOperationResult Get(string key) /// public IOperationResult Get(string key, TimeSpan timeout) { - var operation = new Get(key, null, _transcoder, timeout.GetSeconds()); + var operation = new Get(key, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.SendWithRetry(operation); } @@ -2712,7 +2712,7 @@ public IOperationResult GetAndTouch(string key, TimeSpan expiration) /// public IOperationResult GetAndTouch(string key, TimeSpan expiration, TimeSpan timeout) { - var operation = new GetT(key, null, _transcoder, timeout.GetSeconds()) + var operation = new GetT(key, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration.ToTtl() }; @@ -2742,7 +2742,7 @@ public Task> GetAndTouchAsync(string key, TimeSpan expira /// public Task> GetAndTouchAsync(string key, TimeSpan expiration, TimeSpan timeout) { - var operation = new GetT(key, null, _transcoder, timeout.GetSeconds()) + var operation = new GetT(key, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration.ToTtl() }; @@ -2799,7 +2799,7 @@ public Task> GetAndTouchDocumentAsync(string key, TimeSpan public Task> GetAsync(string key, TimeSpan timeout) { CheckDisposed(); - var operation = new Get(key, null, _transcoder, timeout.GetSeconds()); + var operation = new Get(key, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.SendWithRetryAsync(operation); } @@ -3293,7 +3293,7 @@ public IOperationResult Increment(string key, ulong delta, ulong initial, /// public IOperationResult Increment(string key, ulong delta, ulong initial, uint expiration, TimeSpan timeout) { - var operation = new Increment(key, initial, delta, null, _transcoder, timeout.GetSeconds()) + var operation = new Increment(key, initial, delta, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration }; @@ -3320,7 +3320,7 @@ public IOperationResult Increment(string key, ulong delta, ulong initial, public Task> IncrementAsync(string key, ulong delta, ulong initial, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Increment(key, initial, delta, null, _transcoder, timeout.GetSeconds()) + var operation = new Increment(key, initial, delta, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration }; @@ -3536,7 +3536,7 @@ public IOperationResult Decrement(string key, ulong delta, ulong initial, /// public IOperationResult Decrement(string key, ulong delta, ulong initial, uint expiration, TimeSpan timeout) { - var operation = new Decrement(key, initial, delta, null, _transcoder, timeout.GetSeconds()) + var operation = new Decrement(key, initial, delta, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration }; @@ -3563,7 +3563,7 @@ public IOperationResult Decrement(string key, ulong delta, ulong initial, public Task> DecrementAsync(string key, ulong delta, ulong initial, uint expiration, TimeSpan timeout) { CheckDisposed(); - var operation = new Decrement(key, initial, delta, null, _transcoder, timeout.GetSeconds()) + var operation = new Decrement(key, initial, delta, null, _transcoder, timeout.GetMilliseconds()) { Expires = expiration }; @@ -3646,7 +3646,7 @@ public IOperationResult Append(string key, string value) /// public IOperationResult Append(string key, string value, TimeSpan timeout) { - var operation = new Append(key, value, null, _transcoder, timeout.GetSeconds()); + var operation = new Append(key, value, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.SendWithRetry(operation); } @@ -3662,7 +3662,7 @@ public IOperationResult Append(string key, string value, TimeSpan timeou public Task> AppendAsync(string key, string value, TimeSpan timeout) { CheckDisposed(); - var operation = new Append(key, value, null, _transcoder, timeout.GetSeconds()); + var operation = new Append(key, value, null, _transcoder, timeout.GetMilliseconds()); var result = _requestExecuter.SendWithRetryAsync(operation); return result; } @@ -3689,7 +3689,7 @@ public IOperationResult Append(string key, byte[] value) /// public IOperationResult Append(string key, byte[] value, TimeSpan timeout) { - var operation = new Append(key, value, null, _transcoder, timeout.GetSeconds()); + var operation = new Append(key, value, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.SendWithRetry(operation); } @@ -3705,7 +3705,7 @@ public IOperationResult Append(string key, byte[] value, TimeSpan timeou public Task> AppendAsync(string key, byte[] value, TimeSpan timeout) { CheckDisposed(); - var operation = new Append(key, value, null, _transcoder, timeout.GetSeconds()); + var operation = new Append(key, value, null, _transcoder, timeout.GetMilliseconds()); var result = _requestExecuter.SendWithRetryAsync(operation); return result; } @@ -3732,7 +3732,7 @@ public IOperationResult Prepend(string key, string value) /// public IOperationResult Prepend(string key, string value, TimeSpan timeout) { - var operation = new Prepend(key, value, null, _transcoder, timeout.GetSeconds()); + var operation = new Prepend(key, value, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.SendWithRetry(operation); } @@ -3748,7 +3748,7 @@ public IOperationResult Prepend(string key, string value, TimeSpan timeo public Task> PrependAsync(string key, string value, TimeSpan timeout) { CheckDisposed(); - var operation = new Prepend(key, value, null, _transcoder, timeout.GetSeconds()); + var operation = new Prepend(key, value, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.SendWithRetryAsync(operation); } @@ -3774,7 +3774,7 @@ public IOperationResult Prepend(string key, byte[] value) /// public IOperationResult Prepend(string key, byte[] value, TimeSpan timeout) { - var operation = new Prepend(key, value, null, _transcoder, timeout.GetSeconds()); + var operation = new Prepend(key, value, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.SendWithRetry(operation); } @@ -3794,7 +3794,7 @@ public Task> GetAsync(string key) public Task> InsertAsync(string key, T value) { CheckDisposed(); - var operation = new Add(key, value, null, _transcoder, GlobalTimeout.GetSeconds()); + var operation = new Add(key, value, null, _transcoder, GlobalTimeout.GetMilliseconds()); return _requestExecuter.SendWithRetryAsync(operation); } @@ -3815,7 +3815,7 @@ public Task> QueryAsync(IViewQueryable query) public Task> PrependAsync(string key, byte[] value, TimeSpan timeout) { CheckDisposed(); - var operation = new Prepend(key, value, null, _transcoder, timeout.GetSeconds()); + var operation = new Prepend(key, value, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.SendWithRetryAsync(operation); } @@ -4299,7 +4299,7 @@ public IOperationResult Insert(string key, T value, ReplicateTo replicateT public Task RemoveAsync(IDocument document, TimeSpan timeout) { CheckDisposed(); - var operation = new Delete(document.Id, null, _transcoder, timeout.GetSeconds()); + var operation = new Delete(document.Id, null, _transcoder, timeout.GetMilliseconds()); return _requestExecuter.SendWithRetryAsync(operation); } @@ -4346,7 +4346,7 @@ public IOperationResult Remove(IDocument document, ReplicateTo replicateTo public Task RemoveAsync(string key, ulong cas, TimeSpan timeout) { CheckDisposed(); - var operation = new Delete(key, null, _transcoder, timeout.GetSeconds()) + var operation = new Delete(key, null, _transcoder, timeout.GetMilliseconds()) { Cas = cas }; @@ -4548,7 +4548,7 @@ public Task[]> UpsertAsync(List> documents, R public Task> UpsertAsync(string key, T value) { CheckDisposed(); - var operation = new Set(key, value, null, _transcoder, GlobalTimeout.GetSeconds()); + var operation = new Set(key, value, null, _transcoder, GlobalTimeout.GetMilliseconds()); return _requestExecuter.SendWithRetryAsync(operation); } @@ -4910,7 +4910,7 @@ public Task> ReplaceAsync(IDocument document, Replicate public Task> ReplaceAsync(string key, T value) { CheckDisposed(); - var operation = new Replace(key, value, null, _transcoder, GlobalTimeout.GetSeconds()); + var operation = new Replace(key, value, null, _transcoder, GlobalTimeout.GetMilliseconds()); return _requestExecuter.SendWithRetryAsync(operation); } diff --git a/Src/Couchbase/Utils/TimeSpanExtensions.cs b/Src/Couchbase/Utils/TimeSpanExtensions.cs index f4d8e64d3..15667a40c 100644 --- a/Src/Couchbase/Utils/TimeSpanExtensions.cs +++ b/Src/Couchbase/Utils/TimeSpanExtensions.cs @@ -50,14 +50,25 @@ public static uint ToTtl(this uint duration) return unixTimeStamp; } + /// + /// Retrieves the number of milliseconds expressed in a as an . + /// + /// The timespan. + /// An that is the total number of seconds in the . + public static uint GetMilliseconds(this TimeSpan timeSpan) + { + return (uint) timeSpan.TotalMilliseconds; + } + /// /// Retrieves the number of seconds expressed in a as an . /// /// The timespan. /// An that is the total number of seconds in the . + [Obsolete("Use TimespanExtensions.GetMilliseconds instead.")] public static uint GetSeconds(this TimeSpan timeSpan) { - return (uint) timeSpan.TotalSeconds; + return (uint)timeSpan.TotalSeconds; } private static readonly Regex DurationValueSuffixRegex = new Regex(@"([\d]+)\s?([^\d]+)?", RegexOptions.Compiled | RegexOptions.IgnoreCase); From 48451dea89b8620a5f87cd441aac1e353f696a0b Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Mon, 5 Apr 2021 12:15:42 -0700 Subject: [PATCH 65/70] NCBC-2849: Ensure FF Maps is only used after a NMVB Motivation ---------- Reworks the FFMAP logic if a NMVB is encountered. Previously the FFMAP was used if it existed, the error was NMVB or TransportError and the revision of the cluster map returned in the body of a NMVB matched the current cluster map revision. This fix changes the behavior to use the FFMAP if a NMVB is encountered. Modifications ------------- - Replace IKeyMapper.MapKey int parameter with boolean NMVB flag - Update all code using the MapKey method to pass in the flag - Remove TransportFailure as a retriable response - Add seam to allow the WasNmvb flag to be toggled for testing on operations - Update unit tests Result ------ The .NET SDK will use FFMAPS whenever a NMVB is returned and the cluster has a FFMAP. This aligns with Java behavior. Change-Id: I00d03943b63197734e313f06fcb035f89a9c4356 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/150411 Tested-by: Build Bot Reviewed-by: Reviewed-by: Michael Nitschinger --- .../Core/Buckets/OperationExecutorTests.cs | 6 +++--- .../CouchbaseBucket_Async_Tests.cs | 4 ++-- .../Buckets/CouchbaseRequestExecutorTests.cs | 8 ++++---- Src/Couchbase.UnitTests/OperationResultTests.cs | 2 +- Src/Couchbase/Core/Buckets/CallbackFactory.cs | 4 ++-- .../Core/Buckets/CouchbaseRequestExecuter.cs | 14 +++++++------- Src/Couchbase/Core/Buckets/KetamaKeyMapper.cs | 6 +++--- Src/Couchbase/Core/Buckets/VBucketKeyMapper.cs | 9 +++++---- Src/Couchbase/Core/IKeyMapper.cs | 6 +++--- Src/Couchbase/IO/Operations/IOperation.cs | 2 ++ Src/Couchbase/IO/Operations/OperationBase.cs | 17 +++++++++++++++++ Src/Couchbase/OperationResult.cs | 2 +- Src/couchbase-net-client.sln.DotSettings | 3 ++- 13 files changed, 52 insertions(+), 31 deletions(-) diff --git a/Src/Couchbase.Tests/Core/Buckets/OperationExecutorTests.cs b/Src/Couchbase.Tests/Core/Buckets/OperationExecutorTests.cs index d495c4fe5..9415c55c1 100644 --- a/Src/Couchbase.Tests/Core/Buckets/OperationExecutorTests.cs +++ b/Src/Couchbase.Tests/Core/Buckets/OperationExecutorTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Configuration; @@ -52,7 +52,7 @@ internal IClusterController GetBucketForKey(string key, out IConfigInfo configIn mockVBucket.Setup(x => x.LocatePrimary()).Returns(fakeServer); var mockKeyMapper = new Mock(); - mockKeyMapper.Setup(x => x.MapKey(key, It.IsAny())).Returns(mockVBucket.Object); + mockKeyMapper.Setup(x => x.MapKey(key, false)).Returns(mockVBucket.Object); var mockConfigInfo = new Mock(); mockConfigInfo.Setup(x => x.GetKeyMapper()).Returns(mockKeyMapper.Object); @@ -111,7 +111,7 @@ public void SetUp() vBucket.Setup(x => x.LocatePrimary()).Returns(server.Object); var keyMapper = new Mock(); - keyMapper.Setup(x => x.MapKey(It.IsAny(), It.IsAny())).Returns(vBucket.Object); + keyMapper.Setup(x => x.MapKey(It.IsAny(), false)).Returns(vBucket.Object); var configInfo = new Mock(); configInfo.Setup(x => x.GetKeyMapper()).Returns(keyMapper.Object); diff --git a/Src/Couchbase.Tests/CouchbaseBucket_Async_Tests.cs b/Src/Couchbase.Tests/CouchbaseBucket_Async_Tests.cs index 352ebb19b..9d98a7377 100644 --- a/Src/Couchbase.Tests/CouchbaseBucket_Async_Tests.cs +++ b/Src/Couchbase.Tests/CouchbaseBucket_Async_Tests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Configuration; using System.Net; @@ -43,7 +43,7 @@ public IBucket GetBucketForKey(string key) mockVBucket.Setup(x => x.LocatePrimary()).Returns(fakeServer); var mockKeyMapper = new Mock(); - mockKeyMapper.Setup(x => x.MapKey(key, It.IsAny())).Returns(mockVBucket.Object); + mockKeyMapper.Setup(x => x.MapKey(key, false)).Returns(mockVBucket.Object); var mockConfigInfo = new Mock(); mockConfigInfo.Setup(x => x.GetKeyMapper()).Returns(mockKeyMapper.Object); diff --git a/Src/Couchbase.UnitTests/Core/Buckets/CouchbaseRequestExecutorTests.cs b/Src/Couchbase.UnitTests/Core/Buckets/CouchbaseRequestExecutorTests.cs index a43617499..b5fb2b8e5 100644 --- a/Src/Couchbase.UnitTests/Core/Buckets/CouchbaseRequestExecutorTests.cs +++ b/Src/Couchbase.UnitTests/Core/Buckets/CouchbaseRequestExecutorTests.cs @@ -63,8 +63,8 @@ public void WhenForwardMapIsAvailable_AndRevisionIsSame_OperationUsesForwardMapV var pending = new ConcurrentDictionary(); var executor = new CouchbaseRequestExecuter(controller.Object, configInfo.Object, "default", pending); - var op = new Get("thekey", null, new DefaultTranscoder(), 100); - op.LastConfigRevisionTried = 2; + var op = new Get("thekey", null, new DefaultTranscoder(), 100) {FlagWasNmvb = true}; + var result = executor.SendWithRetry(op); Assert.AreEqual(op.VBucket.LocatePrimary().EndPoint, keyMapper.GetVBucketsForwards().First().Value.LocatePrimary().EndPoint); } @@ -470,7 +470,7 @@ public void SendWithDurability_does_not_dispatch_observe_when_replicate_and_pers mockVBucket.Setup(x => x.LocatePrimary()).Returns(mockServer.Object); var mockKeyMapper = new Mock(); - mockKeyMapper.Setup(x => x.MapKey(It.IsAny(), It.IsAny())).Returns(mockVBucket.Object); + mockKeyMapper.Setup(x => x.MapKey(It.IsAny(), false)).Returns(mockVBucket.Object); var mockConfigInfo = new Mock(); mockConfigInfo.Setup(x => x.IsDataCapable).Returns(true); @@ -506,7 +506,7 @@ public void SendWithDurability_T_does_not_dispatch_observe_when_replicate_and_pe mockVBucket.Setup(x => x.LocatePrimary()).Returns(mockServer.Object); var mockKeyMapper = new Mock(); - mockKeyMapper.Setup(x => x.MapKey(It.IsAny(), It.IsAny())).Returns(mockVBucket.Object); + mockKeyMapper.Setup(x => x.MapKey(It.IsAny(), false)).Returns(mockVBucket.Object); var mockConfigInfo = new Mock(); mockConfigInfo.Setup(x => x.IsDataCapable).Returns(true); diff --git a/Src/Couchbase.UnitTests/OperationResultTests.cs b/Src/Couchbase.UnitTests/OperationResultTests.cs index 638a397b9..4a41909ae 100644 --- a/Src/Couchbase.UnitTests/OperationResultTests.cs +++ b/Src/Couchbase.UnitTests/OperationResultTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Sockets; using Couchbase.IO; using Couchbase.IO.Operations; diff --git a/Src/Couchbase/Core/Buckets/CallbackFactory.cs b/Src/Couchbase/Core/Buckets/CallbackFactory.cs index cedb18485..429e892d0 100644 --- a/Src/Couchbase/Core/Buckets/CallbackFactory.cs +++ b/Src/Couchbase/Core/Buckets/CallbackFactory.cs @@ -310,7 +310,7 @@ public static Func CompletedFuncWithRetryForCouchbase pending.TryAdd(cloned.Opaque, cloned); var keyMapper = c.GetKeyMapper(); - var vBucket = (IVBucket)keyMapper.MapKey(cloned.Key, cloned.LastConfigRevisionTried); + var vBucket = (IVBucket)keyMapper.MapKey(cloned.Key, result.IsNmv()); cloned.LastConfigRevisionTried = vBucket.Rev; cloned.VBucket = vBucket; @@ -415,7 +415,7 @@ public static Func CompletedFuncWithRetryForCouchbase(IR pending.TryAdd(cloned.Opaque, cloned); var keyMapper = c.GetKeyMapper(); - var vBucket = (IVBucket)keyMapper.MapKey(cloned.Key, cloned.LastConfigRevisionTried); + var vBucket = (IVBucket)keyMapper.MapKey(cloned.Key, result.IsNmv()); cloned.LastConfigRevisionTried = vBucket.Rev; cloned.VBucket = vBucket; diff --git a/Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs b/Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs index ce37474e5..a260d635e 100644 --- a/Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs +++ b/Src/Couchbase/Core/Buckets/CouchbaseRequestExecuter.cs @@ -83,13 +83,13 @@ public bool CheckForConfigUpdates(IOperation operation) /// Gets the or node that a key has been mapped to. /// /// The key to get or set. - /// The rev # of the cluster map. + /// A boolean indicating that a NMVB was encountered. /// The VBucket the key belongs to. /// The that the key is mapped to. - public IServer GetServer(string key, uint revision, out IVBucket vBucket) + public IServer GetServer(string key, bool notMyVBucket, out IVBucket vBucket) { var keyMapper = ConfigInfo.GetKeyMapper(); - vBucket = (IVBucket) keyMapper.MapKey(key, revision); + vBucket = (IVBucket) keyMapper.MapKey(key, notMyVBucket); return vBucket.LocatePrimary(); } @@ -591,7 +591,7 @@ public override IOperationResult SendWithRetry(IOperation operation) do { IVBucket vBucket; - var server = GetServer(operation.Key, operation.LastConfigRevisionTried, out vBucket); + var server = GetServer(operation.Key, operation.WasNmvb(), out vBucket); if (server == null) { continue; @@ -672,7 +672,7 @@ public override IOperationResult SendWithRetry(IOperation operation) do { IVBucket vBucket; - var server = GetServer(operation.Key, operation.LastConfigRevisionTried, out vBucket); + var server = GetServer(operation.Key, operation.WasNmvb(), out vBucket); if (server == null) { continue; @@ -753,7 +753,7 @@ public override async Task> SendWithRetryAsync(IOperation } var keyMapper = ConfigInfo.GetKeyMapper(); - var vBucket = (IVBucket)keyMapper.MapKey(operation.Key, operation.LastConfigRevisionTried); + var vBucket = (IVBucket)keyMapper.MapKey(operation.Key, false); operation.VBucket = vBucket; operation.LastConfigRevisionTried = vBucket.Rev; @@ -810,7 +810,7 @@ public override async Task SendWithRetryAsync(IOperation opera } var keyMapper = ConfigInfo.GetKeyMapper(); - var vBucket = (IVBucket)keyMapper.MapKey(operation.Key, operation.LastConfigRevisionTried); + var vBucket = (IVBucket)keyMapper.MapKey(operation.Key, false); operation.VBucket = vBucket; operation.LastConfigRevisionTried = vBucket.Rev; diff --git a/Src/Couchbase/Core/Buckets/KetamaKeyMapper.cs b/Src/Couchbase/Core/Buckets/KetamaKeyMapper.cs index 3b885f92b..2e8083cea 100644 --- a/Src/Couchbase/Core/Buckets/KetamaKeyMapper.cs +++ b/Src/Couchbase/Core/Buckets/KetamaKeyMapper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -41,9 +41,9 @@ public IMappedNode MapKey(string key) /// Not Supported: This overload is only supported by Couchbase buckets. /// /// - /// + /// /// - public IMappedNode MapKey(string key, uint revision) + public IMappedNode MapKey(string key, bool notMyVBucket) { throw new NotSupportedException("This overload is only supported by Couchbase buckets."); } diff --git a/Src/Couchbase/Core/Buckets/VBucketKeyMapper.cs b/Src/Couchbase/Core/Buckets/VBucketKeyMapper.cs index e36e5d9ec..1313c7a33 100644 --- a/Src/Couchbase/Core/Buckets/VBucketKeyMapper.cs +++ b/Src/Couchbase/Core/Buckets/VBucketKeyMapper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Security.Cryptography; @@ -62,10 +62,11 @@ public IMappedNode MapKey(string key) return _vBuckets[index]; } - public IMappedNode MapKey(string key, uint revision) + public IMappedNode MapKey(string key, bool notMyVBucket) { - //its a retry - if (revision > 0 && revision == Rev && HasForwardMap()) + //If this is a retry because of a NMVB status and a new cluster map being returned being returned + //in the body of the Memcached packet. In which case the FF Map should be used if it exists. + if (notMyVBucket && HasForwardMap()) { //use the fast-forward map var index = GetIndex(key); diff --git a/Src/Couchbase/Core/IKeyMapper.cs b/Src/Couchbase/Core/IKeyMapper.cs index 728465f5c..9537c5741 100644 --- a/Src/Couchbase/Core/IKeyMapper.cs +++ b/Src/Couchbase/Core/IKeyMapper.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Couchbase.Configuration.Server.Serialization; namespace Couchbase.Core @@ -7,7 +7,7 @@ internal interface IKeyMapper { IMappedNode MapKey(string key); - IMappedNode MapKey(string key, uint revision); + IMappedNode MapKey(string key, bool notMyVbucket); uint Rev { get; set; } } @@ -34,4 +34,4 @@ internal interface IKeyMapper * * ************************************************************/ -#endregion \ No newline at end of file +#endregion diff --git a/Src/Couchbase/IO/Operations/IOperation.cs b/Src/Couchbase/IO/Operations/IOperation.cs index b12339d7b..62485a673 100644 --- a/Src/Couchbase/IO/Operations/IOperation.cs +++ b/Src/Couchbase/IO/Operations/IOperation.cs @@ -98,6 +98,8 @@ public interface IOperation int GetRetryTimeout(int defaultTimeout); void Validate(); + + bool WasNmvb(); } } diff --git a/Src/Couchbase/IO/Operations/OperationBase.cs b/Src/Couchbase/IO/Operations/OperationBase.cs index a81fbc484..060749e62 100644 --- a/Src/Couchbase/IO/Operations/OperationBase.cs +++ b/Src/Couchbase/IO/Operations/OperationBase.cs @@ -29,6 +29,7 @@ internal abstract class OperationBase : IOperation public const int DefaultRetries = 2; protected static MutationToken DefaultMutationToken = new MutationToken(null, -1, -1, -1); internal ErrorCode ErrorCode; + private bool _wasNmvb; protected OperationBase(string key, IVBucket vBucket, ITypeTranscoder transcoder, uint opaque, uint timeout) { @@ -54,6 +55,15 @@ protected OperationBase(string key, IVBucket vBucket, ITypeTranscoder transcoder { } + /// + /// Internal seam for unit testing + /// + internal bool FlagWasNmvb + { + get => _wasNmvb; + set => _wasNmvb = value; + } + public abstract OperationCode OperationCode { get; } public OperationHeader Header { get; set; } public OperationBody Body { get; set; } @@ -301,6 +311,8 @@ public virtual ResponseStatus GetResponseStatus() status = ResponseStatus.ClientFailure; } + _wasNmvb = status == ResponseStatus.VBucketBelongsToAnotherServer; + //For CB 5.X "LOCKED" is now returned when a key is locked with GetL (surprise, surprise) //However, the 2.X SDKs cannot return locked becuase it will not be backwards compatible, //so will break the bug that was fixed on the server and set the status back to TEMP_FAIL. @@ -537,6 +549,11 @@ public virtual void Validate() } } + public bool WasNmvb() + { + return _wasNmvb; + } + protected void TryReadMutationToken(byte[] buffer) { if (buffer.Length >= 40 && VBucket != null) diff --git a/Src/Couchbase/OperationResult.cs b/Src/Couchbase/OperationResult.cs index eaa23ea1a..3e4373871 100644 --- a/Src/Couchbase/OperationResult.cs +++ b/Src/Couchbase/OperationResult.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Sockets; using Couchbase.Core.Buckets; using Couchbase.IO; diff --git a/Src/couchbase-net-client.sln.DotSettings b/Src/couchbase-net-client.sln.DotSettings index 985c08e14..1f718d5a7 100644 --- a/Src/couchbase-net-client.sln.DotSettings +++ b/Src/couchbase-net-client.sln.DotSettings @@ -1,2 +1,3 @@  - True \ No newline at end of file + True + True \ No newline at end of file From d59ffcc70822427dec550803068f0ce7434ee26d Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Thu, 6 May 2021 15:26:42 -0700 Subject: [PATCH 66/70] NCBC-2872: NullReferenceException in replica reads on TLS/SSL Motivation ---------- If a replica read fails a NullReferenceException will be generated in the retry loop. This commit fixes the issue. Modifications ------------- - Map Endpoint in SslConnection to SocketAsyncState.Endpoint as it is called in the retry loop and generates a NRE. Result ------ NRE should not be encountered if a replica does not exist for a key. Change-Id: Iae008e7aad840f646f597df077ecb49b5a98b588 Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/153052 Tested-by: Build Bot Reviewed-by: --- Src/Couchbase/IO/SslConnection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Src/Couchbase/IO/SslConnection.cs b/Src/Couchbase/IO/SslConnection.cs index 87a812974..e7df62b30 100644 --- a/Src/Couchbase/IO/SslConnection.cs +++ b/Src/Couchbase/IO/SslConnection.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Runtime.ExceptionServices; @@ -139,6 +140,7 @@ public override async Task SendAsync(byte[] request, Func Date: Thu, 6 May 2021 16:12:08 -0700 Subject: [PATCH 67/70] NCBC-2873: Sporadic ArgumentOutOfRange in SharedConnectionPool.cs Motivation: Sporadic test failures throwing ArgumentOutOfRange show a thread safety bug in SharedConnectionPool. Modifications: * Judiciously lock read access to the connections list, not just write access. * Make an intelligent guess at an initial capacity based on configuration.MinSize and MaxSize. Change-Id: Icecad47a624a2f834a719c81c58e7102eeb1097c Reviewed-on: http://review.couchbase.org/c/couchbase-net-client/+/153061 Tested-by: Build Bot Reviewed-by: Jeffry Morris --- Src/Couchbase/IO/SharedConnectionPool.cs | 37 ++++++++++++++++++------ 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/Src/Couchbase/IO/SharedConnectionPool.cs b/Src/Couchbase/IO/SharedConnectionPool.cs index ad5623f43..2def1e23e 100644 --- a/Src/Couchbase/IO/SharedConnectionPool.cs +++ b/Src/Couchbase/IO/SharedConnectionPool.cs @@ -11,10 +11,10 @@ namespace Couchbase.IO public class SharedConnectionPool : ConnectionPoolBase where T : class, IConnection { private static readonly ILog Log = LogManager.GetLogger>(); - readonly List _connections = new List(); + private readonly List _connections; private volatile int _currentIndex; private readonly Guid _identity = Guid.NewGuid(); - private readonly object _lockObj = new object(); + private readonly object _connectionsLock = new object(); private bool _disposed = false; /// @@ -23,7 +23,7 @@ public class SharedConnectionPool : ConnectionPoolBase where T : class, IC /// The configuration. /// The remote endpoint or server node to connect to. public SharedConnectionPool(PoolConfiguration configuration, IPEndPoint endPoint) - : base(configuration, endPoint, DefaultConnectionFactory.GetGeneric(), new DefaultConverter()) + : this(configuration, endPoint, DefaultConnectionFactory.GetGeneric(), new DefaultConverter()) { } @@ -38,11 +38,24 @@ internal SharedConnectionPool(PoolConfiguration configuration, IPEndPoint endPoi Func, IByteConverter, BufferAllocator, T> factory, IByteConverter converter) : base(configuration, endPoint, factory, converter) { + // default List capacity is 4. Let's guestimate a better one to avoid unnecessary resize allocations. + var initialPoolCapacity = Math.Max(configuration.MinSize * 2, 10); + initialPoolCapacity = Math.Min(configuration.MaxSize, initialPoolCapacity); + _connections = new List(initialPoolCapacity); } public override IEnumerable Connections { - get { return _connections; } + get + { + IEnumerable result = null; + lock (_connectionsLock) + { + result = _connections.ToArray(); + } + + return result; + } set { throw new NotSupportedException(); } } @@ -50,6 +63,7 @@ internal int GetIndex() { //we don't care necessarily about thread safety as long as //index is within the allowed range based off the size of the pool. + // do not lock here, as it is called in sections that are already locked, and would deadlock. var index = _currentIndex % _connections.Count; if (_currentIndex > _connections.Count) { @@ -67,7 +81,12 @@ public override IConnection Acquire() // ReSharper disable once InconsistentlySynchronizedField if (_connections.Count >= Configuration.MaxSize) { - var connection = _connections.ElementAtOrDefault(GetIndex()); + IConnection connection; + lock (_connectionsLock) + { + connection = _connections.ElementAtOrDefault(GetIndex()); + } + try { if (connection == null) @@ -90,7 +109,7 @@ public override IConnection Acquire() throw; } } - lock (_lockObj) + lock (_connectionsLock) { var connection = CreateAndAuthConnection(); _connections.Add(connection); @@ -128,7 +147,7 @@ public override void Release(T connection) if (connection.IsDead) { - lock (_lockObj) + lock (_connectionsLock) { connection.Dispose(); Owner?.CheckOnline(connection.IsDead); @@ -141,7 +160,7 @@ public override void Initialize() { try { - lock (_lockObj) + lock (_connectionsLock) { var connectionsToCreate = Configuration.MaxSize - _connections.Count; for (var i = 0; i < connectionsToCreate; i++) @@ -176,7 +195,7 @@ public override void Initialize() public override void Dispose() { - lock (_lockObj) + lock (_connectionsLock) { if (_disposed) return; _disposed = true; From 5a33ff12076ab5ec6be99ed454816652a6c0f1b7 Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Tue, 9 Nov 2021 17:44:58 -0800 Subject: [PATCH 68/70] NCBC-2997: Exists return incorrect result when framing xtras present Motivation ---------- This fixes a bug where in newer versions of the server use frameing extras for observe and the code did not handle this correctly. Modifications ------------- Use Header.BodyOffset which dynamically determines the position to read as opposed to a fixed constant. Change-Id: Ia655a870d37bc2f650557e229181f571e1e6f663 Reviewed-on: https://review.couchbase.org/c/couchbase-net-client/+/165437 Tested-by: Build Bot Reviewed-by: Richard Ponton --- .../Couchbase.UnitTests.csproj | 9 +- .../IO/Operations/ExistsTests.cs | 115 ++++++++++++++++++ Src/Couchbase/IO/Operations/Observe.cs | 10 +- 3 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 Src/Couchbase.UnitTests/IO/Operations/ExistsTests.cs diff --git a/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj b/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj index afea0a2b2..b97e4dfba 100644 --- a/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj +++ b/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj @@ -1,7 +1,7 @@ - netcoreapp1.1;netcoreapp2.0 + netcoreapp1.1; Couchbase.UnitTests Couchbase.UnitTests 2.0.1 @@ -22,11 +22,12 @@ $(DefineConstants);NETSTANDARD;NETCORE11 $(PackageTargetFallback);dnxcore50 - + - + diff --git a/Src/Couchbase.UnitTests/IO/Operations/ExistsTests.cs b/Src/Couchbase.UnitTests/IO/Operations/ExistsTests.cs new file mode 100644 index 000000000..705137373 --- /dev/null +++ b/Src/Couchbase.UnitTests/IO/Operations/ExistsTests.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Couchbase.Core; +using Couchbase.Core.Transcoders; +using Couchbase.IO.Converters; +using Couchbase.IO.Operations; +using NUnit.Framework; + +namespace Couchbase.UnitTests.IO.Operations +{ + [TestFixture] + public class ExistsTests + { + public static IByteConverter Converter = new DefaultConverter(); + + [Test] + public void Exists_KeyNotFound_No_FramingExtras() + { + var buffer = new byte[] + { + 129, 146, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 9, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 118, 0, 12, 68, 79, 69, 83, 78, + 79, 84, 69, 88, 73, 83, 84, 128, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + var header = new OperationHeader + { + BodyLength = 25, + FramingExtrasLength = 0, + }; + var observe = new Observe("DOESNOTEXIST", new VBucket(null, 886, 1, null, 0, null, ""), + new DefaultTranscoder(), 0); + + observe.Read(buffer, header); + + var result = observe.GetResultWithValue(); + Assert.AreEqual(KeyState.NotFound, result.Value.KeyState); + } + + [Test] + public void Exists_KeyNotFound_With_FramingExtras() + { + var buffer = new byte[] + { + 24, 146, 3, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, + 9, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 14, 3, 118, 0, 12, + 68, 79, 69, 83, 78, 79, 84, 69, 88, 73, 83, 84, 128, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + + var header = new OperationHeader + { + BodyLength = 25, + FramingExtrasLength = 3, + }; + var observe = new Observe("DOESNOTEXIST", new VBucket(null, 886, 1, null, 0, null, ""), + new DefaultTranscoder(), 0); + + observe.Read(buffer, header); + + var result = observe.GetResultWithValue(); + Assert.AreEqual(KeyState.NotFound, result.Value.KeyState); + } + + [Test] + public void Exists_KeyFound_No_FramingExtras() + { + var buffer = new byte[] + { + 129, 146, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, + 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 1, 105, 0, 10, 97, + 105, 114, 108, 105, 110, 101, 95, 49, 48, 1, 22, + 181, 170, 118, 136, 119, 0, 0, + }; + + var header = new OperationHeader + { + BodyLength = 25, + FramingExtrasLength = 0, + }; + var observe = new Observe("airline_10", new VBucket(null, 886, 1, null, 0, null, ""), + new DefaultTranscoder(), 0); + + observe.Read(buffer, header); + + var result = observe.GetResultWithValue(); + Assert.AreEqual(KeyState.FoundPersisted, result.Value.KeyState); + } + + [Test] + public void Exists_KeyFound_With_FramingExtras() + { + var buffer = new byte[] + { + 24,146, 3, 0, 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 9, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 16, 1, 105, 0, 10, 97, 105, 114, 108, 105, 110, 101, + 95, 49, 48, 1, 22, 178, 174, 90, 98, 72, 0, 0 + }; + + var header = new OperationHeader + { + BodyLength = 25, + FramingExtrasLength = 3, + }; + var observe = new Observe("airline_10", new VBucket(null, 886, 1, null, 0, null, ""), + new DefaultTranscoder(), 0); + + observe.Read(buffer, header); + + var result = observe.GetResultWithValue(); + Assert.AreEqual(KeyState.FoundPersisted, result.Value.KeyState); + } + } +} diff --git a/Src/Couchbase/IO/Operations/Observe.cs b/Src/Couchbase/IO/Operations/Observe.cs index d1bea030b..76719d3e2 100644 --- a/Src/Couchbase/IO/Operations/Observe.cs +++ b/Src/Couchbase/IO/Operations/Observe.cs @@ -45,17 +45,17 @@ public override ObserveState GetValue() try { var buffer = Data.ToArray(); - var keylength = Converter.ToInt16(buffer, 26); + var keylength = Converter.ToInt16(buffer, Header.BodyOffset + 2); return new ObserveState { PersistStat = Converter.ToUInt32(buffer, 16), ReplState = Converter.ToUInt32(buffer, 20), - VBucket = Converter.ToInt16(buffer, 24), + VBucket = Converter.ToInt16(buffer, Header.BodyOffset), KeyLength = keylength, - Key = Converter.ToString(buffer, 28, keylength), - KeyState = (KeyState) Converter.ToByte(buffer, 28 + keylength), - Cas = Converter.ToUInt64(buffer, 28 + keylength + 1) + Key = Converter.ToString(buffer, Header.BodyOffset + 4, keylength), + KeyState = (KeyState) Converter.ToByte(buffer, Header.BodyOffset + keylength + 4), + Cas = Converter.ToUInt64(buffer, Header.BodyOffset + keylength + 5) }; } catch (Exception e) From 40051fddb0fc855559da85d338c294d44d91123b Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Wed, 19 Jan 2022 15:32:45 -0800 Subject: [PATCH 69/70] NCBC-3093: ClientConfiguration.NetworkType is not correctly assigned to the bucket config Motivation ---------- Fixes a bug where the ClientConfiguration.NetworkType setting is ignored if no BucketConfiguration is defined. Modifications ------------- - Fix network settings bug - Remove Moxy port exclusion - Update test core version to 3.1 Change-Id: I00bca6275a1fb1f833319faa8996fb3ee68704a6 Reviewed-on: https://review.couchbase.org/c/couchbase-net-client/+/169082 Reviewed-by: Richard Ponton Tested-by: Build Bot --- .../Couchbase.IntegrationTests.csproj | 14 +++---- .../Couchbase.Tests.Management.csproj | 4 +- Src/Couchbase.Tests/Couchbase.Tests.csproj | 4 +- .../Couchbase.UnitTests.csproj | 11 ++---- .../N1Ql/QueryRequestTests.cs | 6 +-- .../Client/ClientConfiguration.cs | 6 --- .../Server/Providers/ConfigProviderBase.cs | 37 +++++-------------- Src/Couchbase/N1QL/QueryRequest.cs | 2 +- 8 files changed, 25 insertions(+), 59 deletions(-) diff --git a/Src/Couchbase.IntegrationTests/Couchbase.IntegrationTests.csproj b/Src/Couchbase.IntegrationTests/Couchbase.IntegrationTests.csproj index 4f24c6851..fcd97d105 100644 --- a/Src/Couchbase.IntegrationTests/Couchbase.IntegrationTests.csproj +++ b/Src/Couchbase.IntegrationTests/Couchbase.IntegrationTests.csproj @@ -1,7 +1,7 @@ - + - netcoreapp1.1;netcoreapp2.0 + netcoreapp3.1; Couchbase.IntegrationTests Couchbase.IntegrationTests 2.0.1 @@ -18,15 +18,11 @@ $(DefineConstants);NET452 - - $(DefineConstants);NETSTANDARD;NETCORE11 - $(PackageTargetFallback);dnxcore50 - - - $(DefineConstants);NETSTANDARD;NETCORE20 + + $(DefineConstants);NETSTANDARD;NETCORE31 - + diff --git a/Src/Couchbase.Tests.Management/Couchbase.Tests.Management.csproj b/Src/Couchbase.Tests.Management/Couchbase.Tests.Management.csproj index af1b7634b..16c96d69e 100644 --- a/Src/Couchbase.Tests.Management/Couchbase.Tests.Management.csproj +++ b/Src/Couchbase.Tests.Management/Couchbase.Tests.Management.csproj @@ -1,7 +1,7 @@ - netcoreapp2.0 + netcoreapp3.1 Couchbase.Tests.Management Couchbase.Tests.Management 2.0.1 @@ -43,7 +43,7 @@ - + diff --git a/Src/Couchbase.Tests/Couchbase.Tests.csproj b/Src/Couchbase.Tests/Couchbase.Tests.csproj index a868915ff..ce83bf11f 100644 --- a/Src/Couchbase.Tests/Couchbase.Tests.csproj +++ b/Src/Couchbase.Tests/Couchbase.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.0 + netcoreapp3.1 Couchbase.Tests Couchbase.Tests 2.0.1 @@ -43,7 +43,7 @@ - + diff --git a/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj b/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj index b97e4dfba..7307e3d98 100644 --- a/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj +++ b/Src/Couchbase.UnitTests/Couchbase.UnitTests.csproj @@ -1,7 +1,7 @@ - netcoreapp1.1; + netcoreapp3.1; Couchbase.UnitTests Couchbase.UnitTests 2.0.1 @@ -18,14 +18,9 @@ $(DefineConstants);NET452 - - $(DefineConstants);NETSTANDARD;NETCORE11 - $(PackageTargetFallback);dnxcore50 + + $(DefineConstants);NETSTANDARD;NETCORE31 - diff --git a/Src/Couchbase.UnitTests/N1Ql/QueryRequestTests.cs b/Src/Couchbase.UnitTests/N1Ql/QueryRequestTests.cs index 7d52b3681..d4b50a980 100644 --- a/Src/Couchbase.UnitTests/N1Ql/QueryRequestTests.cs +++ b/Src/Couchbase.UnitTests/N1Ql/QueryRequestTests.cs @@ -168,7 +168,7 @@ public void When_Username_Is_Empty_AddCredentials_Throws_AOOE() Statement("SELECT * FROM authenticated"). AddCredentials("", "secret", false)); - Assert.That(ex.Message, Is.EqualTo("username cannot be null, empty or whitespace.")); + Assert.That(ex.Message, Is.EqualTo("username cannot be null, empty or whitespace. (Parameter 'username')")); } [Test] @@ -179,7 +179,7 @@ public void When_Username_Is_Whitespace_AddCredentials_Throws_AOOE() Statement("SELECT * FROM authenticated"). AddCredentials(" ", "secret", false)); - Assert.That(ex.Message, Is.EqualTo($"username cannot be null, empty or whitespace.{Environment.NewLine}Parameter name: ")); + Assert.That(ex.Message, Is.EqualTo($"username cannot be null, empty or whitespace. (Parameter 'username')")); } [Test] @@ -190,7 +190,7 @@ public void When_Username_Is_Null_AddCredentials_Throws_AOOE() Statement("SELECT * FROM authenticated"). AddCredentials(null, "secret", false)); - Assert.That(ex.Message, Is.EqualTo("username cannot be null, empty or whitespace.")); + Assert.That(ex.Message, Is.EqualTo("username cannot be null, empty or whitespace. (Parameter 'username')")); } private IQueryRequest CreateFullQueryRequest() diff --git a/Src/Couchbase/Configuration/Client/ClientConfiguration.cs b/Src/Couchbase/Configuration/Client/ClientConfiguration.cs index 3d48d0cc4..0773a3dac 100644 --- a/Src/Couchbase/Configuration/Client/ClientConfiguration.cs +++ b/Src/Couchbase/Configuration/Client/ClientConfiguration.cs @@ -1166,12 +1166,6 @@ internal void Initialize() { bucketConfiguration.Servers.AddRange(Servers.Select(x => x).ToList()); } - if (bucketConfiguration.Port == (int)DefaultPorts.Proxy) - { - var message = string.Format("Proxy port {0} is not supported by the .NET client.", - bucketConfiguration.Port); - throw new NotSupportedException(message); - } if (bucketConfiguration.UseSsl) { bucketConfiguration.PoolConfiguration.UseSsl = true; diff --git a/Src/Couchbase/Configuration/Server/Providers/ConfigProviderBase.cs b/Src/Couchbase/Configuration/Server/Providers/ConfigProviderBase.cs index b6e9682d9..2b12edaab 100644 --- a/Src/Couchbase/Configuration/Server/Providers/ConfigProviderBase.cs +++ b/Src/Couchbase/Configuration/Server/Providers/ConfigProviderBase.cs @@ -105,35 +105,16 @@ protected virtual BucketConfiguration GetOrCreateConfiguration(string bucketName return bucketConfiguration; } - // need to create new config - // can we copy settings from another bucket's config? - if (ClientConfig.BucketConfigs.Any()) + // create new config using client configuration settings + bucketConfiguration = new BucketConfiguration { - // use settings from existing config - var defaultConfig = ClientConfig.BucketConfigs.First().Value; - bucketConfiguration = new BucketConfiguration - { - BucketName = bucketName, - PoolConfiguration = defaultConfig.PoolConfiguration, - Servers = defaultConfig.Servers, - Port = defaultConfig.Port, - Username = defaultConfig.Username, - Password = string.Empty, - UseSsl = defaultConfig.UseSsl - }; - } - else - { - // create new config using client configuration settings - bucketConfiguration = new BucketConfiguration - { - BucketName = bucketName, - PoolConfiguration = ClientConfig.PoolConfiguration, - Servers = ClientConfig.Servers, - UseSsl = ClientConfig.UseSsl, - Port = ClientConfig.DirectPort - }; - } + BucketName = bucketName, + PoolConfiguration = ClientConfig.PoolConfiguration, + Servers = ClientConfig.Servers, + UseSsl = ClientConfig.UseSsl, + Port = ClientConfig.DirectPort, + NetworkType = ClientConfig.NetworkType + }; SetNetworkType(bucketConfiguration.NetworkType); diff --git a/Src/Couchbase/N1QL/QueryRequest.cs b/Src/Couchbase/N1QL/QueryRequest.cs index d32e879fb..f5332a5f8 100644 --- a/Src/Couchbase/N1QL/QueryRequest.cs +++ b/Src/Couchbase/N1QL/QueryRequest.cs @@ -570,7 +570,7 @@ public IQueryRequest AddCredentials(string username, string password, bool isAdm if (string.IsNullOrWhiteSpace(username)) { const string usernameParameter = "username"; - throw new ArgumentOutOfRangeException(username, ExceptionUtil.GetMessage(ExceptionUtil.ParameterCannotBeNullOrEmptyFormat, usernameParameter)); + throw new ArgumentOutOfRangeException(usernameParameter, ExceptionUtil.GetMessage(ExceptionUtil.ParameterCannotBeNullOrEmptyFormat, usernameParameter)); } if (isAdmin && !username.StartsWith("admin:")) { From a401f7f8a71ba4f2bb3d9808a7af2dc530ec80a7 Mon Sep 17 00:00:00 2001 From: Jeff Morris Date: Mon, 24 Jan 2022 12:19:05 -0800 Subject: [PATCH 70/70] NCBC-3098: NMVB when using Alternate Addresses Motivation ---------- Fixes a bug where the port for an Alternative Address is swapped with the NodeExt port. This leads to failed node lookup and subsequent NMVB being generated when the SDK forces a new config from the server. Change-Id: I4518f26aea529eeb23519cbec008b11499165275 Reviewed-on: https://review.couchbase.org/c/couchbase-net-client/+/169364 Tested-by: Build Bot Reviewed-by: Richard Ponton --- .../Configuration/Server/Serialization/BucketConfig.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Src/Couchbase/Configuration/Server/Serialization/BucketConfig.cs b/Src/Couchbase/Configuration/Server/Serialization/BucketConfig.cs index a563b0e24..99d7f9115 100644 --- a/Src/Couchbase/Configuration/Server/Serialization/BucketConfig.cs +++ b/Src/Couchbase/Configuration/Server/Serialization/BucketConfig.cs @@ -225,7 +225,8 @@ public void ResolveHostName() if (nodeExt != null && nodeExt.HasAlternateAddress && nodeExt.AlternateAddresses.HasExternalAddress) { //The SSL port is resolved later - VBucketServerMap.ServerList[i] = nodeExt.AlternateAddresses.External.Hostname + ":" + nodeExt.Services.KV; + VBucketServerMap.ServerList[i] = nodeExt.AlternateAddresses.External.Hostname + ":" + + nodeExt.AlternateAddresses.External.Ports.KV; } }