From 2b84443035ae6bb8ba1d4702e87158352ed5ad90 Mon Sep 17 00:00:00 2001 From: Mahir Sertkaya Date: Wed, 6 Jan 2016 09:22:45 +0200 Subject: [PATCH 1/3] EnyimMemcached with touch support Touch support required for extending items' TTL value --- Enyim.Caching/Enyim.Caching.csproj | 6 ++ Enyim.Caching/IMemcachedClient.cs | 2 + Enyim.Caching/Memcached/IOperationFactory.cs | 2 + .../Memcached/OperationInterfaces.cs | 4 + .../Protocol/Binary/BinaryOperationFactory.cs | 7 +- .../Memcached/Protocol/Binary/OpCode.cs | 3 + .../Protocol/Binary/TouchOperation.cs | 75 +++++++++++++++++++ .../Protocol/Text/TextOperationFactory.cs | 5 ++ .../Memcached/Protocol/Text/TouchOperation.cs | 57 ++++++++++++++ .../DefaultTouchOperationResultFactory.cs | 41 ++++++++++ .../Factories/ITouchOperationResultFactory.cs | 34 +++++++++ .../Results/ITouchOperationResult.cs | 33 ++++++++ .../Memcached/Results/TouchOperationResult.cs | 34 +++++++++ Enyim.Caching/MemcachedClient.cs | 46 ++++++++++++ 14 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 Enyim.Caching/Memcached/Protocol/Binary/TouchOperation.cs create mode 100644 Enyim.Caching/Memcached/Protocol/Text/TouchOperation.cs create mode 100644 Enyim.Caching/Memcached/Results/Factories/DefaultTouchOperationResultFactory.cs create mode 100644 Enyim.Caching/Memcached/Results/Factories/ITouchOperationResultFactory.cs create mode 100644 Enyim.Caching/Memcached/Results/ITouchOperationResult.cs create mode 100644 Enyim.Caching/Memcached/Results/TouchOperationResult.cs diff --git a/Enyim.Caching/Enyim.Caching.csproj b/Enyim.Caching/Enyim.Caching.csproj index 57b7451a..1757f576 100755 --- a/Enyim.Caching/Enyim.Caching.csproj +++ b/Enyim.Caching/Enyim.Caching.csproj @@ -213,6 +213,7 @@ + @@ -244,9 +245,11 @@ + + @@ -258,12 +261,14 @@ + + @@ -280,6 +285,7 @@ + diff --git a/Enyim.Caching/IMemcachedClient.cs b/Enyim.Caching/IMemcachedClient.cs index 616dcd1e..86723176 100644 --- a/Enyim.Caching/IMemcachedClient.cs +++ b/Enyim.Caching/IMemcachedClient.cs @@ -52,6 +52,8 @@ public interface IMemcachedClient : IDisposable void FlushAll(); + bool Touch(string key, TimeSpan validFor); + ServerStats Stats(); ServerStats Stats(string type); diff --git a/Enyim.Caching/Memcached/IOperationFactory.cs b/Enyim.Caching/Memcached/IOperationFactory.cs index 446593c4..957fcff0 100644 --- a/Enyim.Caching/Memcached/IOperationFactory.cs +++ b/Enyim.Caching/Memcached/IOperationFactory.cs @@ -17,6 +17,8 @@ public interface IOperationFactory IStatsOperation Stats(string type); IFlushOperation Flush(); + + ITouchOperation Touch(string key, uint expires); } } diff --git a/Enyim.Caching/Memcached/OperationInterfaces.cs b/Enyim.Caching/Memcached/OperationInterfaces.cs index 4c3a00f0..2117168e 100644 --- a/Enyim.Caching/Memcached/OperationInterfaces.cs +++ b/Enyim.Caching/Memcached/OperationInterfaces.cs @@ -77,6 +77,10 @@ public interface IFlushOperation : IOperation { } + public interface ITouchOperation : ISingleItemOperation + { + } + public struct CasResult { public T Result { get; set; } diff --git a/Enyim.Caching/Memcached/Protocol/Binary/BinaryOperationFactory.cs b/Enyim.Caching/Memcached/Protocol/Binary/BinaryOperationFactory.cs index 601569b4..983e385a 100644 --- a/Enyim.Caching/Memcached/Protocol/Binary/BinaryOperationFactory.cs +++ b/Enyim.Caching/Memcached/Protocol/Binary/BinaryOperationFactory.cs @@ -47,7 +47,12 @@ IFlushOperation IOperationFactory.Flush() { return new FlushOperation(); } - } + + ITouchOperation IOperationFactory.Touch(string key, uint expires) + { + return new TouchOperation(key, expires); + } + } } #region [ License information ] diff --git a/Enyim.Caching/Memcached/Protocol/Binary/OpCode.cs b/Enyim.Caching/Memcached/Protocol/Binary/OpCode.cs index 3f22b069..4e8ed45d 100644 --- a/Enyim.Caching/Memcached/Protocol/Binary/OpCode.cs +++ b/Enyim.Caching/Memcached/Protocol/Binary/OpCode.cs @@ -30,6 +30,9 @@ public enum OpCode : byte FlushQ = 0x18, AppendQ = 0x19, PrependQ = 0x1A, + Touch = 0x1C, + GetAndTouch = 0x1D, + GetAndTouchQ = 0x1E, // SASL authentication op-codes SaslList = 0x20, diff --git a/Enyim.Caching/Memcached/Protocol/Binary/TouchOperation.cs b/Enyim.Caching/Memcached/Protocol/Binary/TouchOperation.cs new file mode 100644 index 00000000..ef0cab43 --- /dev/null +++ b/Enyim.Caching/Memcached/Protocol/Binary/TouchOperation.cs @@ -0,0 +1,75 @@ +using System; +using System.Text; +using Enyim.Caching.Memcached.Results; +using Enyim.Caching.Memcached.Results.Extensions; +using Enyim.Caching.Memcached.Results.Helpers; + +namespace Enyim.Caching.Memcached.Protocol.Binary +{ + public class TouchOperation : BinarySingleItemOperation, ITouchOperation + { + private static readonly Enyim.Caching.ILog log = Enyim.Caching.LogManager.GetLogger(typeof(TouchOperation)); + + private uint expires; + + public TouchOperation(string key, uint expires) : base(key) + { + this.expires = expires; + } + + protected override BinaryRequest Build() + { + var extra = new byte[4]; + BinaryConverter.EncodeUInt32(expires, extra, 0); + + var request = new BinaryRequest(OpCode.Touch) + { + Key = this.Key, + Extra = new ArraySegment(extra) + }; + + return request; + } + + protected override IOperationResult ProcessResponse(BinaryResponse response) + { + var result = new BinaryOperationResult(); +#if EVEN_MORE_LOGGING + if (log.IsDebugEnabled) + if (response.StatusCode == 0) + log.DebugFormat("Touch succeeded for key '{0}'.", this.Key); + else + log.DebugFormat("Touch failed for key '{0}'. Reason: {1}", this.Key, Encoding.ASCII.GetString(response.Data.Array, response.Data.Offset, response.Data.Count)); +#endif + if (response.StatusCode == 0) + { + return result.Pass(); + } + else + { + var message = ResultHelper.ProcessResponseData(response.Data); + return result.Fail(message); + } + } + } +} + +#region [ License information ] +/* ************************************************************ + * + * Copyright (c) 2010 Attila Kiskó, enyim.com + * + * Licensed 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. + * + * ************************************************************/ +#endregion diff --git a/Enyim.Caching/Memcached/Protocol/Text/TextOperationFactory.cs b/Enyim.Caching/Memcached/Protocol/Text/TextOperationFactory.cs index a117d92f..54c125b5 100644 --- a/Enyim.Caching/Memcached/Protocol/Text/TextOperationFactory.cs +++ b/Enyim.Caching/Memcached/Protocol/Text/TextOperationFactory.cs @@ -53,6 +53,11 @@ IFlushOperation IOperationFactory.Flush() { return new FlushOperation(); } + + ITouchOperation IOperationFactory.Touch(string key, uint expires) + { + return new TouchOperation(key, expires); + } } } diff --git a/Enyim.Caching/Memcached/Protocol/Text/TouchOperation.cs b/Enyim.Caching/Memcached/Protocol/Text/TouchOperation.cs new file mode 100644 index 00000000..39c2d3dc --- /dev/null +++ b/Enyim.Caching/Memcached/Protocol/Text/TouchOperation.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using Enyim.Caching.Memcached.Results; + +namespace Enyim.Caching.Memcached.Protocol.Text +{ + public class TouchOperation : SingleItemOperation, ITouchOperation + { + private uint expires; + + internal TouchOperation(string key, uint expires) + : base(key) + { + this.expires = expires; + } + + protected internal override IList> GetBuffer() + { + var command = "touch " + this.Key + " " + this.expires + TextSocketHelper.CommandTerminator; + + return TextSocketHelper.GetCommandBuffer(command); + } + + protected internal override IOperationResult ReadResponse(PooledSocket socket) + { + return new TextOperationResult + { + Success = String.Compare(TextSocketHelper.ReadResponse(socket), "TOUCHED", StringComparison.Ordinal) == 0 + }; + } + + protected internal override bool ReadResponseAsync(PooledSocket socket, System.Action next) + { + throw new System.NotSupportedException(); + } + } +} + +#region [ License information ] +/* ************************************************************ + * + * Copyright (c) 2010 Attila Kiskó, enyim.com + * + * Licensed 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. + * + * ************************************************************/ +#endregion diff --git a/Enyim.Caching/Memcached/Results/Factories/DefaultTouchOperationResultFactory.cs b/Enyim.Caching/Memcached/Results/Factories/DefaultTouchOperationResultFactory.cs new file mode 100644 index 00000000..8294c41b --- /dev/null +++ b/Enyim.Caching/Memcached/Results/Factories/DefaultTouchOperationResultFactory.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Enyim.Caching.Memcached.Results.Factories +{ + public class DefaultTouchOperationResultFactory : ITouchOperationResultFactory + { + public ITouchOperationResult Create() + { + return new TouchOperationResult() + { + Success = false, + Message = string.Empty + }; + } + } +} + +#region [ License information ] +/* ************************************************************ + * + * @author Couchbase + * @copyright 2012 Couchbase, Inc. + * @copyright 2012 Attila Kiskó, enyim.com + * + * Licensed 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. + * + * ************************************************************/ +#endregion \ No newline at end of file diff --git a/Enyim.Caching/Memcached/Results/Factories/ITouchOperationResultFactory.cs b/Enyim.Caching/Memcached/Results/Factories/ITouchOperationResultFactory.cs new file mode 100644 index 00000000..3c8b84dd --- /dev/null +++ b/Enyim.Caching/Memcached/Results/Factories/ITouchOperationResultFactory.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Enyim.Caching.Memcached.Results.Factories +{ + public interface ITouchOperationResultFactory + { + ITouchOperationResult Create(); + } +} + +#region [ License information ] +/* ************************************************************ + * + * @author Couchbase + * @copyright 2012 Couchbase, Inc. + * @copyright 2012 Attila Kiskó, enyim.com + * + * Licensed 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. + * + * ************************************************************/ +#endregion \ No newline at end of file diff --git a/Enyim.Caching/Memcached/Results/ITouchOperationResult.cs b/Enyim.Caching/Memcached/Results/ITouchOperationResult.cs new file mode 100644 index 00000000..bc173f2d --- /dev/null +++ b/Enyim.Caching/Memcached/Results/ITouchOperationResult.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Enyim.Caching.Memcached.Results +{ + public interface ITouchOperationResult : IOperationResult + { + } +} + +#region [ License information ] +/* ************************************************************ + * + * @author Couchbase + * @copyright 2012 Couchbase, Inc. + * @copyright 2012 Attila Kiskó, enyim.com + * + * Licensed 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. + * + * ************************************************************/ +#endregion \ No newline at end of file diff --git a/Enyim.Caching/Memcached/Results/TouchOperationResult.cs b/Enyim.Caching/Memcached/Results/TouchOperationResult.cs new file mode 100644 index 00000000..d5f98ff2 --- /dev/null +++ b/Enyim.Caching/Memcached/Results/TouchOperationResult.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Enyim.Caching.Memcached.Results +{ + public class TouchOperationResult : OperationResultBase, ITouchOperationResult + { + + } +} + +#region [ License information ] +/* ************************************************************ + * + * @author Couchbase + * @copyright 2012 Couchbase, Inc. + * @copyright 2012 Attila Kiskó, enyim.com + * + * Licensed 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. + * + * ************************************************************/ +#endregion \ No newline at end of file diff --git a/Enyim.Caching/MemcachedClient.cs b/Enyim.Caching/MemcachedClient.cs index b5319874..11988b91 100755 --- a/Enyim.Caching/MemcachedClient.cs +++ b/Enyim.Caching/MemcachedClient.cs @@ -35,6 +35,7 @@ public partial class MemcachedClient : IMemcachedClient, IMemcachedResultsClient public IMutateOperationResultFactory MutateOperationResultFactory { get; set; } public IConcatOperationResultFactory ConcatOperationResultFactory { get; set; } public IRemoveOperationResultFactory RemoveOperationResultFactory { get; set; } + public ITouchOperationResultFactory TouchOperationResultFactory { get; set; } /// /// Initializes a new MemcachedClient instance using the default configuration section (enyim/memcached). @@ -79,6 +80,7 @@ public MemcachedClient(IMemcachedClientConfiguration configuration) MutateOperationResultFactory = new DefaultMutateOperationResultFactory(); ConcatOperationResultFactory = new DefaultConcatOperationResultFactory(); RemoveOperationResultFactory = new DefaultRemoveOperationResultFactory(); + TouchOperationResultFactory = new DefaultTouchOperationResultFactory(); } public MemcachedClient(IServerPool pool, IMemcachedKeyTransformer keyTransformer, ITranscoder transcoder) @@ -717,6 +719,50 @@ protected virtual IConcatOperationResult PerformConcatenate(ConcatenationMode mo return result; } + #endregion + #region [ Touch ] + + public bool Touch(string key, TimeSpan validFor) + { + return this.PerformTouch(key, (uint)validFor.TotalSeconds).Success; + } + + protected virtual ITouchOperationResult PerformTouch(string key, uint expires) + { + var hashedKey = this.keyTransformer.Transform(key); + var node = this.pool.Locate(hashedKey); + var result = TouchOperationResultFactory.Create(); + + if (node != null) + { + var command = this.pool.OperationFactory.Touch(hashedKey, expires); + var commandResult = node.Execute(command); + + result.StatusCode = command.StatusCode; + + if (commandResult.Success) + { + //if (this.performanceMonitor != null) this.performanceMonitor.s.Mutate(mode, 1, commandResult.Success); + //result.Value = command.Result; + result.Pass(); + return result; + } + else + { + //if (this.performanceMonitor != null) this.performanceMonitor.Mutate(mode, 1, false); + result.InnerResult = commandResult; + result.Fail("Mutate operation failed, see InnerResult or StatusCode for more details"); + } + + } + + //if (this.performanceMonitor != null) this.performanceMonitor.Mutate(mode, 1, false); + + // TODO not sure about the return value when the command fails + result.Fail("Unable to locate node"); + return result; + } + #endregion /// From 1550f81555b78fbb0da66051137973e256233b56 Mon Sep 17 00:00:00 2001 From: Tony Roberts Date: Thu, 29 Sep 2016 15:35:32 +0100 Subject: [PATCH 2/3] Add unit tests for touch operation. --- .../Enyim.Caching.Tests.csproj | 1 + .../MemcachedClientTestsBase.cs | 12 +++++ .../MemcachedClientTouchTests.cs | 52 +++++++++++++++++++ Enyim.Caching/MemcachedClient.Results.cs | 7 +++ 4 files changed, 72 insertions(+) create mode 100644 Enyim.Caching.Tests/MemcachedClientTouchTests.cs diff --git a/Enyim.Caching.Tests/Enyim.Caching.Tests.csproj b/Enyim.Caching.Tests/Enyim.Caching.Tests.csproj index d071389e..7b742b24 100644 --- a/Enyim.Caching.Tests/Enyim.Caching.Tests.csproj +++ b/Enyim.Caching.Tests/Enyim.Caching.Tests.csproj @@ -64,6 +64,7 @@ + diff --git a/Enyim.Caching.Tests/MemcachedClientTestsBase.cs b/Enyim.Caching.Tests/MemcachedClientTestsBase.cs index fb83e5f0..f2e6596b 100644 --- a/Enyim.Caching.Tests/MemcachedClientTestsBase.cs +++ b/Enyim.Caching.Tests/MemcachedClientTestsBase.cs @@ -137,6 +137,18 @@ protected void ConcatAssertFail(IConcatOperationResult result) Assert.That(result.Cas, Is.EqualTo(0), "Cas value was not 0"); Assert.That(result.StatusCode, Is.Null.Or.GreaterThan(0), "StatusCode not greater than 0"); } + + protected void TouchAssertPass(ITouchOperationResult result) + { + Assert.That(result.Success, Is.True, "Success was false"); + Assert.That(result.StatusCode, Is.EqualTo(0), "StatusCode was not 0"); + } + + protected void TouchAssertFail(ITouchOperationResult result) + { + Assert.That(result.Success, Is.False, "Success was true"); + Assert.That(result.StatusCode, Is.Null.Or.GreaterThan(0), "StatusCode not greater than 0"); + } } } diff --git a/Enyim.Caching.Tests/MemcachedClientTouchTests.cs b/Enyim.Caching.Tests/MemcachedClientTouchTests.cs new file mode 100644 index 00000000..51c8ca11 --- /dev/null +++ b/Enyim.Caching.Tests/MemcachedClientTouchTests.cs @@ -0,0 +1,52 @@ +using System; +using NUnit.Framework; + + +namespace Enyim.Caching.Tests +{ + [TestFixture] + public class MemcachedClientTouchTests : MemcachedClientTestsBase + { + [Test] + public void When_Touching_Existing_Item_Result_Is_Successful() + { + var key = GetUniqueKey("get"); + var value = GetRandomString(); + var storeResult = Store(key: key, value: value); + StoreAssertPass(storeResult); + + var touchResult = _Client.ExecuteTouch(key, new TimeSpan(0, 1, 0)); + TouchAssertPass(touchResult); + } + + [Test] + public void When_Touching_Nonexistent_Item_Result_Is_NotSuccessful() + { + var key = GetUniqueKey("get"); + var touchResult = _Client.ExecuteTouch(key, new TimeSpan(0, 1, 0)); + TouchAssertFail(touchResult); + } + } +} + +#region [ License information ] +/* ************************************************************ + * + * @author Couchbase + * @copyright 2012 Couchbase, Inc. + * @copyright 2012 Attila Kiskó, enyim.com + * + * Licensed 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. + * + * ************************************************************/ +#endregion \ No newline at end of file diff --git a/Enyim.Caching/MemcachedClient.Results.cs b/Enyim.Caching/MemcachedClient.Results.cs index 8d1b33ea..441c809b 100644 --- a/Enyim.Caching/MemcachedClient.Results.cs +++ b/Enyim.Caching/MemcachedClient.Results.cs @@ -470,6 +470,13 @@ public IRemoveOperationResult ExecuteRemove(string key) } #endregion + + #region [ Touch ] + public ITouchOperationResult ExecuteTouch(string key, TimeSpan validFor) + { + return this.PerformTouch(key, MemcachedClient.GetExpiration(validFor)); + } + #endregion [ Touch ] } } From 30112bcceabedeafa66e935d06e39531ac3e8765 Mon Sep 17 00:00:00 2001 From: Mahir Sertkaya Date: Wed, 6 Jan 2016 09:38:09 +0200 Subject: [PATCH 3/3] Update readme for Touch operation definitions --- README.mdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.mdown b/README.mdown index 03161dc5..0e1d9de2 100644 --- a/README.mdown +++ b/README.mdown @@ -10,6 +10,7 @@ Features: * CheckAndSet operations * Persistent connections for more speed * SASL Authentication +* Touch operations (After 1.4.8 version of memcached server, for extending items' TTLs) ## Requirements