Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

using System;
using System.Collections.Generic;
using Adaptive.Agrona;
using Adaptive.Aeron.LogBuffer;
using Adaptive.Archiver.IntegrationTests.Helpers;
using Adaptive.Archiver.IntegrationTests.Infrastructure;
Expand Down Expand Up @@ -84,32 +85,13 @@ public virtual void SetUp()
[TearDown]
public virtual void TearDown()
{
foreach (var c in System.Linq.Enumerable.Reverse(Closeables))
{
DisposeWithTimeout(c, 3_000, "Closeable");
}
CloseHelper.CloseAll(System.Linq.Enumerable.Reverse(Closeables));
Closeables.Clear();

DisposeWithTimeout(AeronArchive, 3_000, "AeronArchive");
DisposeWithTimeout(Aeron, 3_000, "Aeron");

try
{
Archive?.Dispose();
}
catch
{
// Ignored
}

try
{
Driver?.Dispose();
}
catch
{
// Ignored
}
CloseHelper.Dispose(AeronArchive);
CloseHelper.Dispose(Aeron);
CloseHelper.Dispose(Archive);
CloseHelper.Dispose(Driver);
}

private static void DisposeWithTimeout(IDisposable target, int timeoutMs, string name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2026 Adaptive Financial Consulting Ltd
*
* 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.
*/

using System;
using System.IO;
using Adaptive.Aeron;
using Adaptive.Aeron.LogBuffer;
using Adaptive.Agrona;
using Adaptive.Agrona.Concurrent;
using Adaptive.Agrona.Concurrent.Status;
using Adaptive.Cluster.Service;
using FakeItEasy;
using NUnit.Framework;
using AeronType = Adaptive.Aeron.Aeron;

namespace Adaptive.Cluster.Tests.Service
{
public class ClusteredServiceContainerContextTest
{
private ClusteredServiceContainer.Context _context;
private DirectoryInfo _clusterDir;

[SetUp]
public void Before()
{
_clusterDir = new DirectoryInfo(
Path.Combine(Path.GetTempPath(), "cluster-ctx-test-" + Guid.NewGuid()));

AeronType aeron = A.Fake<AeronType>();
AeronType.Context aeronCtx = A.Fake<AeronType.Context>();
A.CallTo(() => aeronCtx.AeronDirectoryName()).Returns("test-aeron-dir");
A.CallTo(() => aeronCtx.SubscriberErrorHandler()).Returns(RethrowingErrorHandler.INSTANCE);
A.CallTo(() => aeronCtx.FilePageSize()).Returns(LogBufferDescriptor.PAGE_MIN_SIZE);
A.CallTo(() => aeron.Ctx).Returns(aeronCtx);

UnsafeBuffer metaDataBuffer = new UnsafeBuffer(new byte[128 * 1024]);
UnsafeBuffer valuesBuffer = new UnsafeBuffer(new byte[64 * 1024]);
CountersManager countersManager = new CountersManager(metaDataBuffer, valuesBuffer);

A.CallTo(() => aeron.AddCounter(A<int>._, A<IDirectBuffer>._, A<int>._, A<int>._, A<IDirectBuffer>._,
A<int>._, A<int>._))
.ReturnsLazily((int typeId, IDirectBuffer kb, int ko, int kl, IDirectBuffer lb, int lo, int ll) =>
new Counter(countersManager, countersManager.Allocate("my-counter", typeId)));

_context = new ClusteredServiceContainer.Context()
.AeronClient(aeron)
.ClusterDir(_clusterDir)
.ServiceId(0)
.ClusteredService(A.Fake<IClusteredService>());
}

[TearDown]
public void After()
{
_context?.Dispose();
_context?.DeleteDirectory();
}

[Test]
public void ShouldUseDefaultVersionValidatorWhenNoneSuppliedToContext()
{
_context.Conclude();

Assert.That(_context.AppVersionValidator(), Is.SameAs(AppVersionValidator.SEMANTIC_VERSIONING_VALIDATOR));
}

[Test]
public void ShouldPreserveCustomVersionValidatorSuppliedToContextThroughConclude()
{
var customValidator = A.Fake<IVersionValidator>();
_context.AppVersionValidator(customValidator);

_context.Conclude();

Assert.That(_context.AppVersionValidator(), Is.SameAs(customValidator));
}
}
}
15 changes: 5 additions & 10 deletions src/Adaptive.Cluster/AppVersionValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,20 @@
namespace Adaptive.Cluster
{
/// <summary>
/// Class to be used for determining AppVersion compatibility.
/// Default <seealso cref="IVersionValidator"/> used for determining AppVersion compatibility, which uses
/// <seealso cref="SemanticVersion"/> major version for checking compatibility.
/// <para>
/// Default is to use <seealso cref="SemanticVersion"/> major version for checking compatibility.
/// A custom policy can be supplied by implementing <seealso cref="IVersionValidator"/>.
/// </para>
/// </summary>
public class AppVersionValidator
public class AppVersionValidator : IVersionValidator
{
/// <summary>
/// Singleton instance of <seealso cref="AppVersionValidator"/> version which can be used to avoid allocation.
/// </summary>
public static readonly AppVersionValidator SEMANTIC_VERSIONING_VALIDATOR = new AppVersionValidator();

/// <summary>
/// Check version compatibility between configured context appVersion and appVersion in new leadership term or
/// snapshot.
/// </summary>
/// <param name="contextAppVersion"> configured appVersion value from context. </param>
/// <param name="appVersionUnderTest"> to check against configured appVersion. </param>
/// <returns> true for compatible or false for not compatible. </returns>
/// <inheritdoc />
public bool IsVersionCompatible(int contextAppVersion, int appVersionUnderTest)
{
return SemanticVersion.Major(contextAppVersion) == SemanticVersion.Major(appVersionUnderTest);
Expand Down
38 changes: 38 additions & 0 deletions src/Adaptive.Cluster/IVersionValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2026 Adaptive Financial Consulting Ltd
*
* 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.
*/

using Adaptive.Agrona;

namespace Adaptive.Cluster
{
/// <summary>
/// Interface for determining AppVersion compatibility, so that a custom policy can be supplied via
/// <seealso cref="Service.ClusteredServiceContainer.Context.AppVersionValidator(IVersionValidator)"/>.
/// The default implementation is <seealso cref="AppVersionValidator.SEMANTIC_VERSIONING_VALIDATOR"/> which uses
/// <seealso cref="SemanticVersion"/> major version for checking compatibility.
/// </summary>
public interface IVersionValidator
{
/// <summary>
/// Check version compatibility between configured context appVersion and appVersion in new leadership term or
/// snapshot.
/// </summary>
/// <param name="contextAppVersion"> configured appVersion value from context. </param>
/// <param name="appVersionUnderTest"> to check against configured appVersion. </param>
/// <returns> true for compatible or false for not compatible. </returns>
bool IsVersionCompatible(int contextAppVersion, int appVersionUnderTest);
}
}
6 changes: 6 additions & 0 deletions src/Adaptive.Cluster/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Adaptive.Cluster.Codecs.JoinLogDecoder.IsStandby() -> Adaptive.Cluster.Codecs.Bo
Adaptive.Cluster.Codecs.JoinLogEncoder.IsStandby(Adaptive.Cluster.Codecs.BooleanType value) -> Adaptive.Cluster.Codecs.JoinLogEncoder
Adaptive.Cluster.Codecs.NewLeadershipTermDecoder.CommitPosition() -> long
Adaptive.Cluster.Codecs.NewLeadershipTermEncoder.CommitPosition(long value) -> Adaptive.Cluster.Codecs.NewLeadershipTermEncoder
Adaptive.Cluster.IVersionValidator
Adaptive.Cluster.IVersionValidator.IsVersionCompatible(int contextAppVersion, int appVersionUnderTest) -> bool
Adaptive.Cluster.Service.ClusteredServiceContainer.Context.AppVersionValidator() -> Adaptive.Cluster.IVersionValidator
Adaptive.Cluster.Service.ClusteredServiceContainer.Context.AppVersionValidator(Adaptive.Cluster.IVersionValidator versionValidator) -> Adaptive.Cluster.Service.ClusteredServiceContainer.Context
const Adaptive.Cluster.Codecs.AddPassiveMemberDecoder.SCHEMA_VERSION = 16 -> ushort
const Adaptive.Cluster.Codecs.AddPassiveMemberEncoder.SCHEMA_VERSION = 16 -> ushort
const Adaptive.Cluster.Codecs.AdminRequestDecoder.SCHEMA_VERSION = 16 -> ushort
Expand Down Expand Up @@ -266,3 +270,5 @@ static Adaptive.Cluster.Codecs.NewLeadershipTermEncoder.CommitPositionNullValue(
*REMOVED*const Adaptive.Cluster.Codecs.TimerEventEncoder.SCHEMA_VERSION = 14 -> ushort
*REMOVED*const Adaptive.Cluster.Codecs.VoteDecoder.SCHEMA_VERSION = 14 -> ushort
*REMOVED*const Adaptive.Cluster.Codecs.VoteEncoder.SCHEMA_VERSION = 14 -> ushort
*REMOVED*Adaptive.Cluster.Service.ClusteredServiceContainer.Context.AppVersionValidator() -> Adaptive.Cluster.AppVersionValidator
*REMOVED*Adaptive.Cluster.Service.ClusteredServiceContainer.Context.AppVersionValidator(Adaptive.Cluster.AppVersionValidator appVersionValidator) -> Adaptive.Cluster.Service.ClusteredServiceContainer.Context
18 changes: 9 additions & 9 deletions src/Adaptive.Cluster/Service/ClusteredServiceContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ public class Context
private Aeron.Aeron _aeron;
private DutyCycleTracker _dutyCycleTracker;
private SnapshotDurationTracker _snapshotDurationTracker;
private AppVersionValidator _appVersionValidator;
private IVersionValidator _versionValidator;
private bool _ownsAeronClient;

private IClusteredService _clusteredService;
Expand Down Expand Up @@ -748,9 +748,9 @@ public void Conclude()
_idleStrategySupplier = Configuration.IdleStrategySupplier(null);
}

if (null == _appVersionValidator)
if (null == _versionValidator)
{
_appVersionValidator = Cluster.AppVersionValidator.SEMANTIC_VERSIONING_VALIDATOR;
_versionValidator = Cluster.AppVersionValidator.SEMANTIC_VERSIONING_VALIDATOR;
}

if (null == _epochClock)
Expand Down Expand Up @@ -1026,11 +1026,11 @@ public int AppVersion()
///
/// </para>
/// </summary>
/// <param name="appVersionValidator"> for user application. </param>
/// <param name="versionValidator"> for user application. </param>
/// <returns> this for fluent API. </returns>
public Context AppVersionValidator(AppVersionValidator appVersionValidator)
public Context AppVersionValidator(IVersionValidator versionValidator)
{
this._appVersionValidator = appVersionValidator;
this._versionValidator = versionValidator;
return this;
}

Expand All @@ -1041,10 +1041,10 @@ public Context AppVersionValidator(AppVersionValidator appVersionValidator)
///
/// </para>
/// </summary>
/// <returns> AppVersionValidator in use. </returns>
public AppVersionValidator AppVersionValidator()
/// <returns> IVersionValidator in use. </returns>
public IVersionValidator AppVersionValidator()
{
return _appVersionValidator;
return _versionValidator;
}

/// <summary>
Expand Down
Loading