diff --git a/CorrugatedIron.Tests.Live/GeneralIntegrationTests.cs b/CorrugatedIron.Tests.Live/GeneralIntegrationTests.cs index 1025c36b..41ec61ca 100644 --- a/CorrugatedIron.Tests.Live/GeneralIntegrationTests.cs +++ b/CorrugatedIron.Tests.Live/GeneralIntegrationTests.cs @@ -1,631 +1,633 @@ -// Copyright (c) 2011 - OJ Reeves & Jeremiah Peschka -// -// This file is provided to you under the Apache License, -// Version 2.0 (the "License"); you may not use this file -// except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -using System.Collections.Generic; -using CorrugatedIron.Models; -using CorrugatedIron.Models.MapReduce; -using CorrugatedIron.Tests.Extensions; -using CorrugatedIron.Tests.Live.LiveRiakConnectionTests; -using CorrugatedIron.Util; -using NUnit.Framework; -using System; -using System.Linq; -using System.Threading; - -namespace CorrugatedIron.Tests.Live.GeneralIntegrationTests -{ - [TestFixture] - public class WhenTalkingToRiak : LiveRiakConnectionTestBase - { - [Test] - public void ServerInfoIsSuccessfullyExtracted() - { - var result = Client.GetServerInfo(); - result.IsSuccess.ShouldBeTrue(result.ErrorMessage); - } - - [Test] - public void ServerInfoIsSuccessfullyExtractedAsynchronously() - { - var result = Client.Async.GetServerInfo().Result; - result.IsSuccess.ShouldBeTrue(result.ErrorMessage); - } - - [Test] - public void PingRequstResultsInPingResponse() - { - var result = Client.Ping(); - result.IsSuccess.ShouldBeTrue(result.ErrorMessage); - } - - [Test] - public void ReadingMissingValueDoesntBreak() - { - var readResult = Client.Get("nobucket", "novalue"); - readResult.IsSuccess.ShouldBeFalse(readResult.ErrorMessage); - readResult.ResultCode.ShouldEqual(ResultCode.NotFound); - } - - [Test] - public void GetsWithBucketAndKeyReturnObjectsThatAreMarkedAsNotChanged() - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - var writeResult = Client.Put(doc); - writeResult.IsSuccess.ShouldBeTrue(); - writeResult.Value.ShouldNotBeNull(); - - var readResult = Client.Get(TestBucket, TestKey); - readResult.IsSuccess.ShouldBeTrue(); - readResult.Value.ShouldNotBeNull(); - readResult.Value.HasChanged.ShouldBeFalse(); - } - - [Test] - public void GetWithInvalidBucketReturnsInvalidRequest() - { - var getResult = Client.Get("", "key"); - getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); - getResult = Client.Get("foo/bar", "key"); - getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); - getResult = Client.Get(" ", "key"); - getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); - getResult = Client.Get(null, "key"); - getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); - } - - [Test] - public void GetWithInvalidKeyReturnsInvalidRequest() - { - var getResult = Client.Get("bucket", ""); - getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); - getResult = Client.Get("bucket", "foo/bar"); - getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); - getResult = Client.Get("bucket", " "); - getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); - getResult = Client.Get("bucket", null); - getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); - } - - [Test] - public void MultiGetWithValidAndInvalidBucketsBehavesCorrectly() - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - var writeResult = Client.Put(doc); - - writeResult.IsSuccess.ShouldBeTrue(); - writeResult.Value.ShouldNotBeNull(); - - var getResults = Client.Get(new List - { - new RiakObjectId(null, "key"), - new RiakObjectId("", "key"), - new RiakObjectId(" ", "key"), - new RiakObjectId("foo/bar", "key"), - new RiakObjectId(TestBucket, TestKey) - }).ToList(); - - getResults.Count(r => r.IsSuccess).ShouldEqual(1); - } - - [Test] - public void MultiPutWithValidAndInvalidBucketsBehavesCorrectly() - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - var writeResult = Client.Put(doc); - - writeResult.IsSuccess.ShouldBeTrue(); - writeResult.Value.ShouldNotBeNull(); - - var putResults = Client.Put(new List - { - new RiakObject(null, "key", TestJson, RiakConstants.ContentTypes.ApplicationJson), - new RiakObject("", "key", TestJson, RiakConstants.ContentTypes.ApplicationJson), - new RiakObject(" ", "key", TestJson, RiakConstants.ContentTypes.ApplicationJson), - new RiakObject("foo/bar", "key", TestJson, RiakConstants.ContentTypes.ApplicationJson), - new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson) - }).ToList(); - - putResults.Count(r => r.IsSuccess).ShouldEqual(1); - } - - [Test] - public void MultiDeleteWithValidAndInvalidBucketsBehavesCorrectly() - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - var writeResult = Client.Put(doc); - - writeResult.IsSuccess.ShouldBeTrue(); - writeResult.Value.ShouldNotBeNull(); - - var deleteResults = Client.Delete(new List - { - new RiakObjectId(null, "key"), - new RiakObjectId("", "key"), - new RiakObjectId(" ", "key"), - new RiakObjectId("foo/bar", "key"), - new RiakObjectId(TestBucket, TestKey) - }).ToList(); - - deleteResults.Count(r => r.IsSuccess).ShouldEqual(1); - var deletedItemGetResult = Client.Get(TestBucket, TestKey); - deletedItemGetResult.ResultCode.ShouldEqual(ResultCode.NotFound); - } - - [Test] - public void GetsWithBucketAndKeyReturnObjects() - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - var writeResult = Client.Put(doc); - - writeResult.IsSuccess.ShouldBeTrue(); - writeResult.Value.ShouldNotBeNull(); - - var readResult = Client.Get(TestBucket, TestKey); - readResult.IsSuccess.ShouldBeTrue(); - readResult.Value.ShouldNotBeNull(); - - var otherDoc = readResult.Value; - otherDoc.Bucket.ShouldEqual(TestBucket); - otherDoc.Bucket.ShouldEqual(doc.Bucket); - otherDoc.Key.ShouldEqual(TestKey); - otherDoc.Key.ShouldEqual(doc.Key); - } - - [Test] - public void GetsWithRiakObjectIdReturnObjects() - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - var writeResult = Client.Put(doc); - - writeResult.IsSuccess.ShouldBeTrue(); - writeResult.Value.ShouldNotBeNull(); - - var riakObjectId = new RiakObjectId(TestBucket, TestKey); - var readResult = Client.Get(riakObjectId); - - var otherDoc = readResult.Value; - otherDoc.Bucket.ShouldEqual(TestBucket); - otherDoc.Bucket.ShouldEqual(doc.Bucket); - otherDoc.Key.ShouldEqual(TestKey); - otherDoc.Key.ShouldEqual(doc.Key); - } - - [Test] - public void WritingThenReadingJsonIsSuccessful() - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - - var writeResult = Client.Put(doc); - writeResult.IsSuccess.ShouldBeTrue(writeResult.ErrorMessage); - - var readResult = Client.Get(TestBucket, TestKey); - readResult.IsSuccess.ShouldBeTrue(readResult.ErrorMessage); - - var loadedDoc = readResult.Value; - - loadedDoc.Bucket.ShouldEqual(doc.Bucket); - loadedDoc.Key.ShouldEqual(doc.Key); - loadedDoc.Value.ShouldEqual(doc.Value); - loadedDoc.VectorClock.ShouldNotBeNull(); - } - - [Test] - public void BulkInsertFetchDeleteWorksAsExpected() - { - var keys = new[] { 1, 2, 3, 4, 5 }.Select(i => TestKey + i).ToList(); - var docs = keys.Select(k => new RiakObject(TestBucket, k, TestJson, RiakConstants.ContentTypes.ApplicationJson)).ToList(); - - var writeResult = Client.Put(docs); - - writeResult.All(r => r.IsSuccess).ShouldBeTrue(); - - var objectIds = keys.Select(k => new RiakObjectId(TestBucket, k)).ToList(); - var loadedDocs = Client.Get(objectIds).ToList(); - loadedDocs.All(d => d.IsSuccess).ShouldBeTrue(); - loadedDocs.All(d => d.Value != null).ShouldBeTrue(); - - var deleteResults = Client.Delete(objectIds); - deleteResults.All(r => r.IsSuccess).ShouldBeTrue(); - } - - [Test] - public void BulkInsertFetchDeleteWorksAsExpectedInBatch() - { - var keys = new[] { 1, 2, 3, 4, 5 }.Select(i => TestKey + i).ToList(); - var docs = keys.Select(k => new RiakObject(TestBucket, k, TestJson, RiakConstants.ContentTypes.ApplicationJson)).ToList(); - - Client.Batch(batch => - { - var writeResult = batch.Put(docs); - - writeResult.All(r => r.IsSuccess).ShouldBeTrue(); - - var objectIds = keys.Select(k => new RiakObjectId(TestBucket, k)).ToList(); - var loadedDocs = batch.Get(objectIds); - loadedDocs.All(d => d.IsSuccess).ShouldBeTrue(); - loadedDocs.All(d => d.Value != null).ShouldBeTrue(); - - var deleteResults = batch.Delete(objectIds); - deleteResults.All(r => r.IsSuccess).ShouldBeTrue(); - }); - } - - [Test] - public void DeletingIsSuccessful() - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - Client.Put(doc).IsSuccess.ShouldBeTrue(); - - var result = Client.Get(TestBucket, TestKey); - result.IsSuccess.ShouldBeTrue(); - - Client.Delete(doc.Bucket, doc.Key).IsSuccess.ShouldBeTrue(); - result = Client.Get(TestBucket, TestKey); - result.IsSuccess.ShouldBeFalse(); - result.ResultCode.ShouldEqual(ResultCode.NotFound); - } - - [Test] - public void DeletingIsSuccessfulInBatch() - { - Client.Batch(batch => - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - batch.Put(doc).IsSuccess.ShouldBeTrue(); - - // yup, just to be sure the data is there on the next node - var result = batch.Get(TestBucket, TestKey); - result.IsSuccess.ShouldBeTrue(); - - batch.Delete(doc.Bucket, doc.Key).IsSuccess.ShouldBeTrue(); - result = batch.Get(TestBucket, TestKey); - result.IsSuccess.ShouldBeFalse(); - result.ResultCode.ShouldEqual(ResultCode.NotFound); - }); - } - - [Test] - public void MapReduceQueriesReturnData() - { - var bucket = Guid.NewGuid().ToString(); - - for (var i = 1; i < 11; i++) - { - var doc = new RiakObject(bucket, i.ToString(), new { value = i }); - - Client.Put(doc).IsSuccess.ShouldBeTrue(); - } - - var query = new RiakMapReduceQuery() - .Inputs(bucket) - .MapJs(m => m.Source(@"function(o) {return [ 1 ];}")) - .ReduceJs(r => r.Name(@"Riak.reduceSum").Keep(true)); - - var result = Client.MapReduce(query); - result.IsSuccess.ShouldBeTrue(); - - var mrRes = result.Value; - mrRes.PhaseResults.ShouldNotBeNull(); - mrRes.PhaseResults.Count().ShouldEqual(2); - - mrRes.PhaseResults.ElementAt(0).Phase.ShouldEqual(0u); - mrRes.PhaseResults.ElementAt(1).Phase.ShouldEqual(1u); - - //mrRes.PhaseResults.ElementAt(0).Values.ShouldBeNull(); - foreach(var v in mrRes.PhaseResults.ElementAt(0).Values) - { - v.ShouldBeNull(); - } - mrRes.PhaseResults.ElementAt(1).Values.ShouldNotBeNull(); - - var values = result.Value.PhaseResults.ElementAt(1).GetObjects().First(); - //var values = Newtonsoft.Json.JsonConvert.DeserializeObject(result.Value.PhaseResults.ElementAt(1).Values.First().FromRiakString()); - values[0].ShouldEqual(10); - } - - [Test] - public void MapReduceQueriesReturnDataInBatch() - { - var bucket = Guid.NewGuid().ToString(); - - Client.Batch(batch => - { - for (var i = 1; i < 11; i++) - { - var doc = new RiakObject(bucket, i.ToString(), new { value = i }); - batch.Put(doc).IsSuccess.ShouldBeTrue(); - } - - var query = new RiakMapReduceQuery() - .Inputs(bucket) - .MapJs(m => m.Source(@"function(o) {return [ 1 ];}")) - .ReduceJs(r => r.Name(@"Riak.reduceSum").Keep(true)); - - var result = batch.MapReduce(query); - result.IsSuccess.ShouldBeTrue(); - - var mrRes = result.Value; - mrRes.PhaseResults.ShouldNotBeNull(); - mrRes.PhaseResults.Count().ShouldEqual(2); - - mrRes.PhaseResults.ElementAt(0).Phase.ShouldEqual(0u); - mrRes.PhaseResults.ElementAt(1).Phase.ShouldEqual(1u); - - //mrRes.PhaseResults.ElementAt(0).Values.ShouldBeNull(); - foreach(var v in mrRes.PhaseResults.ElementAt(0).Values) - { - v.ShouldBeNull(); - } - mrRes.PhaseResults.ElementAt(1).Values.ShouldNotBeNull(); - - var values = result.Value.PhaseResults.ElementAt(1).GetObjects().First(); - values[0].ShouldEqual(10); - }); - } - - [Test] - public void ListBucketsIncludesTestBucket() - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - Client.Put(doc).IsSuccess.ShouldBeTrue(); - - var result = Client.ListBuckets(); - result.IsSuccess.ShouldBeTrue(); - result.Value.ShouldContain(TestBucket); - } - - [Test] - public void ListKeysIncludesTestKey() - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - Client.Put(doc).IsSuccess.ShouldBeTrue(); - - var result = Client.ListKeys(TestBucket); - result.IsSuccess.ShouldBeTrue(); - result.Value.ShouldContain(TestKey); - } - - [Test] - public void StreamListKeysIncludesTestKey() - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - Client.Put(doc).IsSuccess.ShouldBeTrue(); - - var result = Client.StreamListKeys(TestBucket); - result.IsSuccess.ShouldBeTrue(); - result.Value.ShouldContain(TestKey); - } - - [Test] - public void WritesWithAllowMultProducesMultiple() - { - DoAllowMultProducesMultipleTest(Client); - } - - [Test] - public void WritesWithAllowMultProducesMultipleInBatch() - { - Client.Batch(DoAllowMultProducesMultipleTest); - } - - private static void DoAllowMultProducesMultipleTest(IRiakBatchClient client) - { - // delete first if something does exist - client.Delete(MultiBucket, MultiKey); - - // Do this via the REST interface - will be substantially slower than PBC - var props = new RiakBucketProperties().SetAllowMultiple(true).SetLastWriteWins(false); - props.CanUsePbc.ShouldBeFalse(); - client.SetBucketProperties(MultiBucket, props).IsSuccess.ShouldBeTrue(); - - var doc = new RiakObject(MultiBucket, MultiKey, MultiBodyOne, RiakConstants.ContentTypes.ApplicationJson); - var writeResult1 = client.Put(doc); - writeResult1.IsSuccess.ShouldBeTrue(); - - doc = new RiakObject(MultiBucket, MultiKey, MultiBodyTwo, RiakConstants.ContentTypes.ApplicationJson); - var writeResult2 = client.Put(doc); - writeResult2.IsSuccess.ShouldBeTrue(); - writeResult2.Value.Siblings.Count.ShouldBeGreaterThan(2); - - var result = client.Get(MultiBucket, MultiKey); - result.Value.Siblings.Count.ShouldBeGreaterThan(2); - } - - [Test] - public void WritesWithAllowMultProducesMultipleVTags() - { - // Do this via the PBC - noticable quicker than REST - var props = new RiakBucketProperties().SetAllowMultiple(true); - props.CanUsePbc.ShouldBeTrue(); - Client.SetBucketProperties(MultiBucket, props).IsSuccess.ShouldBeTrue(); - - var doc = new RiakObject(MultiBucket, MultiKey, MultiBodyOne, RiakConstants.ContentTypes.ApplicationJson); - Client.Put(doc).IsSuccess.ShouldBeTrue(); - - doc = new RiakObject(MultiBucket, MultiKey, MultiBodyTwo, RiakConstants.ContentTypes.ApplicationJson); - Client.Put(doc).IsSuccess.ShouldBeTrue(); - - var result = Client.Get(MultiBucket, MultiKey); - - result.Value.VTags.ShouldNotBeNull(); - result.Value.VTags.Count.IsAtLeast(2); - } - - [Test] - public void WritesWithAllowMultProducesMultipleVTagsInBatch() - { - Client.Batch(batch => - { - // Do this via the PBC - noticable quicker than REST - var props = new RiakBucketProperties().SetAllowMultiple(true); - props.CanUsePbc.ShouldBeTrue(); - batch.SetBucketProperties(MultiBucket, props).IsSuccess.ShouldBeTrue(); - - var doc = new RiakObject(MultiBucket, MultiKey, MultiBodyOne, RiakConstants.ContentTypes.ApplicationJson); - batch.Put(doc).IsSuccess.ShouldBeTrue(); - - doc = new RiakObject(MultiBucket, MultiKey, MultiBodyTwo, RiakConstants.ContentTypes.ApplicationJson); - batch.Put(doc).IsSuccess.ShouldBeTrue(); - - var result = batch.Get(MultiBucket, MultiKey); - - result.Value.VTags.ShouldNotBeNull(); - result.Value.VTags.Count.IsAtLeast(2); - }); - } - - [Test] - public void DeleteBucketDeletesAllKeysInABucketInBatch() - { - // add multiple keys - var bucket = Guid.NewGuid().ToString(); - - Client.Batch(batch => - { - for (var i = 1; i < 11; i++) - { - var doc = new RiakObject(bucket, i.ToString(), new { value = i }); - - batch.Put(doc); - } - - var keyList = batch.ListKeys(bucket); - keyList.Value.Count().ShouldEqual(10); - - batch.DeleteBucket(bucket); - - // This might fail if you check straight away - // because deleting takes time behind the scenes. - // So wait in case (yup, you can shoot me if you like!) - Thread.Sleep(4000); - - keyList = batch.ListKeys(bucket); - keyList.Value.Count().ShouldEqual(0); - batch.ListBuckets().Value.Contains(bucket).ShouldBeFalse(); - }); - } - - [Test] - public void DeleteBucketDeletesAllKeysInABucket() - { - // add multiple keys - var bucket = Guid.NewGuid().ToString(); - - for (var i = 1; i < 11; i++) - { - var doc = new RiakObject(bucket, i.ToString(), new { value = i }); - - Client.Put(doc); - } - - var keyList = Client.ListKeys(bucket); - keyList.Value.Count().ShouldEqual(10); - - Client.DeleteBucket(bucket); - - // This might fail if you check straight away - // because deleting takes time behind the scenes. - // So wait in case (yup, you can shoot me if you like!) - Thread.Sleep(4000); - - keyList = Client.ListKeys(bucket); - keyList.Value.Count().ShouldEqual(0); - Client.ListBuckets().Value.Contains(bucket).ShouldBeFalse(); - } - - [Test] - public void DeleteBucketDeletesAllKeysInABucketAsynchronously() - { - // add multiple keys - var bucket = Guid.NewGuid().ToString(); - - for (var i = 1; i < 11; i++) - { - var doc = new RiakObject(bucket, i.ToString(), new { value = i }); - - Client.Put(doc); - } - - var keyList = Client.ListKeys(bucket); - keyList.Value.Count().ShouldEqual(10); - - var result = Client.Async.DeleteBucket(bucket).Result.ToList(); - result.ForEach(x => x.IsSuccess.ShouldBeTrue(x.ErrorMessage)); - - // This might fail if you check straight away - // because deleting takes time behind the scenes. - // So wait in case (yup, you can shoot me if you like!) - Thread.Sleep(4000); - - keyList = Client.ListKeys(bucket); - keyList.Value.Count().ShouldEqual(0); - Client.ListBuckets().Value.Contains(bucket).ShouldBeFalse(); - } - - [Test] - public void DeletingAnObjectDeletesAnObject() - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - Client.Put(doc).IsSuccess.ShouldBeTrue(); - - var deleteResult = Client.Delete(doc.Bucket, doc.Key); - deleteResult.IsSuccess.ShouldBeTrue(); - - var getResult = Client.Get(doc.Bucket, doc.Key); - getResult.IsSuccess.ShouldBeFalse(); - getResult.Value.ShouldBeNull(); - getResult.ResultCode.ShouldEqual(ResultCode.NotFound); - } - - [Test] - public void DeletingAnObjectDeletesAnObjectInBatch() - { - Client.Batch(batch => - { - var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); - batch.Put(doc).IsSuccess.ShouldBeTrue(); - - var deleteResult = batch.Delete(doc.Bucket, doc.Key); - deleteResult.IsSuccess.ShouldBeTrue(); - - var getResult = batch.Get(doc.Bucket, doc.Key); - getResult.IsSuccess.ShouldBeFalse(); - getResult.Value.ShouldBeNull(); - getResult.ResultCode.ShouldEqual(ResultCode.NotFound); - }); - } - - [Test] - public void AsyncListKeysReturnsTheCorrectNumberOfResults() - { - var bucket = Guid.NewGuid().ToString(); - - for (var i = 1; i < 11; i++) - { - var doc = new RiakObject(bucket, i.ToString(), new { value = i }); - - var r = Client.Put(doc); - r.IsSuccess.ShouldBeTrue(); - } - - var result = Client.Async.ListKeys(bucket).Result; - - result.IsSuccess.ShouldBeTrue(); - result.Value.ShouldNotBeNull(); - result.Value.Count().ShouldEqual(10); - } - } -} +// Copyright (c) 2011 - OJ Reeves & Jeremiah Peschka +// +// This file is provided to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System.Collections.Generic; +using CorrugatedIron.Models; +using CorrugatedIron.Models.MapReduce; +using CorrugatedIron.Tests.Extensions; +using CorrugatedIron.Tests.Live.LiveRiakConnectionTests; +using CorrugatedIron.Util; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading; + +namespace CorrugatedIron.Tests.Live.GeneralIntegrationTests +{ + [TestFixture] + public class WhenTalkingToRiak : LiveRiakConnectionTestBase + { + [Test] + public void ServerInfoIsSuccessfullyExtracted() + { + var result = Client.GetServerInfo(); + result.IsSuccess.ShouldBeTrue(result.ErrorMessage); + } + + [Test] + public void ServerInfoIsSuccessfullyExtractedAsynchronously() + { + var result = Client.Async.GetServerInfo().Result; + result.IsSuccess.ShouldBeTrue(result.ErrorMessage); + } + + [Test] + public void PingRequstResultsInPingResponse() + { + var result = Client.Ping(); + result.IsSuccess.ShouldBeTrue(result.ErrorMessage); + } + + [Test] + public void ReadingMissingValueDoesntBreak() + { + var readResult = Client.Get("nobucket", "novalue"); + readResult.IsSuccess.ShouldBeFalse(readResult.ErrorMessage); + readResult.ResultCode.ShouldEqual(ResultCode.NotFound); + } + + [Test] + public void GetsWithBucketAndKeyReturnObjectsThatAreMarkedAsNotChanged() + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + var writeResult = Client.Put(doc); + writeResult.IsSuccess.ShouldBeTrue(); + writeResult.Value.ShouldNotBeNull(); + + var readResult = Client.Get(TestBucket, TestKey); + readResult.IsSuccess.ShouldBeTrue(); + readResult.Value.ShouldNotBeNull(); + readResult.Value.HasChanged.ShouldBeFalse(); + } + + [Test] + public void GetWithInvalidBucketReturnsInvalidRequest() + { + var getResult = Client.Get("", "key"); + getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); + getResult = Client.Get("foo/bar", "key"); + getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); + getResult = Client.Get(" ", "key"); + getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); + getResult = Client.Get(null, "key"); + getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); + } + + [Test] + public void GetWithInvalidKeyReturnsInvalidRequest() + { + var getResult = Client.Get("bucket", ""); + getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); + getResult = Client.Get("bucket", "foo/bar"); + getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); + getResult = Client.Get("bucket", " "); + getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); + getResult = Client.Get("bucket", null); + getResult.ResultCode.ShouldEqual(ResultCode.InvalidRequest); + } + + [Test] + public void MultiGetWithValidAndInvalidBucketsBehavesCorrectly() + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + var writeResult = Client.Put(doc); + + writeResult.IsSuccess.ShouldBeTrue(); + writeResult.Value.ShouldNotBeNull(); + + var getResults = Client.Get(new List + { + new RiakObjectId(null, "key"), + new RiakObjectId("", "key"), + new RiakObjectId(" ", "key"), + new RiakObjectId("foo/bar", "key"), + new RiakObjectId(TestBucket, TestKey) + }).ToList(); + + getResults.Count(r => r.IsSuccess).ShouldEqual(1); + } + + [Test] + public void MultiPutWithValidAndInvalidBucketsBehavesCorrectly() + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + var writeResult = Client.Put(doc); + + writeResult.IsSuccess.ShouldBeTrue(); + writeResult.Value.ShouldNotBeNull(); + + var putResults = Client.Put(new List + { + new RiakObject(null, "key", TestJson, RiakConstants.ContentTypes.ApplicationJson), + new RiakObject("", "key", TestJson, RiakConstants.ContentTypes.ApplicationJson), + new RiakObject(" ", "key", TestJson, RiakConstants.ContentTypes.ApplicationJson), + new RiakObject("foo/bar", "key", TestJson, RiakConstants.ContentTypes.ApplicationJson), + new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson) + }).ToList(); + + putResults.Count(r => r.IsSuccess).ShouldEqual(1); + } + + [Test] + public void MultiDeleteWithValidAndInvalidBucketsBehavesCorrectly() + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + var writeResult = Client.Put(doc); + + writeResult.IsSuccess.ShouldBeTrue(); + writeResult.Value.ShouldNotBeNull(); + + var deleteResults = Client.Delete(new List + { + new RiakObjectId(null, "key"), + new RiakObjectId("", "key"), + new RiakObjectId(" ", "key"), + new RiakObjectId("foo/bar", "key"), + new RiakObjectId(TestBucket, TestKey) + }).ToList(); + + deleteResults.Count(r => r.IsSuccess).ShouldEqual(1); + var deletedItemGetResult = Client.Get(TestBucket, TestKey); + deletedItemGetResult.ResultCode.ShouldEqual(ResultCode.NotFound); + } + + [Test] + public void GetsWithBucketAndKeyReturnObjects() + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + var writeResult = Client.Put(doc); + + writeResult.IsSuccess.ShouldBeTrue(); + writeResult.Value.ShouldNotBeNull(); + + var readResult = Client.Get(TestBucket, TestKey); + readResult.IsSuccess.ShouldBeTrue(); + readResult.Value.ShouldNotBeNull(); + + var otherDoc = readResult.Value; + otherDoc.Bucket.ShouldEqual(TestBucket); + otherDoc.Bucket.ShouldEqual(doc.Bucket); + otherDoc.Key.ShouldEqual(TestKey); + otherDoc.Key.ShouldEqual(doc.Key); + } + + [Test] + public void GetsWithRiakObjectIdReturnObjects() + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + var writeResult = Client.Put(doc); + + writeResult.IsSuccess.ShouldBeTrue(); + writeResult.Value.ShouldNotBeNull(); + + var riakObjectId = new RiakObjectId(TestBucket, TestKey); + var readResult = Client.Get(riakObjectId); + + var otherDoc = readResult.Value; + otherDoc.Bucket.ShouldEqual(TestBucket); + otherDoc.Bucket.ShouldEqual(doc.Bucket); + otherDoc.Key.ShouldEqual(TestKey); + otherDoc.Key.ShouldEqual(doc.Key); + } + + [Test] + public void WritingThenReadingJsonIsSuccessful() + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + + var writeResult = Client.Put(doc); + writeResult.IsSuccess.ShouldBeTrue(writeResult.ErrorMessage); + + var readResult = Client.Get(TestBucket, TestKey); + readResult.IsSuccess.ShouldBeTrue(readResult.ErrorMessage); + + var loadedDoc = readResult.Value; + + loadedDoc.Bucket.ShouldEqual(doc.Bucket); + loadedDoc.Key.ShouldEqual(doc.Key); + loadedDoc.Value.ShouldEqual(doc.Value); + loadedDoc.VectorClock.ShouldNotBeNull(); + } + + [Test] + public void BulkInsertFetchDeleteWorksAsExpected() + { + var keys = new[] { 1, 2, 3, 4, 5 }.Select(i => TestKey + i).ToList(); + var docs = keys.Select(k => new RiakObject(TestBucket, k, TestJson, RiakConstants.ContentTypes.ApplicationJson)).ToList(); + + var writeResult = Client.Put(docs); + + writeResult.All(r => r.IsSuccess).ShouldBeTrue(); + + var objectIds = keys.Select(k => new RiakObjectId(TestBucket, k)).ToList(); + var loadedDocs = Client.Get(objectIds).ToList(); + loadedDocs.All(d => d.IsSuccess).ShouldBeTrue(); + loadedDocs.All(d => d.Value != null).ShouldBeTrue(); + + var deleteResults = Client.Delete(objectIds); + deleteResults.All(r => r.IsSuccess).ShouldBeTrue(); + } + + [Test] + public void BulkInsertFetchDeleteWorksAsExpectedInBatch() + { + var keys = new[] { 1, 2, 3, 4, 5 }.Select(i => TestKey + i).ToList(); + var docs = keys.Select(k => new RiakObject(TestBucket, k, TestJson, RiakConstants.ContentTypes.ApplicationJson)).ToList(); + + Client.Batch(batch => + { + var writeResult = batch.Put(docs); + + writeResult.All(r => r.IsSuccess).ShouldBeTrue(); + + var objectIds = keys.Select(k => new RiakObjectId(TestBucket, k)).ToList(); + var loadedDocs = batch.Get(objectIds); + loadedDocs.All(d => d.IsSuccess).ShouldBeTrue(); + loadedDocs.All(d => d.Value != null).ShouldBeTrue(); + + var deleteResults = batch.Delete(objectIds); + deleteResults.All(r => r.IsSuccess).ShouldBeTrue(); + }); + } + + [Test] + public void DeletingIsSuccessful() + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + Client.Put(doc).IsSuccess.ShouldBeTrue(); + + var result = Client.Get(TestBucket, TestKey); + result.IsSuccess.ShouldBeTrue(); + + Client.Delete(doc.Bucket, doc.Key).IsSuccess.ShouldBeTrue(); + result = Client.Get(TestBucket, TestKey); + result.IsSuccess.ShouldBeFalse(); + result.ResultCode.ShouldEqual(ResultCode.NotFound); + } + + [Test] + public void DeletingIsSuccessfulInBatch() + { + Client.Batch(batch => + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + batch.Put(doc).IsSuccess.ShouldBeTrue(); + + // yup, just to be sure the data is there on the next node + var result = batch.Get(TestBucket, TestKey); + result.IsSuccess.ShouldBeTrue(); + + batch.Delete(doc.Bucket, doc.Key).IsSuccess.ShouldBeTrue(); + result = batch.Get(TestBucket, TestKey); + result.IsSuccess.ShouldBeFalse(); + result.ResultCode.ShouldEqual(ResultCode.NotFound); + }); + } + + [Test] + public void MapReduceQueriesReturnData() + { + var bucket = Guid.NewGuid().ToString(); + + for (var i = 1; i < 11; i++) + { + var doc = new RiakObject(bucket, i.ToString(), new { value = i }); + + Client.Put(doc).IsSuccess.ShouldBeTrue(); + } + + var query = new RiakMapReduceQuery() + .Inputs(bucket) + .MapJs(m => m.Source(@"function(o) {return [ 1 ];}")) + .ReduceJs(r => r.Name(@"Riak.reduceSum").Keep(true)); + + var result = Client.MapReduce(query); + result.IsSuccess.ShouldBeTrue(); + + var mrRes = result.Value; + mrRes.PhaseResults.ShouldNotBeNull(); + mrRes.PhaseResults.Count().ShouldEqual(2); + + mrRes.PhaseResults.ElementAt(0).Phase.ShouldEqual(0u); + mrRes.PhaseResults.ElementAt(1).Phase.ShouldEqual(1u); + + //mrRes.PhaseResults.ElementAt(0).Values.ShouldBeNull(); + foreach(var v in mrRes.PhaseResults.ElementAt(0).Values) + { + v.ShouldBeNull(); + } + mrRes.PhaseResults.ElementAt(1).Values.ShouldNotBeNull(); + + var values = result.Value.PhaseResults.ElementAt(1).GetObjects().First(); + //var values = Newtonsoft.Json.JsonConvert.DeserializeObject(result.Value.PhaseResults.ElementAt(1).Values.First().FromRiakString()); + values[0].ShouldEqual(10); + } + + [Test] + public void MapReduceQueriesReturnDataInBatch() + { + var bucket = Guid.NewGuid().ToString(); + + Client.Batch(batch => + { + for (var i = 1; i < 11; i++) + { + var doc = new RiakObject(bucket, i.ToString(), new { value = i }); + batch.Put(doc).IsSuccess.ShouldBeTrue(); + } + + var query = new RiakMapReduceQuery() + .Inputs(bucket) + .MapJs(m => m.Source(@"function(o) {return [ 1 ];}")) + .ReduceJs(r => r.Name(@"Riak.reduceSum").Keep(true)); + + var result = batch.MapReduce(query); + result.IsSuccess.ShouldBeTrue(); + + var mrRes = result.Value; + mrRes.PhaseResults.ShouldNotBeNull(); + mrRes.PhaseResults.Count().ShouldEqual(2); + + mrRes.PhaseResults.ElementAt(0).Phase.ShouldEqual(0u); + mrRes.PhaseResults.ElementAt(1).Phase.ShouldEqual(1u); + + //mrRes.PhaseResults.ElementAt(0).Values.ShouldBeNull(); + foreach(var v in mrRes.PhaseResults.ElementAt(0).Values) + { + v.ShouldBeNull(); + } + mrRes.PhaseResults.ElementAt(1).Values.ShouldNotBeNull(); + + var values = result.Value.PhaseResults.ElementAt(1).GetObjects().First(); + values[0].ShouldEqual(10); + }); + } + + [Test] + public void ListBucketsIncludesTestBucket() + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + Client.Put(doc).IsSuccess.ShouldBeTrue(); + + var result = Client.ListBuckets(); + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldContain(TestBucket); + } + + [Test] + public void ListKeysIncludesTestKey() + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + Client.Put(doc).IsSuccess.ShouldBeTrue(); + + var result = Client.ListKeys(TestBucket); + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldContain(TestKey); + } + + [Test] + public void StreamListKeysIncludesTestKey() + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + Client.Put(doc).IsSuccess.ShouldBeTrue(); + + var result = Client.StreamListKeys(TestBucket); + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldContain(TestKey); + } + + [Test] + public void WritesWithAllowMultProducesMultiple() + { + DoAllowMultProducesMultipleTest(Client); + } + + [Test] + public void WritesWithAllowMultProducesMultipleInBatch() + { + Client.Batch(DoAllowMultProducesMultipleTest); + } + + private static void DoAllowMultProducesMultipleTest(IRiakBatchClient client) + { + // delete first if something does exist + client.Delete(MultiBucket, MultiKey); + + // Do this via the REST interface - will be substantially slower than PBC + var props = new RiakBucketProperties().SetAllowMultiple(true).SetLastWriteWins(false); + props.CanUsePbc.ShouldBeFalse(); + client.SetBucketProperties(MultiBucket, props).IsSuccess.ShouldBeTrue(); + + var doc = new RiakObject(MultiBucket, MultiKey, MultiBodyOne, RiakConstants.ContentTypes.ApplicationJson); + var writeResult1 = client.Put(doc); + writeResult1.IsSuccess.ShouldBeTrue(); + + doc = new RiakObject(MultiBucket, MultiKey, MultiBodyTwo, RiakConstants.ContentTypes.ApplicationJson); + var writeResult2 = client.Put(doc); + writeResult2.IsSuccess.ShouldBeTrue(); + writeResult2.Value.Siblings.Count.ShouldBeGreaterThan(2); + + var result = client.Get(MultiBucket, MultiKey); + result.Value.Siblings.Count.ShouldBeGreaterThan(2); + } + + [Test] + public void WritesWithAllowMultProducesMultipleVTags() + { + // Do this via the PBC - noticable quicker than REST + var props = new RiakBucketProperties().SetAllowMultiple(true); + props.CanUsePbc.ShouldBeTrue(); + Client.SetBucketProperties(MultiBucket, props).IsSuccess.ShouldBeTrue(); + + var doc = new RiakObject(MultiBucket, MultiKey, MultiBodyOne, RiakConstants.ContentTypes.ApplicationJson); + Client.Put(doc).IsSuccess.ShouldBeTrue(); + + doc = new RiakObject(MultiBucket, MultiKey, MultiBodyTwo, RiakConstants.ContentTypes.ApplicationJson); + Client.Put(doc).IsSuccess.ShouldBeTrue(); + + var result = Client.Get(MultiBucket, MultiKey); + + result.Value.VTags.ShouldNotBeNull(); + result.Value.VTags.Count.IsAtLeast(2); + } + + [Test] + public void WritesWithAllowMultProducesMultipleVTagsInBatch() + { + Client.Batch(batch => + { + // Do this via the PBC - noticable quicker than REST + var props = new RiakBucketProperties().SetAllowMultiple(true); + props.CanUsePbc.ShouldBeTrue(); + batch.SetBucketProperties(MultiBucket, props).IsSuccess.ShouldBeTrue(); + + var doc = new RiakObject(MultiBucket, MultiKey, MultiBodyOne, RiakConstants.ContentTypes.ApplicationJson); + batch.Put(doc).IsSuccess.ShouldBeTrue(); + + doc = new RiakObject(MultiBucket, MultiKey, MultiBodyTwo, RiakConstants.ContentTypes.ApplicationJson); + batch.Put(doc).IsSuccess.ShouldBeTrue(); + + var result = batch.Get(MultiBucket, MultiKey); + + result.Value.VTags.ShouldNotBeNull(); + result.Value.VTags.Count.IsAtLeast(2); + }); + } + + [Test] + public void DeleteBucketDeletesAllKeysInABucketInBatch() + { + // add multiple keys + var bucket = Guid.NewGuid().ToString(); + + Client.Batch(batch => + { + for (var i = 1; i < 11; i++) + { + var doc = new RiakObject(bucket, i.ToString(), new { value = i }); + + batch.Put(doc); + } + + var keyList = batch.ListKeys(bucket); + keyList.Value.Count().ShouldEqual(10); + + batch.DeleteBucket(bucket); + + // This might fail if you check straight away + // because deleting takes time behind the scenes. + // So wait in case (yup, you can shoot me if you like!) + Thread.Sleep(4000); + + keyList = batch.ListKeys(bucket); + keyList.Value.Count().ShouldEqual(0); + batch.ListBuckets().Value.Contains(bucket).ShouldBeFalse(); + }); + } + + [Test] + public void DeleteBucketDeletesAllKeysInABucket() + { + // add multiple keys + var bucket = Guid.NewGuid().ToString(); + + for (var i = 1; i < 11; i++) + { + var doc = new RiakObject(bucket, i.ToString(), new { value = i }); + + Client.Put(doc); + } + + var keyList = Client.ListKeys(bucket); + keyList.Value.Count().ShouldEqual(10); + + Client.DeleteBucket(bucket); + + // This might fail if you check straight away + // because deleting takes time behind the scenes. + // So wait in case (yup, you can shoot me if you like!) + Thread.Sleep(4000); + + keyList = Client.ListKeys(bucket); + keyList.Value.Count().ShouldEqual(0); + Client.ListBuckets().Value.Contains(bucket).ShouldBeFalse(); + } + + /* no need for this test, given this is now asynchronous wrapper + [Test] + public void DeleteBucketDeletesAllKeysInABucketAsynchronously() + { + // add multiple keys + var bucket = Guid.NewGuid().ToString(); + + for (var i = 1; i < 11; i++) + { + var doc = new RiakObject(bucket, i.ToString(), new { value = i }); + + Client.Put(doc); + } + + var keyList = Client.ListKeys(bucket); + keyList.Value.Count().ShouldEqual(10); + + var result = Client.DeleteBucket(bucket).ToList(); + result.ForEach(x => x.IsSuccess.ShouldBeTrue(x.ErrorMessage)); + + // This might fail if you check straight away + // because deleting takes time behind the scenes. + // So wait in case (yup, you can shoot me if you like!) + Thread.Sleep(4000); + + keyList = Client.ListKeys(bucket); + keyList.Value.Count().ShouldEqual(0); + Client.ListBuckets().Value.Contains(bucket).ShouldBeFalse(); + } + */ + + [Test] + public void DeletingAnObjectDeletesAnObject() + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + Client.Put(doc).IsSuccess.ShouldBeTrue(); + + var deleteResult = Client.Delete(doc.Bucket, doc.Key); + deleteResult.IsSuccess.ShouldBeTrue(); + + var getResult = Client.Get(doc.Bucket, doc.Key); + getResult.IsSuccess.ShouldBeFalse(); + getResult.Value.ShouldBeNull(); + getResult.ResultCode.ShouldEqual(ResultCode.NotFound); + } + + [Test] + public void DeletingAnObjectDeletesAnObjectInBatch() + { + Client.Batch(batch => + { + var doc = new RiakObject(TestBucket, TestKey, TestJson, RiakConstants.ContentTypes.ApplicationJson); + batch.Put(doc).IsSuccess.ShouldBeTrue(); + + var deleteResult = batch.Delete(doc.Bucket, doc.Key); + deleteResult.IsSuccess.ShouldBeTrue(); + + var getResult = batch.Get(doc.Bucket, doc.Key); + getResult.IsSuccess.ShouldBeFalse(); + getResult.Value.ShouldBeNull(); + getResult.ResultCode.ShouldEqual(ResultCode.NotFound); + }); + } + + [Test] + public void AsyncListKeysReturnsTheCorrectNumberOfResults() + { + var bucket = Guid.NewGuid().ToString(); + + for (var i = 1; i < 11; i++) + { + var doc = new RiakObject(bucket, i.ToString(), new { value = i }); + + var r = Client.Put(doc); + r.IsSuccess.ShouldBeTrue(); + } + + var result = Client.Async.ListKeys(bucket).Result; + + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldNotBeNull(); + result.Value.Count().ShouldEqual(10); + } + } +} diff --git a/CorrugatedIron.Tests.Live/IdleTests.cs b/CorrugatedIron.Tests.Live/IdleTests.cs index 3736c1e0..e6f1aa6c 100644 --- a/CorrugatedIron.Tests.Live/IdleTests.cs +++ b/CorrugatedIron.Tests.Live/IdleTests.cs @@ -13,7 +13,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. - +using System.Threading.Tasks; using CorrugatedIron.Comms; using CorrugatedIron.Tests.Extensions; using CorrugatedIron.Tests.Live.LiveRiakConnectionTests; @@ -32,9 +32,9 @@ public WhenConnectionGoesIdle() private IRiakConnection GetIdleConnection() { - var result = Cluster.UseConnection(RiakResult.Success, 1); - //System.Threading.Thread.Sleep(ClusterConfig.RiakNodes[0].IdleTimeout + 1000); - return result.Value; + var task = Cluster.UseConnection(RiakResult.SuccessTask, 1); + task.Wait(); + return task.Result.Value; } [Test] diff --git a/CorrugatedIron.Tests/RiakAsyncClientTests.cs b/CorrugatedIron.Tests/RiakAsyncClientTests.cs index 5166b6f2..a72c9a65 100644 --- a/CorrugatedIron.Tests/RiakAsyncClientTests.cs +++ b/CorrugatedIron.Tests/RiakAsyncClientTests.cs @@ -24,6 +24,8 @@ namespace CorrugatedIron.Tests.RiakAsyncClientTests { + + /* internal abstract class RiakAsyncClientTestBase { protected Mock ClientMock; @@ -141,4 +143,5 @@ public void AsyncClientReturnsCorrectResult() Result.Count().ShouldEqual(0); } } + */ } diff --git a/CorrugatedIron.Tests/RiakClientSetBucketPropertiesTests.cs b/CorrugatedIron.Tests/RiakClientSetBucketPropertiesTests.cs index 70501781..dd0bbaa7 100644 --- a/CorrugatedIron.Tests/RiakClientSetBucketPropertiesTests.cs +++ b/CorrugatedIron.Tests/RiakClientSetBucketPropertiesTests.cs @@ -24,6 +24,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace CorrugatedIron.Tests.RiakClientSetBucketPropertiesTests { @@ -50,19 +51,24 @@ public IRiakClient CreateClient(string seed) return new Mock().Object; } + public IRiakAsyncClient CreateAsyncClient() + { + return new Mock().Object; + } + public int RetryWaitTime { get; set; } - public RiakResult UseConnection(Func> useFun, int retryAttempts) + public Task> UseConnection(Func>> useFun, int retryAttempts) { return useFun(ConnectionMock.Object); } - public RiakResult UseConnection(Func useFun, int retryAttempts) + public Task UseConnection(Func> useFun, int retryAttempts) { return useFun(ConnectionMock.Object); } - public RiakResult> UseDelayedConnection(Func>> useFun, int retryAttempts) + public Task>> UseDelayedConnection(Func>>> useFun, int retryAttempts) where TResult : RiakResult { throw new NotImplementedException(); @@ -79,7 +85,7 @@ protected RiakClientSetBucketPropertiesTestBase() { Cluster = new MockCluster(); ClientId = System.Text.Encoding.Default.GetBytes("fadjskl").Take(4).ToArray(); - Client = new RiakClient(Cluster); + Client = new RiakClient(new RiakAsyncClient(Cluster)); } } @@ -91,7 +97,8 @@ public class WhenSettingBucketPropertiesWithExtendedProperties : RiakClientSetBu public void SetUp() { var result = RiakResult.Success(new RiakRestResponse { StatusCode = System.Net.HttpStatusCode.NoContent }); - Cluster.ConnectionMock.Setup(m => m.RestRequest(It.IsAny())).Returns(result); + Cluster.ConnectionMock.Setup(m => m.RestRequest(It.IsAny())) + .Returns(Task>.Factory.StartNew(() => result)); Response = Client.SetBucketProperties("foo", new RiakBucketProperties().SetAllowMultiple(true).SetRVal("one")); } @@ -112,8 +119,8 @@ public class WhenSettingBucketPropertiesWithoutExtendedProperties : RiakClientSe public void SetUp() { var result = RiakResult.Success(); - Cluster.ConnectionMock.Setup(m => m.PbcWriteRead(It.IsAny(), MessageCode.SetBucketResp)).Returns(result); - + Cluster.ConnectionMock.Setup(m => m.PbcWriteRead(It.IsAny(), MessageCode.SetBucketResp)) + .Returns(Task.Factory.StartNew(() => result)); Response = Client.SetBucketProperties("foo", new RiakBucketProperties().SetAllowMultiple(true)); } diff --git a/CorrugatedIron.Tests/RiakClientTestBase.cs b/CorrugatedIron.Tests/RiakClientTestBase.cs index 7b69407b..56d1d5ce 100644 --- a/CorrugatedIron.Tests/RiakClientTestBase.cs +++ b/CorrugatedIron.Tests/RiakClientTestBase.cs @@ -18,6 +18,7 @@ using CorrugatedIron.Config; using Moq; using System.Collections.Generic; +using System.Threading.Tasks; namespace CorrugatedIron.Tests.RiakClientTests { @@ -40,7 +41,8 @@ protected void SetUpInternal() ConnFactoryMock = new Mock(); NodeConfigMock = new Mock(); - ConnMock.Setup(m => m.PbcWriteRead(It.IsAny())).Returns(() => Result); + ConnMock.Setup(m => m.PbcWriteRead(It.IsAny())) + .Returns(() => Task>.Factory.StartNew(() => Result)); ConnFactoryMock.Setup(m => m.CreateConnection(It.IsAny())).Returns(ConnMock.Object); NodeConfigMock.SetupGet(m => m.PoolSize).Returns(1); ClusterConfigMock.SetupGet(m => m.RiakNodes).Returns(new List { NodeConfigMock.Object }); diff --git a/CorrugatedIron/Comms/IRiakConnectionManager.cs b/CorrugatedIron/Comms/IRiakConnectionManager.cs index 9bc6c0e9..71b35778 100644 --- a/CorrugatedIron/Comms/IRiakConnectionManager.cs +++ b/CorrugatedIron/Comms/IRiakConnectionManager.cs @@ -15,12 +15,13 @@ // under the License. using System; +using System.Threading.Tasks; namespace CorrugatedIron.Comms { internal interface IRiakConnectionManager : IDisposable { - Tuple Consume(Func consumer); - Tuple DelayedConsume(Func consumer); + Task> Consume(Func> consumer); + Task> DelayedConsume(Func> consumer); } } diff --git a/CorrugatedIron/Comms/RiakConnection.cs b/CorrugatedIron/Comms/RiakConnection.cs index 28f3f17b..80827926 100644 --- a/CorrugatedIron/Comms/RiakConnection.cs +++ b/CorrugatedIron/Comms/RiakConnection.cs @@ -1,478 +1,539 @@ -// Copyright (c) 2011 - OJ Reeves & Jeremiah Peschka -// -// This file is provided to you under the Apache License, -// Version 2.0 (the "License"); you may not use this file -// except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -using CorrugatedIron.Config; -using CorrugatedIron.Exceptions; -using CorrugatedIron.Extensions; -using CorrugatedIron.Messages; -using CorrugatedIron.Models.Rest; -using CorrugatedIron.Util; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; -using System.Text; - -namespace CorrugatedIron.Comms -{ - public interface IRiakConnection : IDisposable - { - bool IsIdle { get; } - - void Disconnect(); - - // PBC interface - RiakResult PbcRead() - where TResult : class, new(); - - RiakResult PbcRead(MessageCode expectedMessageCode); - - RiakResult PbcWrite(TRequest request) - where TRequest : class; - - RiakResult PbcWrite(MessageCode messageCode); - - RiakResult PbcWriteRead(TRequest request) - where TRequest : class - where TResult : class, new(); - - RiakResult PbcWriteRead(MessageCode messageCode) - where TResult : class, new(); - - RiakResult PbcWriteRead(TRequest request, MessageCode expectedMessageCode) - where TRequest : class; - - RiakResult PbcWriteRead(MessageCode messageCode, MessageCode expectedMessageCode); - - RiakResult>> PbcRepeatRead(Func, bool> repeatRead) - where TResult : class, new(); - - RiakResult>> PbcWriteRead(MessageCode messageCode, Func, bool> repeatRead) - where TResult : class, new(); - - RiakResult>> PbcWriteRead(TRequest request, Func, bool> repeatRead) - where TRequest : class - where TResult : class, new(); - - RiakResult>> PbcStreamRead(Func, bool> repeatRead, Action onFinish) - where TResult : class, new(); - - RiakResult>> PbcWriteStreamRead(TRequest request, - Func, bool> repeatRead, Action onFinish) - where TRequest : class - where TResult : class, new(); - - RiakResult>> PbcWriteStreamRead(MessageCode messageCode, - Func, bool> repeatRead, Action onFinish) - where TResult : class, new(); - - // REST interface - RiakResult RestRequest(RiakRestRequest request); - } - - internal class RiakConnection : IRiakConnection - { - private readonly string _restRootUrl; - private readonly RiakPbcSocket _socket; - - public bool IsIdle - { - get { return _socket.IsConnected; } - } - - static RiakConnection() - { - ServicePointManager.ServerCertificateValidationCallback += ServerValidationCallback; - } - - public RiakConnection(IRiakNodeConfiguration nodeConfiguration) - { - _restRootUrl = @"{0}://{1}:{2}".Fmt(nodeConfiguration.RestScheme, nodeConfiguration.HostAddress, nodeConfiguration.RestPort); - _socket = new RiakPbcSocket(nodeConfiguration.HostAddress, nodeConfiguration.PbcPort, nodeConfiguration.NetworkReadTimeout, - nodeConfiguration.NetworkWriteTimeout); - } - - public RiakResult PbcRead() - where TResult : class, new() - { - try - { - var result = _socket.Read(); - return RiakResult.Success(result); - } - catch (RiakException ex) - { - if (ex.NodeOffline) - { - Disconnect(); - } - return RiakResult.Error(ResultCode.CommunicationError, ex.Message, ex.NodeOffline); - } - catch (Exception ex) - { - Disconnect(); - return RiakResult.Error(ResultCode.CommunicationError, ex.Message, true); - } - } - - public RiakResult PbcRead(MessageCode expectedMessageCode) - { - try - { - _socket.Read(expectedMessageCode); - return RiakResult.Success(); - } - catch (RiakException ex) - { - if (ex.NodeOffline) - { - Disconnect(); - } - return RiakResult.Error(ResultCode.CommunicationError, ex.Message, ex.NodeOffline); - } - catch(Exception ex) - { - Disconnect(); - return RiakResult.Error(ResultCode.CommunicationError, ex.Message, true); - } - } - - public RiakResult>> PbcRepeatRead(Func, bool> repeatRead) - where TResult : class, new() - { - var results = new List>(); - try - { - RiakResult result; - do - { - result = RiakResult.Success(_socket.Read()); - results.Add(result); - } while(repeatRead(result)); - - return RiakResult>>.Success(results); - } - catch (RiakException ex) - { - if (ex.NodeOffline) - { - Disconnect(); - } - return RiakResult>>.Error(ResultCode.CommunicationError, ex.Message, ex.NodeOffline); - } - catch(Exception ex) - { - Disconnect(); - return RiakResult>>.Error(ResultCode.CommunicationError, ex.Message, true); - } - } - - public RiakResult PbcWrite(TRequest request) - where TRequest : class - { - try - { - _socket.Write(request); - return RiakResult.Success(); - } - catch (RiakException ex) - { - if (ex.NodeOffline) - { - Disconnect(); - } - return RiakResult.Error(ResultCode.CommunicationError, ex.Message, ex.NodeOffline); - } - catch(Exception ex) - { - Disconnect(); - return RiakResult.Error(ResultCode.CommunicationError, ex.Message, true); - } - } - - public RiakResult PbcWrite(MessageCode messageCode) - { - try - { - _socket.Write(messageCode); - return RiakResult.Success(); - } - catch (RiakException ex) - { - if (ex.NodeOffline) - { - Disconnect(); - } - return RiakResult.Error(ResultCode.CommunicationError, ex.Message, ex.NodeOffline); - } - catch(Exception ex) - { - Disconnect(); - return RiakResult.Error(ResultCode.CommunicationError, ex.Message, true); - } - } - - public RiakResult PbcWriteRead(TRequest request) - where TRequest : class - where TResult : class, new() - { - var writeResult = PbcWrite(request); - if(writeResult.IsSuccess) - { - return PbcRead(); - } - return RiakResult.Error(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); - } - - public RiakResult PbcWriteRead(TRequest request, MessageCode expectedMessageCode) - where TRequest : class - { - var writeResult = PbcWrite(request); - if(writeResult.IsSuccess) - { - return PbcRead(expectedMessageCode); - } - return RiakResult.Error(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); - } - - public RiakResult PbcWriteRead(MessageCode messageCode) - where TResult : class, new() - { - var writeResult = PbcWrite(messageCode); - if(writeResult.IsSuccess) - { - return PbcRead(); - } - return RiakResult.Error(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); - } - - public RiakResult PbcWriteRead(MessageCode messageCode, MessageCode expectedMessageCode) - { - var writeResult = PbcWrite(messageCode); - if(writeResult.IsSuccess) - { - return PbcRead(expectedMessageCode); - } - return RiakResult.Error(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); - } - - public RiakResult>> PbcWriteRead(TRequest request, - Func, bool> repeatRead) - where TRequest : class - where TResult : class, new() - { - var writeResult = PbcWrite(request); - if(writeResult.IsSuccess) - { - return PbcRepeatRead(repeatRead); - } - return RiakResult>>.Error(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); - } - - public RiakResult>> PbcWriteRead(MessageCode messageCode, - Func, bool> repeatRead) - where TResult : class, new() - { - var writeResult = PbcWrite(messageCode); - if(writeResult.IsSuccess) - { - return PbcRepeatRead(repeatRead); - } - return RiakResult>>.Error(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); - } - - public RiakResult>> PbcStreamRead(Func, bool> repeatRead, Action onFinish) - where TResult : class, new() - { - var streamer = PbcStreamReadIterator(repeatRead, onFinish); - return RiakResult>>.Success(streamer); - } - - private IEnumerable> PbcStreamReadIterator(Func, bool> repeatRead, Action onFinish) - where TResult : class, new() - { - RiakResult result; - - do - { - result = PbcRead(); - if(!result.IsSuccess) break; - yield return result; - } while(repeatRead(result)); - - // clean up first.. - onFinish(); - - // then return the failure to the client to indicate failure - yield return result; - } - - public RiakResult>> PbcWriteStreamRead(TRequest request, - Func, bool> repeatRead, Action onFinish) - where TRequest : class - where TResult : class, new() - { - var streamer = PbcWriteStreamReadIterator(request, repeatRead, onFinish); - return RiakResult>>.Success(streamer); - } - - public RiakResult>> PbcWriteStreamRead(MessageCode messageCode, - Func, bool> repeatRead, Action onFinish) - where TResult : class, new() - { - var streamer = PbcWriteStreamReadIterator(messageCode, repeatRead, onFinish); - return RiakResult>>.Success(streamer); - } - - private IEnumerable> PbcWriteStreamReadIterator(TRequest request, - Func, bool> repeatRead, Action onFinish) - where TRequest : class - where TResult : class, new() - { - var writeResult = PbcWrite(request); - if(writeResult.IsSuccess) - { - return PbcStreamReadIterator(repeatRead, onFinish); - } - onFinish(); - return new[] { RiakResult.Error(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline) }; - } - - private IEnumerable> PbcWriteStreamReadIterator(MessageCode messageCode, - Func, bool> repeatRead, Action onFinish) - where TResult : class, new() - { - var writeResult = PbcWrite(messageCode); - if(writeResult.IsSuccess) - { - return PbcStreamReadIterator(repeatRead, onFinish); - } - onFinish(); - return new[] { RiakResult.Error(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline) }; - } - - public RiakResult RestRequest(RiakRestRequest request) - { - var baseUri = new StringBuilder(_restRootUrl).Append(request.Uri); - if(request.QueryParams.Count > 0) - { - baseUri.Append("?"); - var first = request.QueryParams.First(); - baseUri.Append(first.Key.UrlEncoded()).Append("=").Append(first.Value.UrlEncoded()); - request.QueryParams.Skip(1).ForEach(kv => baseUri.Append("&").Append(kv.Key.UrlEncoded()).Append("=").Append(kv.Value.UrlEncoded())); - } - var targetUri = new Uri(baseUri.ToString()); - - var req = (HttpWebRequest)WebRequest.Create(targetUri); - req.KeepAlive = true; - req.Method = request.Method; - req.Credentials = CredentialCache.DefaultCredentials; - - if(!string.IsNullOrWhiteSpace(request.ContentType)) - { - req.ContentType = request.ContentType; - } - - if(!request.Cache) - { - req.Headers.Set(RiakConstants.Rest.HttpHeaders.DisableCacheKey, RiakConstants.Rest.HttpHeaders.DisableCacheValue); - } - - request.Headers.ForEach(h => req.Headers.Set(h.Key, h.Value)); - - if(request.Body != null && request.Body.Length > 0) - { - req.ContentLength = request.Body.Length; - using(var writer = req.GetRequestStream()) - { - writer.Write(request.Body, 0, request.Body.Length); - } - } - else - { - req.ContentLength = 0; - } - - try - { - var response = (HttpWebResponse)req.GetResponse(); - - var result = new RiakRestResponse - { - ContentLength = response.ContentLength, - ContentType = response.ContentType, - StatusCode = response.StatusCode, - Headers = response.Headers.AllKeys.ToDictionary(k => k, k => response.Headers[k]), - ContentEncoding = !string.IsNullOrWhiteSpace(response.ContentEncoding) - ? Encoding.GetEncoding(response.ContentEncoding) - : Encoding.Default - }; - - if (response.ContentLength > 0) - { - using (var responseStream = response.GetResponseStream()) - { - if (responseStream != null) - { - using (var reader = new StreamReader(responseStream, result.ContentEncoding)) - { - result.Body = reader.ReadToEnd(); - } - } - } - } - - return RiakResult.Success(result); - } - catch (RiakException ex) - { - return RiakResult.Error(ResultCode.CommunicationError, ex.Message, ex.NodeOffline); - } - catch (WebException ex) - { - if (ex.Status == WebExceptionStatus.ProtocolError) - { - return RiakResult.Error(ResultCode.HttpError, ex.Message, false); - } - - return RiakResult.Error(ResultCode.HttpError, ex.Message, true); - } - catch (Exception ex) - { - return RiakResult.Error(ResultCode.CommunicationError, ex.Message, true); - } - } - - private static bool ServerValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) - { - return true; - } - - public void Dispose() - { - _socket.Dispose(); - Disconnect(); - } - - public void Disconnect() - { - _socket.Disconnect(); - } - } -} +// Copyright (c) 2011 - OJ Reeves & Jeremiah Peschka +// +// This file is provided to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using CorrugatedIron.Config; +using CorrugatedIron.Exceptions; +using CorrugatedIron.Extensions; +using CorrugatedIron.Messages; +using CorrugatedIron.Models.Rest; +using CorrugatedIron.Util; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace CorrugatedIron.Comms +{ + public interface IRiakConnection : IDisposable + { + bool IsIdle { get; } + + void Disconnect(); + + // PBC interface + Task> PbcRead() + where TResult : class, new(); + + Task PbcRead(MessageCode expectedMessageCode); + + Task PbcWrite(TRequest request) + where TRequest : class; + + Task PbcWrite(MessageCode messageCode); + + Task> PbcWriteRead(TRequest request) + where TRequest : class + where TResult : class, new(); + + Task> PbcWriteRead(MessageCode messageCode) + where TResult : class, new(); + + Task PbcWriteRead(TRequest request, MessageCode expectedMessageCode) + where TRequest : class; + + Task PbcWriteRead(MessageCode messageCode, MessageCode expectedMessageCode); + + Task>>> PbcRepeatRead(Func, bool> repeatRead) + where TResult : class, new(); + + Task>>> PbcWriteRead(MessageCode messageCode, Func, bool> repeatRead) + where TResult : class, new(); + + Task>>> PbcWriteRead(TRequest request, Func, bool> repeatRead) + where TRequest : class + where TResult : class, new(); + + Task>>> PbcStreamRead(Func, bool> repeatRead, Action onFinish) + where TResult : class, new(); + + Task>>> PbcWriteStreamRead(TRequest request, + Func, bool> repeatRead, Action onFinish) + where TRequest : class + where TResult : class, new(); + + Task>>> PbcWriteStreamRead(MessageCode messageCode, + Func, bool> repeatRead, Action onFinish) + where TResult : class, new(); + + // REST interface + Task> RestRequest(RiakRestRequest request); + } + + // riak connection + internal class RiakConnection : IRiakConnection + { + private readonly string _restRootUrl; + private readonly RiakPbcSocket _socket; + + public RiakConnection(IRiakNodeConfiguration nodeConfiguration) + { + _restRootUrl = @"{0}://{1}:{2}".Fmt(nodeConfiguration.RestScheme, nodeConfiguration.HostAddress, nodeConfiguration.RestPort); + _socket = new RiakPbcSocket(nodeConfiguration.HostAddress, nodeConfiguration.PbcPort, nodeConfiguration.NetworkReadTimeout, + nodeConfiguration.NetworkWriteTimeout); + } + + public void Disconnect() + { + _socket.Disconnect(); + } + + public Task> PbcRead() where TResult : class, new() + { + return _socket.ReadAsync() + .ContinueWith((Task finishedTask) => { + if (!finishedTask.IsFaulted) + return RiakResult.Success(finishedTask.Result); + else + return GetExceptionResult(finishedTask.Exception); + }); + } + + public Task PbcRead(MessageCode expectedMessageCode) + { + return _socket.ReadAsync(expectedMessageCode) + .ContinueWith((Task finishedTask) => { + if (!finishedTask.IsFaulted) + return RiakResult.Success(); + else + return GetExceptionResult(finishedTask.Exception); + }); + } + + public Task PbcWrite(TRequest request) where TRequest : class + { + return _socket.WriteAsync(request) + .ContinueWith((Task finishedTask) => { + if (!finishedTask.IsFaulted) + return RiakResult.Success(); + else + return GetExceptionResult(finishedTask.Exception); + }); + } + + public Task PbcWrite(MessageCode messageCode) + { + return _socket.WriteAsync(messageCode) + .ContinueWith((Task finishedTask) => { + if (!finishedTask.IsFaulted) + return RiakResult.Success(); + else + return GetExceptionResult(finishedTask.Exception); + }); + } + + public Task> PbcWriteRead(TRequest request) where TRequest : class where TResult : class, new() + { + return PbcWrite(request) + .ContinueWith((Task writeTask) => { + var writeResult = writeTask.Result; + if (writeResult.IsSuccess) + { + return PbcRead(); + } + return RiakResult.ErrorTask(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); + }).Unwrap(); + } + + public Task> PbcWriteRead(MessageCode messageCode) where TResult : class, new() + { + return PbcWrite(messageCode) + .ContinueWith((Task writeTask) => { + var writeResult = writeTask.Result; + if (writeResult.IsSuccess) + { + return PbcRead(); + } + return RiakResult.ErrorTask(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); + }).Unwrap(); + } + + public Task PbcWriteRead(TRequest request, MessageCode expectedMessageCode) where TRequest : class + { + return PbcWrite(request) + .ContinueWith((Task writeTask) => { + var writeResult = writeTask.Result; + if (writeResult.IsSuccess) + { + return PbcRead(expectedMessageCode); + } + return RiakResult.ErrorTask(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); + }).Unwrap(); + } + + public Task PbcWriteRead(MessageCode messageCode, MessageCode expectedMessageCode) + { + return PbcWrite(messageCode) + .ContinueWith((Task writeTask) => { + var writeResult = writeTask.Result; + if (writeResult.IsSuccess) + { + return PbcRead(expectedMessageCode); + } + return RiakResult.ErrorTask(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); + }).Unwrap(); + } + + public Task>>> PbcRepeatRead(Func, bool> repeatRead) where TResult : class, new() + { + var source = new TaskCompletionSource>>>(); + var resultsList = new List>(); + + // repeat as a continuation + Action readNext = null; + readNext = (() => { + _socket.ReadAsync() + .ContinueWith((Task readTask) => { + var result = RiakResult.Success(readTask.Result); + resultsList.Add(result); + + try + { + if (repeatRead(result)) + { + readNext(); + } + else + { + source.SetResult(RiakResult>>.Success(resultsList)); + } + } + catch (Exception ex) + { + source.SetException(ex); + } + }); + }); + + // begin reading and completion task + readNext(); + return source.Task; + } + + public Task>>> PbcWriteRead(MessageCode messageCode, Func, bool> repeatRead) where TResult : class, new() + { + return PbcWrite(messageCode) + .ContinueWith((Task writeTask) => { + var writeResult = writeTask.Result; + if (writeResult.IsSuccess) + { + return PbcRepeatRead(repeatRead); + } + return RiakResult>>.ErrorTask(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); + }).Unwrap(); + } + + public Task>>> PbcWriteRead(TRequest request, Func, bool> repeatRead) where TRequest : class where TResult : class, new() + { + return PbcWrite(request) + .ContinueWith((Task writeTask) => { + var writeResult = writeTask.Result; + if (writeResult.IsSuccess) + { + return PbcRepeatRead(repeatRead); + } + return RiakResult>>.ErrorTask(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); + }).Unwrap(); + } + + public Task>>> PbcStreamRead(Func, bool> repeatRead, Action onFinish) where TResult : class, new() + { + var streamer = PbcStreamReadIterator(repeatRead, onFinish); + return RiakResult>>.SuccessTask(streamer); + } + + public Task>>> PbcWriteStreamRead(TRequest request, Func, bool> repeatRead, Action onFinish) where TRequest : class where TResult : class, new() + { + return PbcWrite(request) + .ContinueWith((Task writeTask) => { + var writeResult = writeTask.Result; + if (writeResult.IsSuccess) + { + var streamer = PbcStreamReadIterator(repeatRead, onFinish); + return RiakResult>>.Success(streamer); + } + return RiakResult>>.Error(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); + }); + } + + public Task>>> PbcWriteStreamRead(MessageCode messageCode, Func, bool> repeatRead, Action onFinish) where TResult : class, new() + { + return PbcWrite(messageCode) + .ContinueWith((Task writeTask) => { + var writeResult = writeTask.Result; + if (writeResult.IsSuccess) + { + var streamer = PbcStreamReadIterator(repeatRead, onFinish); + return RiakResult>>.Success(streamer); + } + return RiakResult>>.Error(writeResult.ResultCode, writeResult.ErrorMessage, writeResult.NodeOffline); + }); + } + + public Task> RestRequest(RiakRestRequest request) + { + var baseUri = new StringBuilder(_restRootUrl).Append(request.Uri); + if(request.QueryParams.Count > 0) + { + baseUri.Append("?"); + var first = request.QueryParams.First(); + baseUri.Append(first.Key.UrlEncoded()).Append("=").Append(first.Value.UrlEncoded()); + request.QueryParams.Skip(1).ForEach(kv => baseUri.Append("&").Append(kv.Key.UrlEncoded()).Append("=").Append(kv.Value.UrlEncoded())); + } + var targetUri = new Uri(baseUri.ToString()); + + var req = (HttpWebRequest)WebRequest.Create(targetUri); + req.KeepAlive = true; + req.Method = request.Method; + req.Credentials = CredentialCache.DefaultCredentials; + + if(!string.IsNullOrWhiteSpace(request.ContentType)) + { + req.ContentType = request.ContentType; + } + + if(!request.Cache) + { + req.Headers.Set(RiakConstants.Rest.HttpHeaders.DisableCacheKey, RiakConstants.Rest.HttpHeaders.DisableCacheValue); + } + + request.Headers.ForEach(h => req.Headers.Set(h.Key, h.Value)); + + if(request.Body != null && request.Body.Length > 0) + { + req.ContentLength = request.Body.Length; + using(var writer = req.GetRequestStream()) + { + writer.Write(request.Body, 0, request.Body.Length); + } + } + else + { + req.ContentLength = 0; + } + + // process response + var asyncResult = req.BeginGetResponse(null, null); + return Task.Factory.FromAsync(asyncResult, req.EndGetResponse) + .ContinueWith((Task responseTask) => { + + // rethrow exception on fault + if (responseTask.IsFaulted) + { + throw responseTask.Exception; + } + + // pull out response + var response = (HttpWebResponse)responseTask.Result; + var result = new RiakRestResponse() + { + ContentLength = response.ContentLength, + ContentType = response.ContentType, + StatusCode = response.StatusCode, + Headers = response.Headers.AllKeys.ToDictionary(k => k, k => response.Headers[k]), + ContentEncoding = !string.IsNullOrWhiteSpace(response.ContentEncoding) + ? Encoding.GetEncoding(response.ContentEncoding) + : Encoding.Default + }; + + if (response.ContentLength > 0) + { + using (var responseStream = response.GetResponseStream()) + { + if (responseStream != null) + { + return ReadStreamAsync(responseStream, (int)response.ContentLength) + .ContinueWith((Task readTask) => { + result.Body = result.ContentEncoding.GetString(readTask.Result); + return result; + }); + } + } + } + + // end of the line + throw new RiakException("Couldn't read HTTP response", false); + }).Unwrap() + .ContinueWith((Task responseTask) => { + if (!responseTask.IsFaulted) + { + return RiakResult.Success(responseTask.Result); + } + else + { + var exceptions = responseTask.Exception.Flatten().InnerExceptions; + var rEx = exceptions.OfType().FirstOrDefault(); + var wEx = exceptions.OfType().FirstOrDefault(); + + // process exceptions + if (rEx != null) + { + return RiakResult.Error(ResultCode.CommunicationError, rEx.Message, rEx.NodeOffline); + } + else if (wEx != null) + { + if (wEx.Status == WebExceptionStatus.ProtocolError) + { + return RiakResult.Error(ResultCode.HttpError, wEx.Message, false); + } + + return RiakResult.Error(ResultCode.HttpError, wEx.Message, true); + } + else + { + var ex = exceptions.FirstOrDefault(); + return RiakResult.Error(ResultCode.CommunicationError, ex.Message, true); + } + } + }); + } + + public bool IsIdle + { + get { return _socket.IsConnected; } + } + + public void Dispose() + { + Disconnect(); + _socket.Dispose(); + } + + // read as a stream - will add bits and pieces as they become available + private IEnumerable> PbcStreamReadIterator(Func, bool> repeatRead, Action onFinish) + where TResult : class, new() + { + RiakResult result; + + do + { + + // block wait for result + var task = PbcRead(); + task.Wait(); + result = task.Result; + + // sync behaviour + if(!result.IsSuccess) + { + break; + } + else + { + yield return result; + } + } while(repeatRead(result)); + + // clean up first.. + onFinish(); + + // then return the failure to the client to indicate failure + yield return result; + } + + // tidy up messy exceptions + private RiakResult GetExceptionResult(AggregateException aggregateException) + { + return GetExceptionResult(aggregateException); + } + + // read incoming stream asynchronously + private Task ReadStreamAsync(Stream stream, int length) + { + var source = new TaskCompletionSource(); + var data = new byte[length]; + var maxBufferSize = 1024 * 4; + var bytesToRetrieve = length; + var position = 0; + + // continuation to read + Action readNextChunk; + readNextChunk = (() => { + var startRead = stream.BeginRead(data, + position, + Math.Min(bytesToRetrieve, maxBufferSize), + null, + null); + Task.Factory.FromAsync(startRead, stream.EndRead) + .ContinueWith((Task finishedTask) => { + if (finishedTask.IsFaulted) + { + source.SetException(finishedTask.Exception); + } + else + { + if (finishedTask.Result == 0) + { + source.SetException(new RiakException("Timed Out")); + } + else + { + position += finishedTask.Result; + bytesToRetrieve -= finishedTask.Result; + if (bytesToRetrieve == 0) + { + source.SetResult(data); + } + else + { + readNextChunk(); + } + } + } + }); + }); + + // start reading + readNextChunk(); + return source.Task; + } + + // tidy up messy exceptions + private RiakResult GetExceptionResult(AggregateException aggregateException) + { + var exceptions = aggregateException.Flatten().InnerExceptions; + var rEx = exceptions.OfType().FirstOrDefault(); + var fEx = exceptions.FirstOrDefault(); + if (rEx != null) + { + if (rEx.NodeOffline) + { + Disconnect(); + } + return RiakResult.Error(ResultCode.CommunicationError, rEx.Message, rEx.NodeOffline); + } + else + { + Disconnect(); + return RiakResult.Error(ResultCode.CommunicationError, fEx.Message, true); + } + } + } +} diff --git a/CorrugatedIron/Comms/RiakConnectionPool.cs b/CorrugatedIron/Comms/RiakConnectionPool.cs index 580db02a..535eae60 100644 --- a/CorrugatedIron/Comms/RiakConnectionPool.cs +++ b/CorrugatedIron/Comms/RiakConnectionPool.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Threading.Tasks; namespace CorrugatedIron.Comms { @@ -41,64 +42,56 @@ public RiakConnectionPool(IRiakNodeConfiguration nodeConfig, IRiakConnectionFact } } - public Tuple Consume(Func consumer) + public Task> Consume(Func> consumer) { - if(_disposing) return Tuple.Create(false, default(TResult)); + if(_disposing) + return TaskResult(Tuple.Create(false, default(TResult))); IRiakConnection instance = null; - try + if(_resources.TryPop(out instance)) { - if(_resources.TryPop(out instance)) - { - var result = consumer(instance); - return Tuple.Create(true, result); - } - } - catch(Exception) - { - return Tuple.Create(false, default(TResult)); + return consumer(instance).ContinueWith((Task finishedTask) => { + if (instance != null) + _resources.Push(instance); + + // finshed task + if (!finishedTask.IsFaulted) + return Tuple.Create(true, finishedTask.Result); + else + return Tuple.Create(false, default(TResult)); + }); } - finally + else { - if(instance != null) - { - _resources.Push(instance); - } + return TaskResult(Tuple.Create(false, default(TResult))); } - - return Tuple.Create(false, default(TResult)); } - public Tuple DelayedConsume(Func consumer) + public Task> DelayedConsume(Func> consumer) { - if(_disposing) return Tuple.Create(false, default(TResult)); + if(_disposing) + return TaskResult(Tuple.Create(false, default(TResult))); IRiakConnection instance = null; - try + if(_resources.TryPop(out instance)) { - if(_resources.TryPop(out instance)) - { - Action cleanup = () => - { - var i = instance; - instance = null; - _resources.Push(i); - }; - - var result = consumer(instance, cleanup); - return Tuple.Create(true, result); - } + Action cleanup = (() => { + if (instance != null) + _resources.Push(instance); + }); + return consumer(instance, cleanup).ContinueWith((Task finishedTask) => { + + // finshed task + if (!finishedTask.IsFaulted) + return Tuple.Create(true, finishedTask.Result); + else + return Tuple.Create(false, default(TResult)); + }); } - catch(Exception) + else { - if(instance != null) - { - _resources.Push(instance); - } - return Tuple.Create(false, default(TResult)); + return TaskResult(Tuple.Create(false, default(TResult))); } - - return Tuple.Create(false, default(TResult)); } public void Dispose() @@ -112,5 +105,13 @@ public void Dispose() conn.Dispose(); } } + + // wrap a task result + private Task TaskResult(T result) + { + var source = new TaskCompletionSource(); + source.SetResult(result); + return source.Task; + } } } diff --git a/CorrugatedIron/Comms/RiakNode.cs b/CorrugatedIron/Comms/RiakNode.cs index 0231ec48..f04eb6fe 100644 --- a/CorrugatedIron/Comms/RiakNode.cs +++ b/CorrugatedIron/Comms/RiakNode.cs @@ -1,93 +1,112 @@ -// Copyright (c) 2011 - OJ Reeves & Jeremiah Peschka -// -// This file is provided to you under the Apache License, -// Version 2.0 (the "License"); you may not use this file -// except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -using CorrugatedIron.Config; -using System; -using System.Collections.Generic; - -namespace CorrugatedIron.Comms -{ - public interface IRiakNode : IDisposable - { - RiakResult UseConnection(Func useFun); - RiakResult UseConnection(Func> useFun); - - RiakResult> UseDelayedConnection(Func>> useFun) - where TResult : RiakResult; - } - - public class RiakNode : IRiakNode - { - private readonly IRiakConnectionManager _connections; - private bool _disposing; - - public RiakNode(IRiakNodeConfiguration nodeConfiguration, IRiakConnectionFactory connectionFactory) - { - // assume that if the node has a pool size of 0 then the intent is to have the connections - // made on the fly - if (nodeConfiguration.PoolSize == 0) - { - _connections = new RiakOnTheFlyConnection(nodeConfiguration, connectionFactory); - } - else - { - _connections = new RiakConnectionPool(nodeConfiguration, connectionFactory); - } - } - - public RiakResult UseConnection(Func useFun) - { - return UseConnection(useFun, RiakResult.Error); - } - - public RiakResult UseConnection(Func> useFun) - { - return UseConnection(useFun, RiakResult.Error); - } - - private TRiakResult UseConnection(Func useFun, Func onError) - where TRiakResult : RiakResult - { - if(_disposing) return onError(ResultCode.ShuttingDown, "Connection is shutting down", true); - - var response = _connections.Consume(useFun); - if(response.Item1) - { - return response.Item2; - } - return onError(ResultCode.NoConnections, "Unable to acquire connection", true); - } - - public RiakResult> UseDelayedConnection(Func>> useFun) - where TResult : RiakResult - { - if(_disposing) return RiakResult>.Error(ResultCode.ShuttingDown, "Connection is shutting down", true); - - var response = _connections.DelayedConsume(useFun); - if(response.Item1) - { - return response.Item2; - } - return RiakResult>.Error(ResultCode.NoConnections, "Unable to acquire connection", true); - } - - public void Dispose() - { - _disposing = true; - _connections.Dispose(); - } - } +// Copyright (c) 2011 - OJ Reeves & Jeremiah Peschka +// +// This file is provided to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using CorrugatedIron.Config; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace CorrugatedIron.Comms +{ + public interface IRiakNode : IDisposable + { + Task UseConnection(Func> useFun); + Task> UseConnection(Func>> useFun); + + Task>> UseDelayedConnection(Func>>> useFun) + where TResult : RiakResult; + } + + public class RiakNode : IRiakNode + { + private readonly IRiakConnectionManager _connections; + private bool _disposing; + + public RiakNode(IRiakNodeConfiguration nodeConfiguration, IRiakConnectionFactory connectionFactory) + { + // assume that if the node has a pool size of 0 then the intent is to have the connections + // made on the fly + if (nodeConfiguration.PoolSize == 0) + { + _connections = new RiakOnTheFlyConnection(nodeConfiguration, connectionFactory); + } + else + { + _connections = new RiakConnectionPool(nodeConfiguration, connectionFactory); + } + } + + public Task UseConnection(Func> useFun) + { + return UseConnection(useFun, RiakResult.Error); + } + + public Task> UseConnection(Func>> useFun) + { + return UseConnection(useFun, RiakResult.Error); + } + + private Task UseConnection(Func> useFun, Func onError) + where TRiakResult : RiakResult + { + if(_disposing) + return TaskResult(onError(ResultCode.ShuttingDown, "Connection is shutting down", true)); + + // attempt consume + return _connections.Consume(useFun) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + if(result.Item1) + { + return result.Item2; + } + return onError(ResultCode.NoConnections, "Unable to acquire connection", true); + }); + } + + public Task>> UseDelayedConnection(Func>>> useFun) + where TResult : RiakResult + { + if(_disposing) + return TaskResult(RiakResult>.Error(ResultCode.ShuttingDown, "Connection is shutting down", true)); + + // attempt consume + return _connections.DelayedConsume(useFun) + .ContinueWith((Task>>> finishedTask) => { + var result = finishedTask.Result; + if(result.Item1) + { + return result.Item2; + } + return RiakResult>.Error(ResultCode.NoConnections, "Unable to acquire connection", true); + }); + } + + public void Dispose() + { + _disposing = true; + _connections.Dispose(); + } + + // wrap a task result + private Task TaskResult(T result) + { + var source = new TaskCompletionSource(); + source.SetResult(result); + return source.Task; + } + } } \ No newline at end of file diff --git a/CorrugatedIron/Comms/RiakOnTheFlyConnection.cs b/CorrugatedIron/Comms/RiakOnTheFlyConnection.cs index ace403d5..6b105b43 100644 --- a/CorrugatedIron/Comms/RiakOnTheFlyConnection.cs +++ b/CorrugatedIron/Comms/RiakOnTheFlyConnection.cs @@ -16,6 +16,7 @@ using CorrugatedIron.Config; using System; +using System.Threading.Tasks; namespace CorrugatedIron.Comms { @@ -31,44 +32,42 @@ public RiakOnTheFlyConnection(IRiakNodeConfiguration nodeConfig, IRiakConnection _connFactory = connFactory; } - public Tuple Consume(Func consumer) + public Task> Consume(Func> consumer) { - if(_disposing) return Tuple.Create(false, default(TResult)); + if(_disposing) + return TaskResult(Tuple.Create(false, default(TResult))); - using (var conn = _connFactory.CreateConnection(_nodeConfig)) - { - try - { - var result = consumer(conn); - return Tuple.Create(true, result); - } - catch(Exception) - { + // connection on the fly + var conn = _connFactory.CreateConnection(_nodeConfig); + return consumer(conn).ContinueWith((Task finishedTask) => { + if (conn != null) + conn.Dispose(); + + if (!finishedTask.IsFaulted) + return Tuple.Create(true, finishedTask.Result); + else return Tuple.Create(false, default(TResult)); - } - } + }); } - public Tuple DelayedConsume(Func consumer) + // consume delayed + public Task> DelayedConsume(Func> consumer) { - if(_disposing) return Tuple.Create(false, default(TResult)); - - IRiakConnection conn = null; + if(_disposing) + return TaskResult(Tuple.Create(false, default(TResult))); - try - { - conn = _connFactory.CreateConnection(_nodeConfig); - var result = consumer(conn, conn.Dispose); - return Tuple.Create(true, result); - } - catch(Exception) - { + // connection on the fly + var conn = _connFactory.CreateConnection(_nodeConfig); + Action cleanup = (() => { if (conn != null) - { conn.Dispose(); - } - return Tuple.Create(false, default(TResult)); - } + }); + return consumer(conn, cleanup).ContinueWith((Task finishedTask) => { + if (!finishedTask.IsFaulted) + return Tuple.Create(true, finishedTask.Result); + else + return Tuple.Create(false, default(TResult)); + }); } public void Dispose() @@ -77,5 +76,13 @@ public void Dispose() _disposing = true; } + + // wrap a task result + private Task TaskResult(T result) + { + var source = new TaskCompletionSource(); + source.SetResult(result); + return source.Task; + } } } diff --git a/CorrugatedIron/Comms/RiakPbcSocket.cs b/CorrugatedIron/Comms/RiakPbcSocket.cs index dedf96b2..a1914396 100644 --- a/CorrugatedIron/Comms/RiakPbcSocket.cs +++ b/CorrugatedIron/Comms/RiakPbcSocket.cs @@ -23,6 +23,7 @@ using System.IO; using System.Net; using System.Net.Sockets; +using System.Threading.Tasks; namespace CorrugatedIron.Comms { @@ -108,7 +109,7 @@ public RiakPbcSocket(string server, int port, int receiveTimeout, int sendTimeou _sendTimeout = sendTimeout; } - public void Write(MessageCode messageCode) + public Task WriteAsync(MessageCode messageCode) { const int sizeSize = sizeof(int); const int codeSize = sizeof(byte); @@ -120,137 +121,225 @@ public void Write(MessageCode messageCode) Array.Copy(size, messageBody, sizeSize); messageBody[sizeSize] = (byte)messageCode; - if(PbcSocket.Send(messageBody, headerSize, SocketFlags.None) == 0) - { - throw new RiakException("Failed to send data to server - Timed Out: {0}:{1}".Fmt(_server, _port)); - } + // send content + return SocketWriteAsync(messageBody); } - public void Write(T message) where T : class + public Task WriteAsync(T message) where T : class { const int sizeSize = sizeof(int); const int codeSize = sizeof(byte); const int headerSize = sizeSize + codeSize; - const int sendBufferSize = 1024 * 4; - byte[] messageBody; - long messageLength = 0; + // write to bytes + byte[] data = null; using(var memStream = new MemoryStream()) { // add a buffer to the start of the array to put the size and message code memStream.Position += headerSize; Serializer.Serialize(memStream, message); - messageBody = memStream.GetBuffer(); - messageLength = memStream.Position; - } - - // check to make sure something was written, otherwise we'll have to create a new array - if(messageLength == headerSize) - { - messageBody = new byte[headerSize]; + data = memStream.ToArray(); } + // fill header var messageCode = TypeToMessageCodeMap[typeof(T)]; - var size = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)messageLength - headerSize + 1)); - Array.Copy(size, messageBody, sizeSize); - messageBody[sizeSize] = (byte)messageCode; + var sizeBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(data.Length - headerSize + 1)); + Array.Copy(sizeBytes, data, sizeSize); + data[sizeSize] = (byte)messageCode; - var bytesToSend = (int)messageLength; - var position = 0; + // send content + return SocketWriteAsync(data); + } - while (bytesToSend > 0) - { - var sent = PbcSocket.Send(messageBody, position, Math.Min(bytesToSend, sendBufferSize), SocketFlags.None); - if (sent == 0) - { - throw new RiakException("Failed to send data to server - Timed Out: {0}:{1}".Fmt(_server, _port)); - } - position += sent; - bytesToSend -= sent; - } + public Task ReadAsync(MessageCode expectedCode) + { + return SocketReadAsync(5) + .ContinueWith((Task readHeaderTask) => { + var header = readHeaderTask.Result; + var size = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(header, 0)); + var messageCode = (MessageCode)header[sizeof(int)]; + + if(messageCode == MessageCode.ErrorResp) + { + return DeserializeInstanceAsync(size) + .ContinueWith((Task finishedTask) => { + var error = finishedTask.Result; + throw new RiakException(error.errcode, error.errmsg.FromRiakString(), false); + }); + } + + if (expectedCode != messageCode) + { + throw new RiakException("Expected return code {0} received {1}".Fmt(expectedCode, messageCode)); + } + + return TaskResult(messageCode); + }).Unwrap(); } - public MessageCode Read(MessageCode expectedCode) + public Task ReadAsync() where T : new() { - var header = ReceiveAll(new byte[5]); - var size = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(header, 0)); - var messageCode = (MessageCode)header[sizeof(int)]; + return SocketReadAsync(5) + .ContinueWith((Task readHeaderTask) => { + var header = readHeaderTask.Result; + var size = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(header, 0)); + var messageCode = (MessageCode)header[sizeof(int)]; - if(messageCode == MessageCode.ErrorResp) - { - var error = DeserializeInstance(size); - throw new RiakException(error.errcode, error.errmsg.FromRiakString(), false); - } + if (messageCode == MessageCode.ErrorResp) + { + return DeserializeInstanceAsync(size) + .ContinueWith((Task finishedTask) => { + var error = finishedTask.Result; + throw new RiakException(error.errcode, error.errmsg.FromRiakString(), false); + }); + } + + if (!MessageCodeToTypeMap.ContainsKey(messageCode)) + { + throw new RiakInvalidDataException((byte)messageCode); + } - if (expectedCode != messageCode) +#if DEBUG + // This message code validation is here to make sure that the caller + // is getting exactly what they expect. This "could" be removed from + // production code, but it's a good thing to have in here for dev. + if(MessageCodeToTypeMap[messageCode] != typeof(T)) + { + throw new InvalidOperationException(string.Format("Attempt to decode message to type '{0}' when received type '{1}'.", typeof(T).Name, MessageCodeToTypeMap[messageCode].Name)); + } +#endif + + return DeserializeInstanceAsync(size); + }).Unwrap(); + } + + private Task DeserializeInstanceAsync(int size) where T : new() + { + if (size <= 1) { - throw new RiakException("Expected return code {0} received {1}".Fmt(expectedCode, messageCode)); + return TaskResult(new T()); } - return messageCode; + return SocketReadAsync(size - 1) + .ContinueWith((Task finishedTask) => { + using (var memStream = new MemoryStream(finishedTask.Result)) + { + return Serializer.Deserialize(memStream); + } + }); } - public T Read() where T : new() + // write to socket async + private Task SocketWriteAsync(byte[] data) { - var header = ReceiveAll(new byte[5]); - - var size = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(header, 0)); + var source = new TaskCompletionSource(); - var messageCode = (MessageCode)header[sizeof(int)]; - if(messageCode == MessageCode.ErrorResp) - { - var error = DeserializeInstance(size); - throw new RiakException(error.errcode, error.errmsg.FromRiakString()); - } + // start writing + var maxBufferSize = 1024 * 4; + var bytesToSend = data.Length; + var position = 0; - if(!MessageCodeToTypeMap.ContainsKey(messageCode)) - { - throw new RiakInvalidDataException((byte)messageCode); - } -#if DEBUG - // This message code validation is here to make sure that the caller - // is getting exactly what they expect. This "could" be removed from - // production code, but it's a good thing to have in here for dev. - if(MessageCodeToTypeMap[messageCode] != typeof(T)) - { - throw new InvalidOperationException(string.Format("Attempt to decode message to type '{0}' when received type '{1}'.", typeof(T).Name, MessageCodeToTypeMap[messageCode].Name)); - } -#endif - return DeserializeInstance(size); + // write chunks as a continuation behaviour + Action writeChunkAsync = null; + writeChunkAsync = (() => { + var startWrite = PbcSocket.BeginSend(data, position, Math.Min(bytesToSend, maxBufferSize), + SocketFlags.None, null, 0); + Task.Factory.FromAsync(startWrite, PbcSocket.EndSend) + .ContinueWith((Task writeTask) => { + if (writeTask.IsFaulted) + { + source.SetException(writeTask.Exception); + } + else + { + if (writeTask.Result == 0) + { + source.SetException( + new RiakException("Failed to send data to server - Timed Out: {0}:{1}" + .Fmt(_server, _port))); + } + else + { + + // continue if necessary + position += writeTask.Result; + bytesToSend -= writeTask.Result; + if (bytesToSend > 0) + { + writeChunkAsync(); + } + else + { + source.SetResult(new object()); + } + } + } + }); + }); + + // start writing and give back deferred task + writeChunkAsync(); + return source.Task; } - private byte[] ReceiveAll(byte[] resultBuffer) + // read from socket async + private Task SocketReadAsync(int size) { + var source = new TaskCompletionSource(); + var data = new byte[size]; + + // start reading PbcSocket.ReceiveTimeout = 0; int totalBytesReceived = 0; - int lengthToReceive = resultBuffer.Length; - while(lengthToReceive > 0) - { - int bytesReceived = PbcSocket.Receive(resultBuffer, totalBytesReceived, lengthToReceive, 0); - if(bytesReceived == 0) - { - throw new RiakException("Unable to read data from the source stream - Timed Out."); - } - totalBytesReceived += bytesReceived; - lengthToReceive -= bytesReceived; - } - return resultBuffer; + int lengthToReceive = size; + + // read in chunks as a continuation behaviour + Action readChunkAsync = null; + readChunkAsync = (() => { + var startRead = PbcSocket.BeginReceive(data, totalBytesReceived, lengthToReceive, + SocketFlags.None, null, 0); + Task.Factory.FromAsync(startRead, PbcSocket.EndReceive) + .ContinueWith((Task readTask) => { + if (readTask.IsFaulted) + { + source.SetException(readTask.Exception); + } + else + { + if (readTask.Result == 0) + { + source.SetException( + new RiakException("Unable to read data from the source stream - Timed Out.")); + } + else + { + + // continue if necessary + totalBytesReceived += readTask.Result; + lengthToReceive -= readTask.Result; + if (lengthToReceive > 0) + { + readChunkAsync(); + } + else + { + source.SetResult(data); + } + } + } + }); + }); + + // start reading and give back delayed buffer task + readChunkAsync(); + return source.Task; } - private T DeserializeInstance(int size) - where T : new() + private Task TaskResult(T result) { - if(size <= 1) - { - return new T(); - } - - var resultBuffer = ReceiveAll(new byte[size - 1]); - - using(var memStream = new MemoryStream(resultBuffer)) - { - return Serializer.Deserialize(memStream); - } + var source = new TaskCompletionSource(); + source.SetResult(result); + return source.Task; } public void Disconnect() diff --git a/CorrugatedIron/CorrugatedIron.csproj b/CorrugatedIron/CorrugatedIron.csproj index f56fb049..79f362ef 100644 --- a/CorrugatedIron/CorrugatedIron.csproj +++ b/CorrugatedIron/CorrugatedIron.csproj @@ -41,14 +41,6 @@ ..\Metadata\CorrugatedIron.snk - - False - ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll - - - False - ..\packages\protobuf-net.2.0.0.621\lib\net40\protobuf-net.dll - @@ -59,6 +51,12 @@ + + ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll + + + ..\packages\protobuf-net.2.0.0.621\lib\net40\protobuf-net.dll + @@ -247,6 +245,8 @@ + + diff --git a/CorrugatedIron/IRiakAsyncBatchClient.cs b/CorrugatedIron/IRiakAsyncBatchClient.cs new file mode 100644 index 00000000..e53aa7bb --- /dev/null +++ b/CorrugatedIron/IRiakAsyncBatchClient.cs @@ -0,0 +1,77 @@ +// Copyright (c) 2011 - OJ Reeves & Jeremiah Peschka +// +// This file is provided to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using CorrugatedIron.Models; +using CorrugatedIron.Models.MapReduce; +using CorrugatedIron.Models.Search; +using CorrugatedIron.Util; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace CorrugatedIron +{ + public interface IRiakBatchAsyncClient + { + int RetryCount { get; set; } + Task Ping(); + + Task> Get(string bucket, string key, RiakGetOptions options = null); + Task> Get(string bucket, string key, uint rVal = RiakConstants.Defaults.RVal); + Task> Get(RiakObjectId objectId, uint rVal = RiakConstants.Defaults.RVal); + Task>> Get(IEnumerable bucketKeyPairs, uint rVal = RiakConstants.Defaults.RVal); + + Task> Put(RiakObject value, RiakPutOptions options = null); + Task>> Put(IEnumerable values, RiakPutOptions options = null); + + Task Delete(string bucket, string key, RiakDeleteOptions options = null); + Task Delete(RiakObjectId objectId, RiakDeleteOptions options = null); + Task> Delete(IEnumerable objectIds, RiakDeleteOptions options = null); + + Task> DeleteBucket(string bucket, RiakDeleteOptions options = null); + Task> DeleteBucket(string bucket, uint rwVal = RiakConstants.Defaults.RVal); + + Task> Search(RiakSearchRequest search); + + Task> MapReduce(RiakMapReduceQuery query); + + Task> StreamMapReduce(RiakMapReduceQuery query); + + Task>> ListBuckets(); + + Task>> ListKeys(string bucket); + + Task>> StreamListKeys(string bucket); + + Task> GetBucketProperties(string bucket, bool extended = false); + + Task SetBucketProperties(string bucket, RiakBucketProperties properties); + + Task ResetBucketProperties(string bucket); + + Task>> WalkLinks(RiakObject riakObject, IList riakLinks); + + Task> GetServerInfo(); + + Task>> IndexGet(string bucket, string indexName, int value); + Task>> IndexGet(string bucket, string indexName, string value); + Task>> IndexGet(string bucket, string indexName, int minValue, int maxValue); + Task>> IndexGet(string bucket, string indexName, string minValue, string maxValue); + + Task>> ListKeysFromIndex(string bucket); + + //RiakResult Search(Action prepareRequest) + } +} \ No newline at end of file diff --git a/CorrugatedIron/IRiakAsyncClient.cs b/CorrugatedIron/IRiakAsyncClient.cs new file mode 100644 index 00000000..cac68cde --- /dev/null +++ b/CorrugatedIron/IRiakAsyncClient.cs @@ -0,0 +1,78 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +using CorrugatedIron.Models; +using CorrugatedIron.Models.MapReduce; +using CorrugatedIron.Util; + +namespace CorrugatedIron +{ + public interface IRiakAsyncClient : IRiakBatchAsyncClient + { + + Task Batch(Func batchAction); + + Task Batch(Action batchAction); + + Task Batch(Func> batchFunction); + + [Obsolete("All async operations should use the functions that return Task.")] + void Ping(Action callback); + + [Obsolete("All async operations should use the functions that return Task.")] + void Get(string bucket, string key, Action> callback, uint rVal = RiakConstants.Defaults.RVal); + + [Obsolete("All async operations should use the functions that return Task.")] + void Get(RiakObjectId objectId, Action> callback, uint rVal = RiakConstants.Defaults.RVal); + + [Obsolete("All async operations should use the functions that return Task.")] + void Get(IEnumerable bucketKeyPairs, Action>> callback, uint rVal = RiakConstants.Defaults.RVal); + + [Obsolete("All async operations should use the functions that return Task.")] + void Put(RiakObject value, Action> callback, RiakPutOptions options = null); + + [Obsolete("All async operations should use the functions that return Task.")] + void Put(IEnumerable values, Action>> callback, RiakPutOptions options = null); + + [Obsolete("All async operations should use the functions that return Task.")] + void Delete(string bucket, string key, Action callback, RiakDeleteOptions options = null); + + [Obsolete("All async operations should use the functions that return Task.")] + void Delete(RiakObjectId objectId, Action callback, RiakDeleteOptions options = null); + + [Obsolete("All async operations should use the functions that return Task.")] + void Delete(IEnumerable objectIds, Action> callback, RiakDeleteOptions options = null); + + [Obsolete("All async operations should use the functions that return Task.")] + void DeleteBucket(string bucket, Action> callback, uint rwVal = RiakConstants.Defaults.RVal); + + [Obsolete("All async operations should use the functions that return Task.")] + void MapReduce(RiakMapReduceQuery query, Action> callback); + + [Obsolete("All async operations should use the functions that return Task.")] + void StreamMapReduce(RiakMapReduceQuery query, Action> callback); + + [Obsolete("All async operations should use the functions that return Task.")] + void ListBuckets(Action>> callback); + + [Obsolete("All async operations should use the functions that return Task.")] + void ListKeys(string bucket, Action>> callback); + + [Obsolete("All async operations should use the functions that return Task.")] + void StreamListKeys(string bucket, Action>> callback); + + [Obsolete("All async operations should use the functions that return Task.")] + void GetBucketProperties(string bucket, Action> callback, bool extended = false); + + [Obsolete("All async operations should use the functions that return Task.")] + void SetBucketProperties(string bucket, RiakBucketProperties properties, Action callback); + + [Obsolete("All async operations should use the functions that return Task.")] + void WalkLinks(RiakObject riakObject, IList riakLinks, Action>> callback); + + [Obsolete("All async operations should use the functions that return Task.")] + void GetServerInfo(Action> callback); + } +} + diff --git a/CorrugatedIron/IRiakEndPoint.cs b/CorrugatedIron/IRiakEndPoint.cs index 3c9c5eb6..dbe3ddc7 100644 --- a/CorrugatedIron/IRiakEndPoint.cs +++ b/CorrugatedIron/IRiakEndPoint.cs @@ -17,6 +17,7 @@ using CorrugatedIron.Comms; using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace CorrugatedIron { @@ -25,14 +26,16 @@ public interface IRiakEndPoint : IDisposable int RetryWaitTime { get; set; } IRiakClient CreateClient(); + IRiakAsyncClient CreateAsyncClient(); [Obsolete("Clients no longer need a seed value, use CreateClient() instead")] IRiakClient CreateClient(string seed); - RiakResult UseConnection(Func> useFun, int retryAttempts); - RiakResult UseConnection(Func useFun, int retryAttempts); + // use connections asyncronously + Task> UseConnection(Func>> useFun, int retryAttempts); + Task UseConnection(Func> useFun, int retryAttempts); - RiakResult> UseDelayedConnection(Func>> useFun, int retryAttempts) + Task>> UseDelayedConnection(Func>>> useFun, int retryAttempts) where TResult : RiakResult; } } \ No newline at end of file diff --git a/CorrugatedIron/RiakAsyncClient.cs b/CorrugatedIron/RiakAsyncClient.cs index 59789139..a95a4d0f 100644 --- a/CorrugatedIron/RiakAsyncClient.cs +++ b/CorrugatedIron/RiakAsyncClient.cs @@ -14,244 +14,769 @@ // specific language governing permissions and limitations // under the License. +using CorrugatedIron.Comms; +using CorrugatedIron.Extensions; +using CorrugatedIron.Messages; using CorrugatedIron.Models; using CorrugatedIron.Models.MapReduce; +using CorrugatedIron.Models.MapReduce.Inputs; +using CorrugatedIron.Models.Rest; +using CorrugatedIron.Models.Search; using CorrugatedIron.Util; using System; using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Web; + using System.Threading.Tasks; namespace CorrugatedIron { - public interface IRiakAsyncClient - { - Task Ping(); - - Task> Get(string bucket, string key, uint rVal = RiakConstants.Defaults.RVal); - Task> Get(RiakObjectId objectId, uint rVal = RiakConstants.Defaults.RVal); - Task>> Get(IEnumerable bucketKeyPairs, uint rVal = RiakConstants.Defaults.RVal); - - Task> Put(RiakObject value, RiakPutOptions options = null); - Task>> Put(IEnumerable values, RiakPutOptions options = null); - - Task Delete(string bucket, string key, RiakDeleteOptions options = null); - Task Delete(RiakObjectId objectId, RiakDeleteOptions options = null); - Task> Delete(IEnumerable objectIds, RiakDeleteOptions options = null); - Task> DeleteBucket(string bucket, uint rwVal = RiakConstants.Defaults.RVal); - - Task> MapReduce(RiakMapReduceQuery query); - Task> StreamMapReduce(RiakMapReduceQuery query); - - Task>> ListBuckets(); - Task>> ListKeys(string bucket); - Task>> StreamListKeys(string bucket); - - Task> GetBucketProperties(string bucket, bool extended = false); - Task SetBucketProperties(string bucket, RiakBucketProperties properties); - - Task>> IndexGet(string bucket, string indexName, int value); - Task>> IndexGet(string bucket, string indexName, string value); - Task>> IndexGet(string bucket, string indexName, int minValue, int maxValue); - Task>> IndexGet(string bucket, string indexName, string minValue, string maxValue); - - Task>> WalkLinks(RiakObject riakObject, IList riakLinks); - - Task> GetServerInfo(); - - Task Batch(Action batchAction); - - [Obsolete("All async operations should use the functions that return Task.")] - void Ping(Action callback); - - [Obsolete("All async operations should use the functions that return Task.")] - void Get(string bucket, string key, Action> callback, uint rVal = RiakConstants.Defaults.RVal); - - [Obsolete("All async operations should use the functions that return Task.")] - void Get(RiakObjectId objectId, Action> callback, uint rVal = RiakConstants.Defaults.RVal); - - [Obsolete("All async operations should use the functions that return Task.")] - void Get(IEnumerable bucketKeyPairs, Action>> callback, uint rVal = RiakConstants.Defaults.RVal); - - [Obsolete("All async operations should use the functions that return Task.")] - void Put(RiakObject value, Action> callback, RiakPutOptions options = null); - - [Obsolete("All async operations should use the functions that return Task.")] - void Put(IEnumerable values, Action>> callback, RiakPutOptions options = null); - - [Obsolete("All async operations should use the functions that return Task.")] - void Delete(string bucket, string key, Action callback, RiakDeleteOptions options = null); - - [Obsolete("All async operations should use the functions that return Task.")] - void Delete(RiakObjectId objectId, Action callback, RiakDeleteOptions options = null); - - [Obsolete("All async operations should use the functions that return Task.")] - void Delete(IEnumerable objectIds, Action> callback, RiakDeleteOptions options = null); - - [Obsolete("All async operations should use the functions that return Task.")] - void DeleteBucket(string bucket, Action> callback, uint rwVal = RiakConstants.Defaults.RVal); - - [Obsolete("All async operations should use the functions that return Task.")] - void MapReduce(RiakMapReduceQuery query, Action> callback); - - [Obsolete("All async operations should use the functions that return Task.")] - void StreamMapReduce(RiakMapReduceQuery query, Action> callback); - - [Obsolete("All async operations should use the functions that return Task.")] - void ListBuckets(Action>> callback); - - [Obsolete("All async operations should use the functions that return Task.")] - void ListKeys(string bucket, Action>> callback); - - [Obsolete("All async operations should use the functions that return Task.")] - void StreamListKeys(string bucket, Action>> callback); - - [Obsolete("All async operations should use the functions that return Task.")] - void GetBucketProperties(string bucket, Action> callback, bool extended = false); - - [Obsolete("All async operations should use the functions that return Task.")] - void SetBucketProperties(string bucket, RiakBucketProperties properties, Action callback); - - [Obsolete("All async operations should use the functions that return Task.")] - void WalkLinks(RiakObject riakObject, IList riakLinks, Action>> callback); - - [Obsolete("All async operations should use the functions that return Task.")] - void GetServerInfo(Action> callback); - } internal class RiakAsyncClient : IRiakAsyncClient { - private readonly IRiakClient _client; + private const string ListKeysWarning = "*** [CI] -> ListKeys is an expensive operation and should not be used in Production scenarios. ***"; + private const string InvalidBucketErrorMessage = "Bucket cannot be blank or contain forward-slashes"; + private const string InvalidKeyErrorMessage = "Key cannot be blank or contain forward-slashes"; + + private readonly IRiakEndPoint _endPoint; + private readonly IRiakConnection _batchConnection; + + public int RetryCount { get; set; } - public RiakAsyncClient(IRiakClient client) + internal RiakAsyncClient(IRiakEndPoint endPoint) { - _client = client; + _endPoint = endPoint; + } + + internal RiakAsyncClient(IRiakConnection batchConnection) + { + _batchConnection = batchConnection; } public Task Ping() { - return Task.Factory.StartNew(() => _client.Ping()); + return UseConnection(conn => conn.PbcWriteRead(MessageCode.PingReq, MessageCode.PingResp)); + } + + public Task> Get(string bucket, string key, RiakGetOptions options = null) + { + if (!IsValidBucketOrKey(bucket)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); + } + + if (!IsValidBucketOrKey(key)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidKeyErrorMessage, false); + } + + var request = new RpbGetReq { bucket = bucket.ToRiakString(), key = key.ToRiakString() }; + options = options ?? new RiakGetOptions(); + options.Populate(request); + + return UseConnection(conn => conn.PbcWriteRead(request)) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + if(!result.IsSuccess) + { + return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + } + + if(result.Value.vclock == null) + { + return RiakResult.Error(ResultCode.NotFound, "Unable to find value in Riak", false); + } + + var o = new RiakObject(bucket, key, result.Value.content, result.Value.vclock); + return RiakResult.Success(o); + }); } public Task> Get(string bucket, string key, uint rVal = RiakConstants.Defaults.RVal) { - return Task.Factory.StartNew(() => _client.Get(bucket, key, rVal)); + var options = new RiakGetOptions().SetR(rVal); + return Get(bucket, key, options); } public Task> Get(RiakObjectId objectId, uint rVal = RiakConstants.Defaults.RVal) { - return Task.Factory.StartNew(() => _client.Get(objectId.Bucket, objectId.Key, rVal)); + return Get(objectId.Bucket, objectId.Key, rVal); + } + + public Task>> Get(IEnumerable bucketKeyPairs, RiakGetOptions options = null) + { + bucketKeyPairs = bucketKeyPairs.ToList(); + options = options ?? new RiakGetOptions(); + + return UseConnection(conn => { + return AfterAll(bucketKeyPairs.Select(bkp => { + + // modified closure FTW + var bk = bkp; + if (!IsValidBucketOrKey(bk.Bucket)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); + } + if (!IsValidBucketOrKey(bk.Key)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidKeyErrorMessage, false); + } + + var req = new RpbGetReq { bucket = bk.Bucket.ToRiakString(), key = bk.Key.ToRiakString() }; + options.Populate(req); + + return conn.PbcWriteRead(req); + })).ContinueWith((Task>> finishedTask) => { + return RiakResult>>.Success(finishedTask.Result); + }); + }).ContinueWith((Task>>> finishedTask) => { + + // zip up results + var results = RiakResult>>.Success(finishedTask.Result.Value); + return results.Value.Zip(bucketKeyPairs, Tuple.Create).Select(result => { + if(!result.Item1.IsSuccess) + { + return RiakResult.Error(result.Item1.ResultCode, result.Item1.ErrorMessage, result.Item1.NodeOffline); + } + if(result.Item1.Value.vclock == null) + { + return RiakResult.Error(ResultCode.NotFound, "Unable to find value in Riak", false); + } + + var o = new RiakObject(result.Item2.Bucket, result.Item2.Key, result.Item1.Value.content.First(), result.Item1.Value.vclock); + if(result.Item1.Value.content.Count > 1) + { + o.Siblings = result.Item1.Value.content.Select( + c => new RiakObject(result.Item2.Bucket, result.Item2.Key, c, result.Item1.Value.vclock)).ToList(); + } + + return RiakResult.Success(o); + }); + }); } public Task>> Get(IEnumerable bucketKeyPairs, uint rVal = RiakConstants.Defaults.RVal) { - return Task.Factory.StartNew(() => _client.Get(bucketKeyPairs, rVal)); - } - - public Task>> Put(IEnumerable values, RiakPutOptions options) - { - return Task.Factory.StartNew(() => _client.Put(values, options)); - } - - public Task> Put(RiakObject value, RiakPutOptions options) - { - return Task.Factory.StartNew(() => _client.Put(value, options)); + var options = new RiakGetOptions().SetR(rVal); + return Get(bucketKeyPairs, options); + } + + public Task> Put(RiakObject value, RiakPutOptions options = null) + { + if (!IsValidBucketOrKey(value.Bucket)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); + } + + if (!IsValidBucketOrKey(value.Key)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidKeyErrorMessage, false); + } + + var request = value.ToMessage(); + options = options ?? new RiakPutOptions(); + options.Populate(request); + + return UseConnection(conn => conn.PbcWriteRead(request)) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + + if(!result.IsSuccess) + { + return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + } + + var finalResult = options.ReturnBody + ? new RiakObject(value.Bucket, value.Key, result.Value.content.First(), result.Value.vclock) + : value; + + if(options.ReturnBody && result.Value.content.Count > 1) + { + finalResult.Siblings = result.Value.content.Select( + c => new RiakObject(value.Bucket, value.Key, c, result.Value.vclock)).ToList(); + } + + return RiakResult.Success(finalResult); + }); + } + + public Task>> Put(IEnumerable values, RiakPutOptions options = null) + { + options = options ?? new RiakPutOptions(); + + return UseConnection(conn => { + return AfterAll(values.Select(v => { + if (!IsValidBucketOrKey(v.Bucket)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); + } + + if (!IsValidBucketOrKey(v.Key)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidKeyErrorMessage, false); + } + + var msg = v.ToMessage(); + options.Populate(msg); + + return conn.PbcWriteRead(msg); + })).ContinueWith((Task>> finishedTask) => { + return RiakResult>>.Success(finishedTask.Result); + }); + }).ContinueWith((Task>>> finishedTask) => { + var results = finishedTask.Result; + return results.Value.Zip(values, Tuple.Create).Select(t => { + if(t.Item1.IsSuccess) + { + var finalResult = options.ReturnBody + ? new RiakObject(t.Item2.Bucket, t.Item2.Key, t.Item1.Value.content.First(), t.Item1.Value.vclock) + : t.Item2; + + if(options.ReturnBody && t.Item1.Value.content.Count > 1) + { + finalResult.Siblings = t.Item1.Value.content.Select( + c => new RiakObject(t.Item2.Bucket, t.Item2.Key, c, t.Item1.Value.vclock)).ToList(); + } + + return RiakResult.Success(finalResult); + } + + return RiakResult.Error(t.Item1.ResultCode, t.Item1.ErrorMessage, t.Item1.NodeOffline); + }); + }); } public Task Delete(string bucket, string key, RiakDeleteOptions options = null) { - return Task.Factory.StartNew(() => _client.Delete(bucket, key, options)); + if (!IsValidBucketOrKey(bucket)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); + } + + if (!IsValidBucketOrKey(key)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidKeyErrorMessage, false); + } + + var request = new RpbDelReq { bucket = bucket.ToRiakString(), key = key.ToRiakString() }; + options = options ?? new RiakDeleteOptions(); + options.Populate(request); + return UseConnection(conn => conn.PbcWriteRead(request, MessageCode.DelResp)); + } + + private Task>> Delete(IRiakConnection conn, + IEnumerable objectIds, + RiakDeleteOptions options = null) + { + options = options ?? new RiakDeleteOptions(); + return AfterAll(objectIds.Select(id => { + if (!IsValidBucketOrKey(id.Bucket)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); + } + + if (!IsValidBucketOrKey(id.Key)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidKeyErrorMessage, false); + } + + var req = new RpbDelReq { bucket = id.Bucket.ToRiakString(), key = id.Key.ToRiakString() }; + options.Populate(req); + return conn.PbcWriteRead(req, MessageCode.DelResp); + })).ContinueWith((Task> finishedTask) => { + return RiakResult>.Success(finishedTask.Result); + }); } public Task Delete(RiakObjectId objectId, RiakDeleteOptions options = null) { - return Task.Factory.StartNew(() => _client.Delete(objectId.Bucket, objectId.Key, options)); + return Delete(objectId.Bucket, objectId.Key); } public Task> Delete(IEnumerable objectIds, RiakDeleteOptions options = null) { - return Task.Factory.StartNew(() => _client.Delete(objectIds, options)); + return UseConnection(conn => Delete(conn, objectIds, options)) + .ContinueWith((Task>> finishedTask) => { + return finishedTask.Result.Value; + }); + } + + public Task> DeleteBucket(string bucket, RiakDeleteOptions deleteOptions) + { + return UseConnection(conn => { + return ListKeys(conn, bucket) + .ContinueWith((Task>> finishedListingKeys) => { + var keyResults = finishedListingKeys.Result; + if (keyResults.IsSuccess) + { + var objectIds = keyResults.Value.Select(key => new RiakObjectId(bucket, key)).ToList(); + return Delete(conn, objectIds, deleteOptions); + } + return RiakResult>.ErrorTask(keyResults.ResultCode, keyResults.ErrorMessage, keyResults.NodeOffline); + }).Unwrap(); + }).ContinueWith((Task>> finishedTask) => { + return finishedTask.Result.Value; + }); } public Task> DeleteBucket(string bucket, uint rwVal = RiakConstants.Defaults.RVal) { - return Task.Factory.StartNew(() => _client.DeleteBucket(bucket, rwVal)); + var options = new RiakDeleteOptions(); + options.SetRw(rwVal); + return DeleteBucket(bucket, options); } public Task> MapReduce(RiakMapReduceQuery query) { - return Task.Factory.StartNew(() => _client.MapReduce(query)); + var request = query.ToMessage(); + return UseConnection(conn => conn.PbcWriteRead(request, r => r.IsSuccess && !r.Value.done)) + .ContinueWith((Task>>> finishedTask) => { + var result = finishedTask.Result; + if(result.IsSuccess) + { + return RiakResult.Success(new RiakMapReduceResult(result.Value)); + } + return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + }); } public Task> StreamMapReduce(RiakMapReduceQuery query) { - return Task.Factory.StartNew(() => _client.StreamMapReduce(query)); + var request = query.ToMessage(); + return UseDelayedConnection((conn, onFinish) => { + return conn.PbcWriteStreamRead(request, r => r.IsSuccess && !r.Value.done, onFinish); + }).ContinueWith((Task>>> finishedTask) => { + var result = finishedTask.Result; + if(result.IsSuccess) + { + return RiakResult.Success(new RiakStreamedMapReduceResult(result.Value)); + } + return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + }); } public Task>> ListBuckets() { - return Task.Factory.StartNew(() => _client.ListBuckets()); + return UseConnection(conn => conn.PbcWriteRead(MessageCode.ListBucketsReq)) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + if(result.IsSuccess) + { + var buckets = result.Value.buckets.Select(b => b.FromRiakString()); + return RiakResult>.Success(buckets.ToList()); + } + return RiakResult>.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + }); } public Task>> ListKeys(string bucket) { - return Task.Factory.StartNew(() => _client.ListKeys(bucket)); + return UseConnection(conn => ListKeys(conn, bucket)); } public Task>> StreamListKeys(string bucket) { - return Task.Factory.StartNew(() => _client.StreamListKeys(bucket)); + System.Diagnostics.Debug.Write(ListKeysWarning); + System.Diagnostics.Trace.TraceWarning(ListKeysWarning); + Console.WriteLine(ListKeysWarning); + + var lkReq = new RpbListKeysReq { bucket = bucket.ToRiakString() }; + return UseDelayedConnection((conn, onFinish) => { + return conn.PbcWriteStreamRead(lkReq, lkr => lkr.IsSuccess && !lkr.Value.done, onFinish); + }).ContinueWith((Task>>> finishedTask) => { + var result = finishedTask.Result; + if(result.IsSuccess) + { + var keys = result.Value.Where(r => r.IsSuccess).SelectMany(r => r.Value.keys).Select(k => k.FromRiakString()); + return RiakResult>.Success(keys); + } + return RiakResult>.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + }); } public Task> GetBucketProperties(string bucket, bool extended = false) { - return Task.Factory.StartNew(() => _client.GetBucketProperties(bucket, extended)); + // bucket names cannot have slashes in the names, the REST interface doesn't like it at all + if (!IsValidBucketOrKey(bucket)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); + } + + if(extended) + { + var request = new RiakRestRequest(ToBucketUri(bucket), RiakConstants.Rest.HttpMethod.Get) + .AddQueryParam(RiakConstants.Rest.QueryParameters.Bucket.GetPropertiesKey, + RiakConstants.Rest.QueryParameters.Bucket.GetPropertiesValue); + + return UseConnection(conn => conn.RestRequest(request)) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + if(result.IsSuccess) + { + if(result.Value.StatusCode == HttpStatusCode.OK) + { + var response = new RiakBucketProperties(result.Value); + return RiakResult.Success(response); + } + return RiakResult.Error(ResultCode.InvalidResponse, + "Unexpected Status Code: {0} ({1})".Fmt(result.Value.StatusCode, (int)result.Value.StatusCode), false); + } + return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + }); + } + else + { + var bpReq = new RpbGetBucketReq { bucket = bucket.ToRiakString() }; + return UseConnection(conn => conn.PbcWriteRead(bpReq)) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + if(result.IsSuccess) + { + var props = new RiakBucketProperties(result.Value.props); + return RiakResult.Success(props); + } + return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + }); + } } public Task SetBucketProperties(string bucket, RiakBucketProperties properties) { - return Task.Factory.StartNew(() => _client.SetBucketProperties(bucket, properties)); + if (!IsValidBucketOrKey(bucket)) + { + return RiakResult.ErrorTask(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); + } + + if(properties.CanUsePbc) + { + var request = new RpbSetBucketReq { bucket = bucket.ToRiakString(), props = properties.ToMessage() }; + return UseConnection(conn => conn.PbcWriteRead(request, MessageCode.SetBucketResp)); + } + else + { + var request = new RiakRestRequest(ToBucketUri(bucket), RiakConstants.Rest.HttpMethod.Put) + { + Body = properties.ToJsonString().ToRiakString(), + ContentType = RiakConstants.ContentTypes.ApplicationJson + }; + + return UseConnection(conn => conn.RestRequest(request)) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + if(result.IsSuccess && result.Value.StatusCode != HttpStatusCode.NoContent) + { + return RiakResult.Error(ResultCode.InvalidResponse, "Unexpected Status Code: {0} ({1})".Fmt(result.Value.StatusCode, (int)result.Value.StatusCode), result.NodeOffline); + } + return result; + }); + } + } + + public Task ResetBucketProperties(string bucket) + { + var request = new RiakRestRequest(ToBucketPropsUri(bucket), RiakConstants.Rest.HttpMethod.Delete); + return UseConnection(conn => conn.RestRequest(request)) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + if(result.IsSuccess) + { + switch (result.Value.StatusCode) + { + case HttpStatusCode.NoContent: + return result; + case HttpStatusCode.NotFound: + return RiakResult.Error(ResultCode.NotFound, "Bucket {0} not found.".Fmt(bucket), false); + default: + return RiakResult.Error(ResultCode.InvalidResponse, "Unexpected Status Code: {0} ({1})".Fmt(result.Value.StatusCode, (int)result.Value.StatusCode), result.NodeOffline); + } + } + return result; + }); } public Task>> IndexGet(string bucket, string indexName, int value) { - return Task.Factory.StartNew(() => _client.IndexGet(bucket, indexName, value)); + return IndexGetEquals(bucket, indexName.ToIntegerKey(), value.ToString()); } public Task>> IndexGet(string bucket, string indexName, string value) { - return Task.Factory.StartNew(() => _client.IndexGet(bucket, indexName, value)); + return IndexGetEquals(bucket, indexName.ToBinaryKey(), value); } public Task>> IndexGet(string bucket, string indexName, int minValue, int maxValue) { - return Task.Factory.StartNew(() => _client.IndexGet(bucket, indexName, minValue, maxValue)); + return IndexGetRange(bucket, indexName.ToIntegerKey(), minValue.ToString(), maxValue.ToString()); } public Task>> IndexGet(string bucket, string indexName, string minValue, string maxValue) { - return Task.Factory.StartNew(() => _client.IndexGet(bucket, indexName, minValue, maxValue)); + return IndexGetRange(bucket, indexName.ToBinaryKey(), minValue, maxValue); + } + + private Task>> IndexGetRange(string bucket, string indexName, string minValue, string maxValue) + { + var message = new RpbIndexReq + { + bucket = bucket.ToRiakString(), + index = indexName.ToRiakString(), + qtype = RpbIndexReq.IndexQueryType.range, + range_min = minValue.ToRiakString(), + range_max = maxValue.ToRiakString() + }; + + return UseConnection(conn => conn.PbcWriteRead(message)) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + if (result.IsSuccess) + { + return RiakResult>.Success(result.Value.keys.Select(k => k.FromRiakString()).ToList()); + } + return RiakResult>.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + }); + } + + private Task>> IndexGetEquals(string bucket, string indexName, string value) + { + var message = new RpbIndexReq + { + bucket = bucket.ToRiakString(), + index = indexName.ToRiakString(), + key = value.ToRiakString(), + qtype = RpbIndexReq.IndexQueryType.eq + }; + + return UseConnection(conn => conn.PbcWriteRead(message)) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + if (result.IsSuccess) + { + return RiakResult>.Success(result.Value.keys.Select(k => k.FromRiakString()).ToList()); + } + return RiakResult>.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + }); } public Task>> WalkLinks(RiakObject riakObject, IList riakLinks) { - return Task.Factory.StartNew(() => _client.WalkLinks(riakObject, riakLinks)); + System.Diagnostics.Debug.Assert(riakLinks.Count > 0, "Link walking requires at least one link"); + + var input = new RiakBucketKeyInput(); + input.AddBucketKey(riakObject.Bucket, riakObject.Key); + + var query = new RiakMapReduceQuery() + .Inputs(input); + + var lastLink = riakLinks.Last(); + + foreach(var riakLink in riakLinks) + { + var link = riakLink; + var keep = ReferenceEquals(link, lastLink); + query.Link(l => l.FromRiakLink(link).Keep(keep)); + } + + return MapReduce(query) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + if(result.IsSuccess) + { + var linkResults = result.Value.PhaseResults.GroupBy(r => r.Phase).Where(g => g.Key == riakLinks.Count - 1); + var linkResultStrings = linkResults.SelectMany(lr => lr.ToList(), (lr, r) => new { lr, r }) + .SelectMany(@t => @t.r.Values, (@t, s) => s.FromRiakString()); + + //var linkResultStrings = linkResults.SelectMany(g => g.Select(r => r.Values.Value.FromRiakString())); + var rawLinks = linkResultStrings.SelectMany(RiakLink.ParseArrayFromJsonString).Distinct(); + var oids = rawLinks.Select(l => new RiakObjectId(l.Bucket, l.Key)).ToList(); + + return Get(oids, new RiakGetOptions()) + .ContinueWith((Task>> getTask) => { + var objects = getTask.Result; + + // FIXME + // we could be discarding results here. Not good? + // This really should be a multi-phase map/reduce + return RiakResult>.Success(objects.Where(r => r.IsSuccess).Select(r => r.Value).ToList()); + }); + } + return RiakResult>.ErrorTask(result.ResultCode, result.ErrorMessage, result.NodeOffline); + }).Unwrap(); } public Task> GetServerInfo() { - return Task.Factory.StartNew(() => _client.GetServerInfo()); + return UseConnection(conn => conn.PbcWriteRead(MessageCode.GetServerInfoReq)) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + if(result.IsSuccess) + { + return RiakResult.Success(new RiakServerInfo(result.Value)); + } + return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + }); + } + + public Task>> ListKeysFromIndex(string bucket) + { + return IndexGet(bucket, RiakConstants.SystemIndexKeys.RiakBucketIndex, bucket); + } + + public Task> Search(RiakSearchRequest search) + { + var request = search.ToMessage(); + return UseConnection(conn => conn.PbcWriteRead(request)) + .ContinueWith((Task> finishedTask) => { + var result = finishedTask.Result; + if (result.IsSuccess) + { + return RiakResult.Success(new RiakSearchResult(result.Value)); + } + + return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + }); } public Task Batch(Action batchAction) { - return Task.Factory.StartNew(() => _client.Batch(batchAction)); + return Batch(ac => { + return Task.Factory.StartNew(() => { + var c = new RiakClient((RiakAsyncClient)ac); + batchAction(c); + return new object(); + }); + }); + } + + public Task Batch(Func batchFunc) + { + return Batch(ac => { + return batchFunc(ac).ContinueWith(t => new object()); + }); + } + + public Task Batch(Func> batchFun) + { + var taskResult = default(T); + + // no idea what this is + Func>>>> helperBatchFun = ((conn, onFinish) => + { + return Task.Factory.StartNew(() => batchFun(new RiakAsyncClient(conn))).Unwrap() + .ContinueWith((Task finishedTask) => { + onFinish(); + + // exception or success + if (!finishedTask.IsFaulted) + { + taskResult = finishedTask.Result; + return RiakResult>>.Success(null); + } + else + { + var ex = finishedTask.Exception.Flatten().InnerExceptions.First(); + return RiakResult>>.Error( + ResultCode.BatchException, "{0}\n{1}".Fmt(ex.Message, ex.StackTrace), true); + } + }); + }); + + // apply task to connection + return _endPoint.UseDelayedConnection(helperBatchFun, RetryCount) + .ContinueWith((Task>>> finishedTask) => { + var result = finishedTask.Result; + if(!result.IsSuccess && result.ResultCode == ResultCode.BatchException) + { + throw new Exception(result.ErrorMessage); + } + return taskResult; + }); + } + + private Task>> ListKeys(IRiakConnection conn, string bucket) + { + System.Diagnostics.Debug.Write(ListKeysWarning); + System.Diagnostics.Trace.TraceWarning(ListKeysWarning); + Console.WriteLine(ListKeysWarning); + + // start listing keys + var lkReq = new RpbListKeysReq { bucket = bucket.ToRiakString() }; + return conn.PbcWriteRead(lkReq, lkr => lkr.IsSuccess && !lkr.Value.done) + .ContinueWith((Task>>> finishedTask) => { + var result = finishedTask.Result; + if(result.IsSuccess) + { + var keys = result.Value.Where(r => r.IsSuccess).SelectMany(r => r.Value.keys).Select(k => k.FromRiakString()).Distinct().ToList(); + return RiakResult>.Success(keys); + } + return RiakResult>.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + }); + } + + + // using connections etc... + + private Task UseConnection(Func> op) + { + return _batchConnection != null ? op(_batchConnection) : _endPoint.UseConnection(op, RetryCount); + } + + private Task> UseConnection(Func>> op) + { + return _batchConnection != null ? op(_batchConnection) : _endPoint.UseConnection(op, RetryCount); } - public Task Batch(Func batchFunc) + private Task>>> UseDelayedConnection( + Func>>>> op) { - return Task.Factory.StartNew(() => _client.Batch(batchFunc)); + return _batchConnection != null + ? op(_batchConnection, () => { }) + : _endPoint.UseDelayedConnection(op, RetryCount); } + + private string ToBucketUri(string bucket) + { + return "{0}/{1}".Fmt(RiakConstants.Rest.Uri.RiakRoot, HttpUtility.UrlEncode(bucket)); + } + + private string ToBucketPropsUri(string bucket) + { + return RiakConstants.Rest.Uri.BucketPropsFmt.Fmt(HttpUtility.UrlEncode(bucket)); + } + + private bool IsValidBucketOrKey(string value) + { + return !string.IsNullOrWhiteSpace(value) && !value.Contains('/'); + } + + // wait for tasks to complete + private Task> AfterAll(IEnumerable> tasks) + { + var source = new TaskCompletionSource>(); + var leftToComplete = tasks.Count(); + + // complete all tasks + foreach (var task in tasks) + { + task.ContinueWith(t => { + leftToComplete--; + if (leftToComplete == 0) + { + source.SetResult(tasks.Select(tt => tt.Result)); + } + }); + } + + // completed task (handle no tasks case) + if (!tasks.Any()) + source.SetResult(Enumerable.Empty()); + return source.Task; + } + + + // ----------------------------------------------------------------------------------------------------------- // All functions below are marked as obsolete @@ -259,102 +784,104 @@ public Task Batch(Func batchFunc) public void Ping(Action callback) { - ExecAsync(() => callback(_client.Ping())); + ExecAsync(Ping(), callback); } public void Get(string bucket, string key, Action> callback, uint rVal = RiakConstants.Defaults.RVal) { - ExecAsync(() => callback(_client.Get(bucket, key, rVal))); + ExecAsync(Get(bucket, key, rVal), callback); } public void Get(RiakObjectId objectId, Action> callback, uint rVal = RiakConstants.Defaults.RVal) { - ExecAsync(() => callback(_client.Get(objectId.Bucket, objectId.Key, rVal))); + ExecAsync(Get(objectId, rVal), callback); } public void Get(IEnumerable bucketKeyPairs, Action>> callback, uint rVal = RiakConstants.Defaults.RVal) { - ExecAsync(() => callback(_client.Get(bucketKeyPairs, rVal))); + ExecAsync(Get(bucketKeyPairs, rVal), callback); } public void Put(IEnumerable values, Action>> callback, RiakPutOptions options) { - ExecAsync(() => callback(_client.Put(values, options))); + ExecAsync(Put(values, options), callback); } public void Put(RiakObject value, Action> callback, RiakPutOptions options) { - ExecAsync(() => callback(_client.Put(value, options))); + ExecAsync(Put(value, options), callback); } public void Delete(string bucket, string key, Action callback, RiakDeleteOptions options = null) { - ExecAsync(() => callback(_client.Delete(bucket, key, options))); + ExecAsync(Delete(bucket, key, options), callback); } public void Delete(RiakObjectId objectId, Action callback, RiakDeleteOptions options = null) { - ExecAsync(() => callback(_client.Delete(objectId.Bucket, objectId.Key, options))); + ExecAsync(Delete(objectId, options), callback); } public void Delete(IEnumerable objectIds, Action> callback, RiakDeleteOptions options = null) { - ExecAsync(() => callback(_client.Delete(objectIds, options))); + ExecAsync(Delete(objectIds, options), callback); } public void DeleteBucket(string bucket, Action> callback, uint rwVal = RiakConstants.Defaults.RVal) - { - ExecAsync(() => callback(_client.DeleteBucket(bucket, rwVal))); + { + ExecAsync(DeleteBucket(bucket, rwVal), callback); } public void MapReduce(RiakMapReduceQuery query, Action> callback) { - ExecAsync(() => callback(_client.MapReduce(query))); + ExecAsync(MapReduce(query), callback); } public void StreamMapReduce(RiakMapReduceQuery query, Action> callback) { - ExecAsync(() => callback(_client.StreamMapReduce(query))); + ExecAsync(StreamMapReduce(query), callback); } public void ListBuckets(Action>> callback) { - ExecAsync(() => callback(_client.ListBuckets())); + ExecAsync(ListBuckets(), callback); } public void ListKeys(string bucket, Action>> callback) { - ExecAsync(() => callback(_client.ListKeys(bucket))); + ExecAsync(ListKeys(bucket), callback); } public void StreamListKeys(string bucket, Action>> callback) { - ExecAsync(() => callback(_client.StreamListKeys(bucket))); + ExecAsync(StreamListKeys(bucket), callback); } public void GetBucketProperties(string bucket, Action> callback, bool extended = false) { - ExecAsync(() => callback(_client.GetBucketProperties(bucket, extended))); + ExecAsync(GetBucketProperties(bucket, extended), callback); } public void SetBucketProperties(string bucket, RiakBucketProperties properties, Action callback) { - ExecAsync(() => callback(_client.SetBucketProperties(bucket, properties))); + ExecAsync(SetBucketProperties(bucket, properties), callback); } public void WalkLinks(RiakObject riakObject, IList riakLinks, Action>> callback) { - ExecAsync(() => callback(_client.WalkLinks(riakObject, riakLinks))); + ExecAsync(WalkLinks(riakObject, riakLinks), callback); } public void GetServerInfo(Action> callback) { - ExecAsync(() => callback(_client.GetServerInfo())); + ExecAsync(GetServerInfo(), callback); } - private static void ExecAsync(Action action) + private void ExecAsync(Task getResult, Action callback) { - Task.Factory.StartNew(action); + getResult.ContinueWith((Task task) => { + callback(task.Result); + }); } } } \ No newline at end of file diff --git a/CorrugatedIron/RiakClient.cs b/CorrugatedIron/RiakClient.cs index 17c7271d..c765b11a 100644 --- a/CorrugatedIron/RiakClient.cs +++ b/CorrugatedIron/RiakClient.cs @@ -28,6 +28,7 @@ using System.Linq; using System.Net; using System.Web; +using System.Threading.Tasks; namespace CorrugatedIron { @@ -42,34 +43,23 @@ public interface IRiakClient : IRiakBatchClient public class RiakClient : IRiakClient { - private const string ListKeysWarning = "*** [CI] -> ListKeys is an expensive operation and should not be used in Production scenarios. ***"; - private const string InvalidBucketErrorMessage = "Bucket cannot be blank or contain forward-slashes"; - private const string InvalidKeyErrorMessage = "Key cannot be blank or contain forward-slashes"; + private IRiakAsyncClient _client; - private readonly IRiakEndPoint _endPoint; - private readonly IRiakConnection _batchConnection; - - public int RetryCount { get; set; } - - public IRiakAsyncClient Async { get; private set; } - - internal RiakClient(IRiakEndPoint endPoint) + public int RetryCount { - _endPoint = endPoint; - Async = new RiakAsyncClient(this); + get { return _client.RetryCount; } + set { _client.RetryCount = value; } } - [Obsolete("This method should no longer be used, use RiakClient(IRiakEndPoint) instead")] - internal RiakClient(IRiakEndPoint endPoint, string seed = null) : this(endPoint) { } - - private RiakClient(IRiakConnection batchConnection) + public IRiakAsyncClient Async { - _batchConnection = batchConnection; - Async = new RiakAsyncClient(this); + get { return _client; } } - [Obsolete("This method should no longer be used, use RiakClient(IRiakConnection) instead")] - private RiakClient(IRiakConnection batchConnection, byte[] clientId) : this(batchConnection) { } + internal RiakClient(IRiakAsyncClient client) + { + _client = client; + } /// /// Ping this instance of Riak @@ -82,7 +72,7 @@ private RiakClient(IRiakConnection batchConnection, byte[] clientId) : this(batc /// Returns false if Riak is unavailable or returns a 'pang' response. public RiakResult Ping() { - return UseConnection(conn => conn.PbcWriteRead(MessageCode.PingReq, MessageCode.PingResp)); + return WaitFor(_client.Ping()); } /// @@ -106,36 +96,7 @@ public RiakResult Ping() /// public RiakResult Get(string bucket, string key, RiakGetOptions options = null) { - if (!IsValidBucketOrKey(bucket)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); - } - - if (!IsValidBucketOrKey(key)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidKeyErrorMessage, false); - } - - var request = new RpbGetReq { bucket = bucket.ToRiakString(), key = key.ToRiakString() }; - - options = options ?? new RiakGetOptions(); - options.Populate(request); - - var result = UseConnection(conn => conn.PbcWriteRead(request)); - - if(!result.IsSuccess) - { - return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); - } - - if(result.Value.vclock == null) - { - return RiakResult.Error(ResultCode.NotFound, "Unable to find value in Riak", false); - } - - var o = new RiakObject(bucket, key, result.Value.content, result.Value.vclock); - - return RiakResult.Success(o); + return WaitFor(_client.Get(bucket, key, options)); } /// @@ -205,56 +166,7 @@ public RiakResult Get(RiakObjectId objectId, uint rVal = RiakConstan public IEnumerable> Get(IEnumerable bucketKeyPairs, RiakGetOptions options = null) { - bucketKeyPairs = bucketKeyPairs.ToList(); - - options = options ?? new RiakGetOptions(); - - var results = UseConnection(conn => - { - var responses = bucketKeyPairs.Select(bkp => - { - // modified closure FTW - var bk = bkp; - if (!IsValidBucketOrKey(bk.Bucket)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); - } - - if (!IsValidBucketOrKey(bk.Key)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidKeyErrorMessage, false); - } - - var req = new RpbGetReq { bucket = bk.Bucket.ToRiakString(), key = bk.Key.ToRiakString() }; - options.Populate(req); - - return conn.PbcWriteRead(req); - }).ToList(); - return RiakResult>>.Success(responses); - }); - - return results.Value.Zip(bucketKeyPairs, Tuple.Create).Select(result => - { - if(!result.Item1.IsSuccess) - { - return RiakResult.Error(result.Item1.ResultCode, result.Item1.ErrorMessage, result.Item1.NodeOffline); - } - - if(result.Item1.Value.vclock == null) - { - return RiakResult.Error(ResultCode.NotFound, "Unable to find value in Riak", false); - } - - var o = new RiakObject(result.Item2.Bucket, result.Item2.Key, result.Item1.Value.content.First(), result.Item1.Value.vclock); - - if(result.Item1.Value.content.Count > 1) - { - o.Siblings = result.Item1.Value.content.Select(c => - new RiakObject(result.Item2.Bucket, result.Item2.Key, c, result.Item1.Value.vclock)).ToList(); - } - - return RiakResult.Success(o); - }); + return WaitFor(_client.Get(bucketKeyPairs)); } /// @@ -276,7 +188,6 @@ public IEnumerable> Get(IEnumerable bucketK public IEnumerable> Get(IEnumerable bucketKeyPairs, uint rVal = RiakConstants.Defaults.RVal) { var options = new RiakGetOptions().SetR(rVal); - return Get(bucketKeyPairs, options); } @@ -292,39 +203,7 @@ public IEnumerable> Get(IEnumerable bucketK /// public RiakResult Put(RiakObject value, RiakPutOptions options = null) { - if (!IsValidBucketOrKey(value.Bucket)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); - } - - if (!IsValidBucketOrKey(value.Key)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidKeyErrorMessage, false); - } - - options = options ?? new RiakPutOptions(); - - var request = value.ToMessage(); - options.Populate(request); - - var result = UseConnection(conn => conn.PbcWriteRead(request)); - - if(!result.IsSuccess) - { - return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); - } - - var finalResult = options.ReturnBody - ? new RiakObject(value.Bucket, value.Key, result.Value.content.First(), result.Value.vclock) - : value; - - if(options.ReturnBody && result.Value.content.Count > 1) - { - finalResult.Siblings = result.Value.content.Select(c => - new RiakObject(value.Bucket, value.Key, c, result.Value.vclock)).ToList(); - } - - return RiakResult.Success(finalResult); + return WaitFor(_client.Put(value, options)); } /// @@ -344,50 +223,7 @@ public RiakResult Put(RiakObject value, RiakPutOptions options = nul /// In addition, applications should plan for multiple failures or multiple cases of siblings being present. public IEnumerable> Put(IEnumerable values, RiakPutOptions options = null) { - options = options ?? new RiakPutOptions(); - - var results = UseConnection(conn => - { - var responses = values.Select(v => - { - if (!IsValidBucketOrKey(v.Bucket)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); - } - - if (!IsValidBucketOrKey(v.Key)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidKeyErrorMessage, false); - } - - var msg = v.ToMessage(); - options.Populate(msg); - - return conn.PbcWriteRead(msg); - }).ToList(); - - return RiakResult>>.Success(responses); - }); - - return results.Value.Zip(values, Tuple.Create).Select(t => - { - if(t.Item1.IsSuccess) - { - var finalResult = options.ReturnBody - ? new RiakObject(t.Item2.Bucket, t.Item2.Key, t.Item1.Value.content.First(), t.Item1.Value.vclock) - : t.Item2; - - if(options.ReturnBody && t.Item1.Value.content.Count > 1) - { - finalResult.Siblings = t.Item1.Value.content.Select(c => - new RiakObject(t.Item2.Bucket, t.Item2.Key, c, t.Item1.Value.vclock)).ToList(); - } - - return RiakResult.Success(finalResult); - } - - return RiakResult.Error(t.Item1.ResultCode, t.Item1.ErrorMessage, t.Item1.NodeOffline); - }); + return WaitFor(_client.Put(values, options)); } /// @@ -404,23 +240,7 @@ public IEnumerable> Put(IEnumerable values, R /// public RiakResult Delete(string bucket, string key, RiakDeleteOptions options = null) { - if (!IsValidBucketOrKey(bucket)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); - } - - if (!IsValidBucketOrKey(key)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidKeyErrorMessage, false); - } - - options = options ?? new RiakDeleteOptions(); - - var request = new RpbDelReq { bucket = bucket.ToRiakString(), key = key.ToRiakString() }; - options.Populate(request); - var result = UseConnection(conn => conn.PbcWriteRead(request, MessageCode.DelResp)); - - return result; + return WaitFor(_client.Delete(bucket, key, options)); } /// @@ -448,8 +268,7 @@ public RiakResult Delete(RiakObjectId objectId, RiakDeleteOptions options = null /// public IEnumerable Delete(IEnumerable objectIds, RiakDeleteOptions options = null) { - var results = UseConnection(conn => Delete(conn, objectIds, options)); - return results.Value; + return WaitFor(_client.Delete(objectIds, options)); } /// @@ -479,7 +298,7 @@ public IEnumerable Delete(IEnumerable objectIds, RiakD /// public IEnumerable DeleteBucket(string bucket, uint rwVal) { - return DeleteBucket(bucket, new RiakDeleteOptions().SetRw(rwVal)); + return WaitFor(_client.DeleteBucket(bucket, rwVal)); } /// @@ -509,82 +328,22 @@ public IEnumerable DeleteBucket(string bucket, uint rwVal) /// public IEnumerable DeleteBucket(string bucket, RiakDeleteOptions deleteOptions) { - var results = UseConnection(conn => - { - var keyResults = ListKeys(conn, bucket); - if (keyResults.IsSuccess) - { - var objectIds = keyResults.Value.Select(key => new RiakObjectId(bucket, key)).ToList(); - return Delete(conn, objectIds, deleteOptions); - } - return RiakResult>.Error(keyResults.ResultCode, keyResults.ErrorMessage, keyResults.NodeOffline); - }); - - return results.Value; - } - - private static RiakResult> Delete(IRiakConnection conn, - IEnumerable objectIds, RiakDeleteOptions options = null) - { - options = options ?? new RiakDeleteOptions(); - - var responses = objectIds.Select(id => - { - if (!IsValidBucketOrKey(id.Bucket)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); - } - - if (!IsValidBucketOrKey(id.Key)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidKeyErrorMessage, false); - } - - var req = new RpbDelReq { bucket = id.Bucket.ToRiakString(), key = id.Key.ToRiakString() }; - options.Populate(req); - return conn.PbcWriteRead(req, MessageCode.DelResp); - }).ToList(); - - return RiakResult>.Success(responses); + return WaitFor(_client.DeleteBucket(bucket, deleteOptions)); } public RiakResult MapReduce(RiakMapReduceQuery query) { - var request = query.ToMessage(); - var response = UseConnection(conn => conn.PbcWriteRead(request, r => r.IsSuccess && !r.Value.done)); - - if(response.IsSuccess) - { - return RiakResult.Success(new RiakMapReduceResult(response.Value)); - } - - return RiakResult.Error(response.ResultCode, response.ErrorMessage, response.NodeOffline); + return WaitFor(_client.MapReduce(query)); } public RiakResult Search(RiakSearchRequest search) { - var request = search.ToMessage(); - var response = UseConnection(conn => conn.PbcWriteRead(request)); - - if (response.IsSuccess) - { - return RiakResult.Success(new RiakSearchResult(response.Value)); - } - - return RiakResult.Error(response.ResultCode, response.ErrorMessage, response.NodeOffline); + return WaitFor(_client.Search(search)); } public RiakResult StreamMapReduce(RiakMapReduceQuery query) { - var request = query.ToMessage(); - var response = UseDelayedConnection((conn, onFinish) => - conn.PbcWriteStreamRead(request, r => r.IsSuccess && !r.Value.done, onFinish)); - - if(response.IsSuccess) - { - return RiakResult.Success(new RiakStreamedMapReduceResult(response.Value)); - } - return RiakResult.Error(response.ResultCode, response.ErrorMessage, response.NodeOffline); + return WaitFor(_client.StreamMapReduce(query)); } /// @@ -598,14 +357,7 @@ public RiakResult StreamMapReduce(RiakMapReduceQuer /// physical I/O and can take a long time. public RiakResult> ListBuckets() { - var result = UseConnection(conn => conn.PbcWriteRead(MessageCode.ListBucketsReq)); - - if(result.IsSuccess) - { - var buckets = result.Value.buckets.Select(b => b.FromRiakString()); - return RiakResult>.Success(buckets.ToList()); - } - return RiakResult>.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + return WaitFor(_client.ListBuckets()); } /// @@ -621,42 +373,12 @@ public RiakResult> ListBuckets() /// a list of keys. This operation, while cheaper in Riak 1.0 than in earlier versions of Riak, should be avoided. public RiakResult> ListKeys(string bucket) { - return UseConnection(conn => ListKeys(conn, bucket)); - } - - private static RiakResult> ListKeys(IRiakConnection conn, string bucket) - { - System.Diagnostics.Debug.Write(ListKeysWarning); - System.Diagnostics.Trace.TraceWarning(ListKeysWarning); - Console.WriteLine(ListKeysWarning); - - var lkReq = new RpbListKeysReq { bucket = bucket.ToRiakString() }; - var result = conn.PbcWriteRead(lkReq, - lkr => lkr.IsSuccess && !lkr.Value.done); - if(result.IsSuccess) - { - var keys = result.Value.Where(r => r.IsSuccess).SelectMany(r => r.Value.keys).Select(k => k.FromRiakString()).Distinct().ToList(); - return RiakResult>.Success(keys); - } - return RiakResult>.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + return WaitFor(_client.ListKeys(bucket)); } public RiakResult> StreamListKeys(string bucket) { - System.Diagnostics.Debug.Write(ListKeysWarning); - System.Diagnostics.Trace.TraceWarning(ListKeysWarning); - Console.WriteLine(ListKeysWarning); - - var lkReq = new RpbListKeysReq { bucket = bucket.ToRiakString() }; - var result = UseDelayedConnection((conn, onFinish) => - conn.PbcWriteStreamRead(lkReq, lkr => lkr.IsSuccess && !lkr.Value.done, onFinish)); - - if(result.IsSuccess) - { - var keys = result.Value.Where(r => r.IsSuccess).SelectMany(r => r.Value.keys).Select(k => k.FromRiakString()); - return RiakResult>.Success(keys); - } - return RiakResult>.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + return WaitFor(_client.StreamListKeys(bucket)); } /// @@ -668,7 +390,7 @@ public RiakResult> StreamListKeys(string bucket) /// quickly return an unsorted list of keys from Riak. public RiakResult> ListKeysFromIndex(string bucket) { - return IndexGet(bucket, RiakConstants.SystemIndexKeys.RiakBucketIndex, bucket); + return WaitFor(_client.ListKeysFromIndex(bucket)); } /// @@ -685,44 +407,7 @@ public RiakResult> ListKeysFromIndex(string bucket) /// public RiakResult GetBucketProperties(string bucket, bool extended = false) { - // bucket names cannot have slashes in the names, the REST interface doesn't like it at all - if (!IsValidBucketOrKey(bucket)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); - } - - if(extended) - { - var request = new RiakRestRequest(ToBucketUri(bucket), RiakConstants.Rest.HttpMethod.Get) - .AddQueryParam(RiakConstants.Rest.QueryParameters.Bucket.GetPropertiesKey, - RiakConstants.Rest.QueryParameters.Bucket.GetPropertiesValue); - - var result = UseConnection(conn => conn.RestRequest(request)); - - if(result.IsSuccess) - { - if(result.Value.StatusCode == HttpStatusCode.OK) - { - var response = new RiakBucketProperties(result.Value); - return RiakResult.Success(response); - } - return RiakResult.Error(ResultCode.InvalidResponse, - "Unexpected Status Code: {0} ({1})".Fmt(result.Value.StatusCode, (int)result.Value.StatusCode), false); - } - return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); - } - else - { - var bpReq = new RpbGetBucketReq { bucket = bucket.ToRiakString() }; - var result = UseConnection(conn => conn.PbcWriteRead(bpReq)); - - if(result.IsSuccess) - { - var props = new RiakBucketProperties(result.Value.props); - return RiakResult.Success(props); - } - return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); - } + return WaitFor(_client.GetBucketProperties(bucket, extended)); } /// @@ -739,32 +424,7 @@ public RiakResult GetBucketProperties(string bucket, bool /// public RiakResult SetBucketProperties(string bucket, RiakBucketProperties properties) { - if (!IsValidBucketOrKey(bucket)) - { - return RiakResult.Error(ResultCode.InvalidRequest, InvalidBucketErrorMessage, false); - } - - if(properties.CanUsePbc) - { - var request = new RpbSetBucketReq { bucket = bucket.ToRiakString(), props = properties.ToMessage() }; - var result = UseConnection(conn => conn.PbcWriteRead(request, MessageCode.SetBucketResp)); - return result; - } - else - { - var request = new RiakRestRequest(ToBucketUri(bucket), RiakConstants.Rest.HttpMethod.Put) - { - Body = properties.ToJsonString().ToRiakString(), - ContentType = RiakConstants.ContentTypes.ApplicationJson - }; - - var result = UseConnection(conn => conn.RestRequest(request)); - if(result.IsSuccess && result.Value.StatusCode != HttpStatusCode.NoContent) - { - return RiakResult.Error(ResultCode.InvalidResponse, "Unexpected Status Code: {0} ({1})".Fmt(result.Value.StatusCode, (int)result.Value.StatusCode), result.NodeOffline); - } - return result; - } + return WaitFor(_client.SetBucketProperties(bucket, properties)); } /// @@ -775,22 +435,7 @@ public RiakResult SetBucketProperties(string bucket, RiakBucketProperties proper /// This function requires Riak v1.3+. public RiakResult ResetBucketProperties(string bucket) { - var request = new RiakRestRequest(ToBucketPropsUri(bucket), RiakConstants.Rest.HttpMethod.Delete); - - var result = UseConnection(conn => conn.RestRequest(request)); - if(result.IsSuccess) - { - switch (result.Value.StatusCode) - { - case HttpStatusCode.NoContent: - return result; - case HttpStatusCode.NotFound: - return RiakResult.Error(ResultCode.NotFound, "Bucket {0} not found.".Fmt(bucket), false); - default: - return RiakResult.Error(ResultCode.InvalidResponse, "Unexpected Status Code: {0} ({1})".Fmt(result.Value.StatusCode, (int)result.Value.StatusCode), result.NodeOffline); - } - } - return result; + return WaitFor(_client.ResetBucketProperties(bucket)); } /// @@ -829,44 +474,7 @@ public RiakBucketKeyInput GetIndex(RiakIndexInput indexQuery) /// Refer to http://wiki.basho.com/Links-and-Link-Walking.html for more information. public RiakResult> WalkLinks(RiakObject riakObject, IList riakLinks) { - System.Diagnostics.Debug.Assert(riakLinks.Count > 0, "Link walking requires at least one link"); - - var input = new RiakBucketKeyInput(); - input.AddBucketKey(riakObject.Bucket, riakObject.Key); - - var query = new RiakMapReduceQuery() - .Inputs(input); - - var lastLink = riakLinks.Last(); - - foreach(var riakLink in riakLinks) - { - var link = riakLink; - var keep = ReferenceEquals(link, lastLink); - - query.Link(l => l.FromRiakLink(link).Keep(keep)); - } - - var result = MapReduce(query); - - if(result.IsSuccess) - { - var linkResults = result.Value.PhaseResults.GroupBy(r => r.Phase).Where(g => g.Key == riakLinks.Count - 1); - var linkResultStrings = linkResults.SelectMany(lr => lr.ToList(), (lr, r) => new { lr, r }) - .SelectMany(@t => @t.r.Values, (@t, s) => s.FromRiakString()); - - //var linkResultStrings = linkResults.SelectMany(g => g.Select(r => r.Values.Value.FromRiakString())); - var rawLinks = linkResultStrings.SelectMany(RiakLink.ParseArrayFromJsonString).Distinct(); - var oids = rawLinks.Select(l => new RiakObjectId(l.Bucket, l.Key)).ToList(); - - var objects = Get(oids, new RiakGetOptions()); - - // FIXME - // we could be discarding results here. Not good? - // This really should be a multi-phase map/reduce - return RiakResult>.Success(objects.Where(r => r.IsSuccess).Select(r => r.Value).ToList()); - } - return RiakResult>.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + return WaitFor(_client.WalkLinks(riakObject, riakLinks)); } /// @@ -879,74 +487,27 @@ public RiakResult> WalkLinks(RiakObject riakObject, IList public RiakResult GetServerInfo() { - var result = UseConnection(conn => conn.PbcWriteRead(MessageCode.GetServerInfoReq)); - - if(result.IsSuccess) - { - return RiakResult.Success(new RiakServerInfo(result.Value)); - } - return RiakResult.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + return WaitFor(_client.GetServerInfo()); } public RiakResult> IndexGet(string bucket, string indexName, string minValue, string maxValue) { - return IndexGetRange(bucket, indexName.ToBinaryKey(), minValue, maxValue); + return WaitFor(_client.IndexGet(bucket, indexName, minValue, maxValue)); } public RiakResult> IndexGet(string bucket, string indexName, int minValue, int maxValue) { - return IndexGetRange(bucket, indexName.ToIntegerKey(), minValue.ToString(), maxValue.ToString()); - } - - private RiakResult> IndexGetRange(string bucket, string indexName, string minValue, string maxValue) - { - var message = new RpbIndexReq - { - bucket = bucket.ToRiakString(), - index = indexName.ToRiakString(), - qtype = RpbIndexReq.IndexQueryType.range, - range_min = minValue.ToRiakString(), - range_max = maxValue.ToRiakString() - }; - - var result = UseConnection(conn => conn.PbcWriteRead(message)); - - if (result.IsSuccess) - { - return RiakResult>.Success(result.Value.keys.Select(k => k.FromRiakString()).ToList()); - } - - return RiakResult>.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + return WaitFor(_client.IndexGet(bucket, indexName, minValue, maxValue)); } public RiakResult> IndexGet(string bucket, string indexName, string value) { - return IndexGetEquals(bucket, indexName.ToBinaryKey(), value); + return WaitFor(_client.IndexGet(bucket, indexName, value)); } public RiakResult> IndexGet(string bucket, string indexName, int value) { - return IndexGetEquals(bucket, indexName.ToIntegerKey(), value.ToString()); - } - - private RiakResult> IndexGetEquals(string bucket, string indexName, string value) - { - var message = new RpbIndexReq - { - bucket = bucket.ToRiakString(), - index = indexName.ToRiakString(), - key = value.ToRiakString(), - qtype = RpbIndexReq.IndexQueryType.eq - }; - - var result = UseConnection(conn => conn.PbcWriteRead(message)); - - if (result.IsSuccess) - { - return RiakResult>.Success(result.Value.keys.Select(k => k.FromRiakString()).ToList()); - } - - return RiakResult>.Error(result.ResultCode, result.ErrorMessage, result.NodeOffline); + return WaitFor(_client.IndexGet(bucket, indexName, value)); } /// @@ -960,71 +521,30 @@ private RiakResult> IndexGetEquals(string bucket, string indexName /// public void Batch(Action batchAction) { - Batch(c => { batchAction(c); return null; }); + WaitFor(_client.Batch(batchAction)); } public T Batch(Func batchFun) { - var funResult = default(T); - - Func>>> helperBatchFun = (conn, onFinish) => - { - try - { - funResult = batchFun(new RiakClient(conn)); - return RiakResult>>.Success(null); - } - catch(Exception ex) - { - return RiakResult>>.Error(ResultCode.BatchException, "{0}\n{1}".Fmt(ex.Message, ex.StackTrace), true); - } - finally - { - onFinish(); - } - }; - - var result = _endPoint.UseDelayedConnection(helperBatchFun, RetryCount); - - if(!result.IsSuccess && result.ResultCode == ResultCode.BatchException) - { - throw new Exception(result.ErrorMessage); - } - - return funResult; - } - - private RiakResult UseConnection(Func op) - { - return _batchConnection != null ? op(_batchConnection) : _endPoint.UseConnection(op, RetryCount); - } - - private RiakResult UseConnection(Func> op) - { - return _batchConnection != null ? op(_batchConnection) : _endPoint.UseConnection(op, RetryCount); - } - - private RiakResult>> UseDelayedConnection( - Func>>> op) - { - return _batchConnection != null - ? op(_batchConnection, () => { }) - : _endPoint.UseDelayedConnection(op, RetryCount); - } - - private static string ToBucketUri(string bucket) - { - return "{0}/{1}".Fmt(RiakConstants.Rest.Uri.RiakRoot, HttpUtility.UrlEncode(bucket)); + return WaitFor(_client.Batch(ac => { + var c = new RiakClient((RiakAsyncClient)ac); + var s = new TaskCompletionSource(); + s.SetResult(batchFun(c)); + return s.Task; + })); } - private static string ToBucketPropsUri(string bucket) + // simple wrapper + private T WaitFor(Task task) { - return RiakConstants.Rest.Uri.BucketPropsFmt.Fmt(HttpUtility.UrlEncode(bucket)); + task.Wait(); + return task.Result; } - private static bool IsValidBucketOrKey(string value) + // just keep consistent + private void WaitFor(Task task) { - return !string.IsNullOrWhiteSpace(value) && !value.Contains('/'); + task.Wait(); } } } \ No newline at end of file diff --git a/CorrugatedIron/RiakCluster.cs b/CorrugatedIron/RiakCluster.cs index e54d9aaa..5a0ab001 100644 --- a/CorrugatedIron/RiakCluster.cs +++ b/CorrugatedIron/RiakCluster.cs @@ -1,209 +1,285 @@ -// Copyright (c) 2011 - OJ Reeves & Jeremiah Peschka -// -// This file is provided to you under the Apache License, -// Version 2.0 (the "License"); you may not use this file -// except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -using CorrugatedIron.Comms; -using CorrugatedIron.Comms.LoadBalancing; -using CorrugatedIron.Config; -using CorrugatedIron.Messages; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace CorrugatedIron -{ - public class RiakCluster : RiakEndPoint - { - private readonly RoundRobinStrategy _loadBalancer; - private readonly List _nodes; - private readonly ConcurrentQueue _offlineNodes; - private readonly int _nodePollTime; - private readonly int _defaultRetryCount; - private bool _disposing; - - protected override int DefaultRetryCount - { - get { return _defaultRetryCount; } - } - - public RiakCluster(IRiakClusterConfiguration clusterConfiguration, IRiakConnectionFactory connectionFactory) - { - _nodePollTime = clusterConfiguration.NodePollTime; - _nodes = clusterConfiguration.RiakNodes.Select(rn => new RiakNode(rn, connectionFactory)).Cast().ToList(); - _loadBalancer = new RoundRobinStrategy(); - _loadBalancer.Initialise(_nodes); - _offlineNodes = new ConcurrentQueue(); - _defaultRetryCount = clusterConfiguration.DefaultRetryCount; - RetryWaitTime = clusterConfiguration.DefaultRetryWaitTime; - - Task.Factory.StartNew(NodeMonitor); - } - - /// - /// Creates an instance of populated from from the configuration section - /// specified by . - /// - /// The name of the configuration section to load the settings from. - /// A fully configured - public static IRiakEndPoint FromConfig(string configSectionName) - { - return new RiakCluster(RiakClusterConfiguration.LoadFromConfig(configSectionName), new RiakConnectionFactory()); - } - - /// - /// Creates an instance of populated from from the configuration section - /// specified by . - /// - /// The name of the configuration section to load the settings from. - /// The full path and name of the config file to load the configuration from. - /// A fully configured - public static IRiakEndPoint FromConfig(string configSectionName, string configFileName) - { - return new RiakCluster(RiakClusterConfiguration.LoadFromConfig(configSectionName, configFileName), new RiakConnectionFactory()); - } - - protected override TRiakResult UseConnection(Func useFun, Func onError, int retryAttempts) - { - if(retryAttempts < 0) return onError(ResultCode.NoRetries, "Unable to access a connection on the cluster.", false); - if (_disposing) return onError(ResultCode.ShuttingDown, "System currently shutting down", true); - - var node = _loadBalancer.SelectNode(); - - if (node != null) - { - var result = node.UseConnection(useFun); - if (!result.IsSuccess) - { - TRiakResult nextResult = null; - if (result.ResultCode == ResultCode.NoConnections) - { - Thread.Sleep(RetryWaitTime); - nextResult = UseConnection(useFun, onError, retryAttempts - 1); - } - else if (result.ResultCode == ResultCode.CommunicationError) - { - if (result.NodeOffline) - { - DeactivateNode(node); - } - - Thread.Sleep(RetryWaitTime); - nextResult = UseConnection(useFun, onError, retryAttempts - 1); - } - - // if the next result is successful then return that - if (nextResult != null && nextResult.IsSuccess) - { - return nextResult; - } - - // otherwise we'll return the result that we had at this call to make sure that - // the correct/initial error is shown - return onError(result.ResultCode, result.ErrorMessage, result.NodeOffline); - } - return (TRiakResult)result; - } - return onError(ResultCode.ClusterOffline, "Unable to access functioning Riak node", true); - } - - public override RiakResult> UseDelayedConnection(Func>> useFun, int retryAttempts) - { - if(retryAttempts < 0) return RiakResult>.Error(ResultCode.NoRetries, "Unable to access a connection on the cluster.", false); - if (_disposing) return RiakResult>.Error(ResultCode.ShuttingDown, "System currently shutting down", true); - - var node = _loadBalancer.SelectNode(); - - if (node != null) - { - var result = node.UseDelayedConnection(useFun); - if (!result.IsSuccess) - { - if (result.ResultCode == ResultCode.NoConnections) - { - Thread.Sleep(RetryWaitTime); - return UseDelayedConnection(useFun, retryAttempts - 1); - } - - if (result.ResultCode == ResultCode.CommunicationError) - { - if (result.NodeOffline) - { - DeactivateNode(node); - } - - Thread.Sleep(RetryWaitTime); - return UseDelayedConnection(useFun, retryAttempts - 1); - } - } - return result; - } - return RiakResult>.Error(ResultCode.ClusterOffline, "Unable to access functioning Riak node", true); - } - - private void DeactivateNode(IRiakNode node) - { - lock (node) - { - if (!_offlineNodes.Contains(node)) - { - _loadBalancer.RemoveNode(node); - _offlineNodes.Enqueue(node); - } - } - } - - private void NodeMonitor() - { - while (!_disposing) - { - var deadNodes = new List(); - IRiakNode node = null; - while (_offlineNodes.TryDequeue(out node) && !_disposing) - { - var result = node.UseConnection(c => c.PbcWriteRead(MessageCode.PingReq, MessageCode.PingResp)); - - if (result.IsSuccess) - { - _loadBalancer.AddNode(node); - } - else - { - deadNodes.Add(node); - } - } - - if (!_disposing) - { - foreach (var deadNode in deadNodes) - { - _offlineNodes.Enqueue(deadNode); - } - - Thread.Sleep(_nodePollTime); - } - } - } - - public override void Dispose() - { - _disposing = true; - - _nodes.ForEach(n => n.Dispose()); - } - } -} +// Copyright (c) 2011 - OJ Reeves & Jeremiah Peschka +// +// This file is provided to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using CorrugatedIron.Comms; +using CorrugatedIron.Comms.LoadBalancing; +using CorrugatedIron.Config; +using CorrugatedIron.Messages; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace CorrugatedIron +{ + public class RiakCluster : RiakEndPoint + { + private readonly RoundRobinStrategy _loadBalancer; + private readonly List _nodes; + private readonly ConcurrentQueue _offlineNodes; + private readonly int _nodePollTime; + private readonly Timer _nodePollTimer; + private readonly int _defaultRetryCount; + private bool _disposing; + + protected override int DefaultRetryCount + { + get { return _defaultRetryCount; } + } + + public RiakCluster(IRiakClusterConfiguration clusterConfiguration, IRiakConnectionFactory connectionFactory) + { + _nodePollTime = clusterConfiguration.NodePollTime; + _nodes = clusterConfiguration.RiakNodes.Select(rn => new RiakNode(rn, connectionFactory)).Cast().ToList(); + _loadBalancer = new RoundRobinStrategy(); + _loadBalancer.Initialise(_nodes); + _offlineNodes = new ConcurrentQueue(); + _defaultRetryCount = clusterConfiguration.DefaultRetryCount; + RetryWaitTime = clusterConfiguration.DefaultRetryWaitTime; + + // node monitor is now asynchronous, just triggered by timer! + _nodePollTimer = new Timer(state => NodeMonitorCycle(), null, _nodePollTime, Timeout.Infinite); + } + + /// + /// Creates an instance of populated from from the configuration section + /// specified by . + /// + /// The name of the configuration section to load the settings from. + /// A fully configured + public static IRiakEndPoint FromConfig(string configSectionName) + { + return new RiakCluster(RiakClusterConfiguration.LoadFromConfig(configSectionName), new RiakConnectionFactory()); + } + + /// + /// Creates an instance of populated from from the configuration section + /// specified by . + /// + /// The name of the configuration section to load the settings from. + /// The full path and name of the config file to load the configuration from. + /// A fully configured + public static IRiakEndPoint FromConfig(string configSectionName, string configFileName) + { + return new RiakCluster(RiakClusterConfiguration.LoadFromConfig(configSectionName, configFileName), new RiakConnectionFactory()); + } + + protected override Task UseConnection(Func> useFun, Func onError, int retryAttempts) + { + if(retryAttempts < 0) + return TaskResult(onError(ResultCode.NoRetries, "Unable to access a connection on the cluster.", false)); + if (_disposing) + return TaskResult(onError(ResultCode.ShuttingDown, "System currently shutting down", true)); + + // use connetion with recursive fallback + var node = _loadBalancer.SelectNode() as RiakNode; + if (node != null) + { + Func> cUseFun = ( + c => useFun(c).ContinueWith((Task t) => (RiakResult)t.Result)); + + // make sure the correct / initial error is shown + TRiakResult originalError = null; + + // attempt to use the connection + return node.UseConnection(cUseFun) + .ContinueWith((Task finishedTask) => { + var result = (TRiakResult)finishedTask.Result; + if (result.IsSuccess) + { + return TaskResult(result); + } + else + { + originalError = onError(result.ResultCode, result.ErrorMessage, result.NodeOffline); + if (result.ResultCode == ResultCode.NoConnections) + { + return DelayTask(RetryWaitTime) + .ContinueWith(delayTask => { + return UseConnection(useFun, onError, retryAttempts - 1); + }).Unwrap(); + } + else if (result.ResultCode == ResultCode.CommunicationError) + { + if (result.NodeOffline) + { + DeactivateNode(node); + } + + return DelayTask(RetryWaitTime) + .ContinueWith(delayTask => { + return UseConnection(useFun, onError, retryAttempts - 1); + }).Unwrap(); + } + } + + + return TaskResult(onError(result.ResultCode, result.ErrorMessage, result.NodeOffline)); + }).Unwrap() + .ContinueWith((Task finishedTask) => { + + // make sure the original error is shown + if (finishedTask.Result.IsSuccess) + return finishedTask.Result; + else + return originalError; + }); + } + + // can't get node + return TaskResult(onError(ResultCode.ClusterOffline, "Unable to access functioning Riak node", true)); + } + + public override Task>> UseDelayedConnection(Func>>> useFun, int retryAttempts) + { + if(retryAttempts < 0) + return RiakResult>.ErrorTask(ResultCode.NoRetries, "Unable to access a connection on the cluster.", false); + if (_disposing) + return RiakResult>.ErrorTask(ResultCode.ShuttingDown, "System currently shutting down", true); + + // select a node + var node = _loadBalancer.SelectNode(); + if (node != null) + { + return node.UseDelayedConnection(useFun) + .ContinueWith((Task>> finishedTask) => { + var result = finishedTask.Result; + if (result.IsSuccess) + { + return TaskResult(result); + } + else + { + if (result.ResultCode == ResultCode.NoConnections) + { + return DelayTask(RetryWaitTime) + .ContinueWith(delayTask => { + return UseDelayedConnection(useFun, retryAttempts - 1); + }).Unwrap(); + } + if (result.ResultCode == ResultCode.CommunicationError) + { + if (result.NodeOffline) + { + DeactivateNode(node); + } + + return DelayTask(RetryWaitTime) + .ContinueWith(delayTask => { + return UseDelayedConnection(useFun, retryAttempts - 1); + }).Unwrap(); + } + } + + // out of options + return RiakResult>.ErrorTask(ResultCode.ClusterOffline, "Unable to access functioning Riak node", true); + }).Unwrap(); + } + + // no functioning node + return RiakResult>.ErrorTask(ResultCode.ClusterOffline, "Unable to access functioning Riak node", true); + } + + private void DeactivateNode(IRiakNode node) + { + lock (node) + { + if (!_offlineNodes.Contains(node)) + { + _loadBalancer.RemoveNode(node); + _offlineNodes.Enqueue(node); + } + } + } + + // try to re-add dead nodes, started by timer + private void NodeMonitorCycle() + { + if (!_disposing) + { + _nodePollTimer.Change(_nodePollTime, Timeout.Infinite); + + // dequeue all offline nodes + var offlineList = new List(); + IRiakNode queueNode = null; + while (_offlineNodes.TryDequeue(out queueNode) && !_disposing) + { + offlineList.Add(queueNode); + } + + // try to ping all offline nodes + foreach (var node in offlineList) + { + if (!_disposing) + { + node.UseConnection(c => c.PbcWriteRead(MessageCode.PingReq, MessageCode.PingResp)) + .ContinueWith((Task finishedTask) => { + if (!_disposing) + { + lock (node) + { + if (finishedTask.Result.IsSuccess) + { + _loadBalancer.AddNode(node); + } + else + { + if (!_offlineNodes.Contains(node)) + { + _offlineNodes.Enqueue(node); + } + } + } + } + }); + } + } + } + } + + public override void Dispose() + { + _disposing = true; + _nodes.ForEach(n => n.Dispose()); + _nodePollTimer.Dispose(); + } + + // wrap a task result + private Task TaskResult(T result) + { + var source = new TaskCompletionSource(); + source.SetResult(result); + return source.Task; + } + + // async timer + private Task DelayTask(int milliseconds) + { + var source = new TaskCompletionSource(); + + var timer = null as Timer; + timer = new Timer(state => { + source.SetResult(new object()); + timer.Dispose(); + timer = null; + }, null, milliseconds, Timeout.Infinite); + + return source.Task; + } + } +} diff --git a/CorrugatedIron/RiakEndPoint.cs b/CorrugatedIron/RiakEndPoint.cs index 1cb6737a..43abf1e1 100644 --- a/CorrugatedIron/RiakEndPoint.cs +++ b/CorrugatedIron/RiakEndPoint.cs @@ -17,6 +17,7 @@ using CorrugatedIron.Comms; using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace CorrugatedIron { @@ -38,7 +39,7 @@ public abstract class RiakEndPoint : IRiakEndPoint [Obsolete("Clients no longer need a seed value, use CreateClient() instead")] public IRiakClient CreateClient(string seed) { - return new RiakClient(this, seed) { RetryCount = DefaultRetryCount }; + return CreateClient(); } /// @@ -49,23 +50,29 @@ public IRiakClient CreateClient(string seed) /// public IRiakClient CreateClient() { - return new RiakClient(this) { RetryCount = DefaultRetryCount }; + var asyncClient = CreateAsyncClient(); + return new RiakClient(asyncClient); } - public RiakResult UseConnection(Func useFun, int retryAttempts) + public IRiakAsyncClient CreateAsyncClient() + { + return new RiakAsyncClient(this) { RetryCount = DefaultRetryCount }; + } + + public Task UseConnection(Func> useFun, int retryAttempts) { return UseConnection(useFun, RiakResult.Error, retryAttempts); } - public RiakResult UseConnection(Func> useFun, int retryAttempts) + public Task> UseConnection(Func>> useFun, int retryAttempts) { return UseConnection(useFun, RiakResult.Error, retryAttempts); } - protected abstract TRiakResult UseConnection(Func useFun, Func onError, int retryAttempts) + protected abstract Task UseConnection(Func> useFun, Func onError, int retryAttempts) where TRiakResult : RiakResult; - public abstract RiakResult> UseDelayedConnection(Func>> useFun, int retryAttempts) + public abstract Task>> UseDelayedConnection(Func>>> useFun, int retryAttempts) where TResult : RiakResult; public abstract void Dispose(); diff --git a/CorrugatedIron/RiakExternalLoadBalancer.cs b/CorrugatedIron/RiakExternalLoadBalancer.cs index 51d2a70e..6e690468 100644 --- a/CorrugatedIron/RiakExternalLoadBalancer.cs +++ b/CorrugatedIron/RiakExternalLoadBalancer.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; namespace CorrugatedIron { @@ -49,56 +50,70 @@ protected override int DefaultRetryCount get { return _lbConfiguration.DefaultRetryCount; } } - protected override TRiakResult UseConnection(Func useFun, Func onError, int retryAttempts) + protected override Task UseConnection(Func> useFun, Func onError, int retryAttempts) { if(retryAttempts < 0) { - return onError(ResultCode.NoRetries, "Unable to access a connection on the cluster.", true); + return TaskResult(onError(ResultCode.NoRetries, "Unable to access a connection on the cluster.", true)); } if(_disposing) { - return onError(ResultCode.ShuttingDown, "System currently shutting down", true); + return TaskResult(onError(ResultCode.ShuttingDown, "System currently shutting down", true)); } + // get result (note the harmless but annoying casting) var node = _node; - if(node != null) { - var result = node.UseConnection(useFun); - if(!result.IsSuccess) - { - Thread.Sleep(RetryWaitTime); - return UseConnection(useFun, onError, retryAttempts - 1); - } - return (TRiakResult)result; + Func> cUseFun = ( + c => useFun(c).ContinueWith((Task t) => (RiakResult)t.Result)); + + // use harmess casting + return node.UseConnection(cUseFun) + .ContinueWith((Task finishedTask) => { + var result = (TRiakResult)finishedTask.Result; + if(!result.IsSuccess) + { + return TaskDelay(RetryWaitTime) + .ContinueWith(finishedDelayTask => { + return UseConnection(useFun, onError, retryAttempts - 1); + }).Unwrap(); + } + return TaskResult(result); + }).Unwrap(); } - return onError(ResultCode.ClusterOffline, "Unable to access functioning Riak node", true); + + // no functioning node + return TaskResult(onError(ResultCode.ClusterOffline, "Unable to access functioning Riak node", true)); } - public override RiakResult> UseDelayedConnection(Func>> useFun, int retryAttempts) + public override Task>> UseDelayedConnection(Func>>> useFun, int retryAttempts) { if(retryAttempts < 0) { - return RiakResult>.Error(ResultCode.NoRetries, "Unable to access a connection on the cluster.", true); + return RiakResult>.ErrorTask(ResultCode.NoRetries, "Unable to access a connection on the cluster.", true); } if(_disposing) { - return RiakResult>.Error(ResultCode.ShuttingDown, "System currently shutting down", true); + return RiakResult>.ErrorTask(ResultCode.ShuttingDown, "System currently shutting down", true); } var node = _node; - if(node != null) { - var result = node.UseDelayedConnection(useFun); - if(!result.IsSuccess) - { - Thread.Sleep(RetryWaitTime); - return UseDelayedConnection(useFun, retryAttempts - 1); - } - return result; + return node.UseDelayedConnection(useFun) + .ContinueWith((Task>> finishedTask) => { + var result = finishedTask.Result; + if(!result.IsSuccess) + { + return TaskDelay(RetryWaitTime).ContinueWith(finishedDelayTask => { + return UseDelayedConnection(useFun, retryAttempts - 1); + }).Unwrap(); + } + return TaskResult(result); + }).Unwrap(); } - return RiakResult>.Error(ResultCode.ClusterOffline, "Unable to access functioning Riak node", true); + return RiakResult>.ErrorTask(ResultCode.ClusterOffline, "Unable to access functioning Riak node", true); } public override void Dispose() @@ -107,5 +122,30 @@ public override void Dispose() _node.Dispose(); } + + // wrap a task result + private Task TaskResult(T result) + { + var source = new TaskCompletionSource(); + source.SetResult(result); + return source.Task; + } + + // create a delay task + private Task TaskDelay(int milliseconds) + { + var source = new TaskCompletionSource(); + + // setup timer + var timer = null as Timer; + timer = new Timer(dummyState => { + source.SetResult(new object()); + timer.Dispose(); + timer = null; + }, null, milliseconds, Timeout.Infinite); + + // give back the task + return source.Task; + } } } \ No newline at end of file diff --git a/CorrugatedIron/RiakResult.cs b/CorrugatedIron/RiakResult.cs index 62bea7b3..7af65c05 100644 --- a/CorrugatedIron/RiakResult.cs +++ b/CorrugatedIron/RiakResult.cs @@ -13,6 +13,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. +using System.Threading.Tasks; namespace CorrugatedIron { @@ -42,6 +43,13 @@ protected RiakResult() { } + internal static Task SuccessTask() + { + var source = new TaskCompletionSource(); + source.SetResult(RiakResult.Success()); + return source.Task; + } + internal static RiakResult Success() { return new RiakResult @@ -51,6 +59,13 @@ internal static RiakResult Success() }; } + internal static Task ErrorTask(ResultCode code, string message, bool nodeOffline) + { + var source = new TaskCompletionSource(); + source.SetResult(RiakResult.Error(code, message, nodeOffline)); + return source.Task; + } + internal static RiakResult Error(ResultCode code, string message, bool nodeOffline) { return new RiakResult @@ -71,6 +86,13 @@ private RiakResult() { } + internal static Task> SuccessTask(TResult value) + { + var source = new TaskCompletionSource>(); + source.SetResult(RiakResult.Success(value)); + return source.Task; + } + internal static RiakResult Success(TResult value) { return new RiakResult @@ -81,6 +103,13 @@ internal static RiakResult Success(TResult value) }; } + internal new static Task> ErrorTask(ResultCode code, string message, bool nodeOffline) + { + var source = new TaskCompletionSource>(); + source.SetResult(RiakResult.Error(code, message, nodeOffline)); + return source.Task; + } + internal new static RiakResult Error(ResultCode code, string message, bool nodeOffline) { return new RiakResult