Skip to content

Commit b8250aa

Browse files
(#16) Add command timeout
1 parent 8a024a5 commit b8250aa

File tree

11 files changed

+131
-22
lines changed

11 files changed

+131
-22
lines changed

src/Stravaig.Configuration.SqlServer.Tests/FakeDataLoader.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ public IDictionary<string, string> RetrieveData(SqlServerConfigurationSource sou
2323

2424
public class FakeSqlServerConfigurationWatcher : ISqlServerConfigurationWatcher
2525
{
26-
ILogger _logger = NullLogger.Instance;
27-
private SqlServerConfigurationProvider _provider;
26+
ILogger? _logger = NullLogger.Instance;
27+
private SqlServerConfigurationProvider? _provider;
2828

2929
public void EnsureStarted()
3030
{
@@ -35,10 +35,8 @@ public void AttachLogger(ILogger logger)
3535
_logger = logger;
3636
}
3737

38-
public void AttacheProvider(SqlServerConfigurationProvider provider)
38+
public void AttachProvider(SqlServerConfigurationProvider provider)
3939
{
4040
_provider = provider;
4141
}
42-
43-
4442
}

src/Stravaig.Configuration.SqlServer.Tests/SqlServerConfigurationProviderTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public void ReplayLogExceptionOnLoadFailure()
103103
thirdLog.PropertyDictionary["exceptionMessage"].ShouldBe("Dummy Exception.");
104104
logs[3].OriginalMessage.ShouldBe("End of replay.");
105105
}
106-
106+
107107
private SqlServerConfigurationProvider SetupProvider()
108108
{
109109
_fakeLoader = new FakeDataLoader()
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using Microsoft.Extensions.Logging.Abstractions;
3+
using NUnit.Framework;
4+
using Shouldly;
5+
using Stravaig.Configuration.SqlServer.Glue;
6+
using Stravaig.Extensions.Logging.Diagnostics;
7+
using Stravaig.Extensions.Logging.Diagnostics.Render;
8+
9+
namespace Stravaig.Configuration.SqlServer.Tests;
10+
11+
[TestFixture]
12+
public class SqlServerConfigurationSourceExtensionTests
13+
{
14+
private const string DummyConnectionString = "Server=localhost;Database=testing;Connection Timeout=5";
15+
16+
[Test]
17+
public void CreateLoggerWhenNotExpectingALoggerIsNullLogger()
18+
{
19+
var source = new SqlServerConfigurationSource(DummyConnectionString, expectLogger: false);
20+
21+
source.CreateLogger().ShouldBe(NullLogger<SqlServerConfigurationProvider>.Instance);
22+
}
23+
24+
[Test]
25+
public void CreateLoggerWhenExpectingALoggerIsReplayLogger()
26+
{
27+
var source = new SqlServerConfigurationSource(DummyConnectionString, expectLogger: true);
28+
29+
source.CreateLogger().ShouldBeOfType<ReplayLogger<SqlServerConfigurationProvider>>();
30+
}
31+
32+
[Test]
33+
public void CreateLoggerIndicatesHowTheProviderIsSetup()
34+
{
35+
var source = new SqlServerConfigurationSource(DummyConnectionString, expectLogger: true, refreshInterval: TimeSpan.FromSeconds(120), commandTimeout: TimeSpan.FromSeconds(10));
36+
var replayLogger = (ReplayLogger<SqlServerConfigurationProvider>)source.CreateLogger();
37+
var captureLogger = new TestCaptureLogger<SqlServerConfigurationProvider>();
38+
replayLogger.Replay(captureLogger);
39+
40+
var logs = captureLogger.GetLogs();
41+
logs.RenderLogs(Formatter.SimpleBySequence, Sink.Console);
42+
43+
logs[1].OriginalMessage.ShouldBe("SQL Server Configuration Provider will retrieve from [{serverName}].[{databaseName}].[{schemaName}].[{tableName}] {frequency}. Will wait {connectionTimeout} seconds to connect, and {commandTimeout} seconds to retrieve data.");
44+
logs[1].PropertyDictionary["serverName"].ShouldBe("localhost");
45+
logs[1].PropertyDictionary["databaseName"].ShouldBe("testing");
46+
logs[1].PropertyDictionary["schemaName"].ShouldBe("Stravaig");
47+
logs[1].PropertyDictionary["tableName"].ShouldBe("AppConfiguration");
48+
logs[1].PropertyDictionary["frequency"].ShouldBe("every 120 seconds");
49+
logs[1].PropertyDictionary["connectionTimeout"].ShouldBe(5);
50+
logs[1].PropertyDictionary["commandTimeout"].ShouldBe(10);
51+
}
52+
53+
}

src/Stravaig.Configuration.SqlServer/Glue/DataLoader.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Data.SqlClient;
34

45
namespace Stravaig.Configuration.SqlServer.Glue;
@@ -15,6 +16,7 @@ public IDictionary<string, string> RetrieveData(SqlServerConfigurationSource sou
1516
using SqlConnection connection = new SqlConnection(source.ConnectionString);
1617
connection.Open();
1718
var cmd = new SqlCommand(sql, connection);
19+
cmd.CommandTimeout = (int)source.CommandTimeout.TotalSeconds;
1820
using var reader = cmd.ExecuteReader();
1921
return MaterialiseData(reader);
2022
}

src/Stravaig.Configuration.SqlServer/Glue/DefaultValues.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ namespace Stravaig.Configuration.SqlServer.Glue;
33
internal static class DefaultValues
44
{
55
public const int NoRefresh = 0;
6+
public const int CommandTimeout = 15;
7+
public const int ConnectionTimeOut = 15;
68
public const string SchemaName = "Stravaig";
79
public const string TableName = "AppConfiguration";
810
public const string ConfigurationSection = "Stravaig:AppConfiguration";

src/Stravaig.Configuration.SqlServer/Glue/Log.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,16 @@ public static partial class Log
5858
Level = LogLevel.Warning,
5959
Message = "The replay limit was reached. There are {excessCount} logs that cannot be replayed.")]
6060
public static partial void ReplayLimitExceeded(this ILogger logger, int excessCount);
61+
62+
[LoggerMessage(
63+
EventId = 10,
64+
Level = LogLevel.Information,
65+
Message = "SQL Server Configuration Provider will retrieve from [{serverName}].[{databaseName}].[{schemaName}].[{tableName}] {frequency}. Will wait {connectionTimeout} seconds to connect, and {commandTimeout} seconds to retrieve data.")]
66+
public static partial void InitialProviderDescription(this ILogger logger, string serverName, string databaseName, string schemaName, string tableName, string frequency, int connectionTimeout, int commandTimeout);
67+
68+
[LoggerMessage(
69+
EventId = 11,
70+
Level = LogLevel.Warning,
71+
Message = "The refresh interval, {refreshInterval} seconds, should be greater than combined connection, {connectionTimeout} seconds, and command, {commandTimeout} seconds, timeouts. A new refresh cycle may start before the previous cycle is complete.")]
72+
public static partial void WarnOfCycleInterleave(this ILogger logger, int refreshInterval, int connectionTimeout, int commandTimeout);
6173
}

src/Stravaig.Configuration.SqlServer/Glue/SourceBuilder.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ public static SqlServerConfigurationSource BuildSource(IConfigurationBuilder bui
2121
ApplyFromConnectionStringsSection(options, configRoot.Value);
2222

2323
return new SqlServerConfigurationSource(
24-
options.ConnectionString ?? throw new SqlServerConfigurationProviderException("Cannot build a SQL Server Configuration Provider without a connection string."),
25-
options.IsLoggerExpected,
26-
TimeSpan.FromSeconds(options.RefreshSeconds),
27-
options.SchemaName ?? throw new SqlServerConfigurationProviderException("The schema name is required to use SQL Server Configuration."),
28-
options.TableName ?? throw new SqlServerConfigurationProviderException("The table name is required to use SQL Server Configuration."));
24+
connectionString: options.ConnectionString ?? throw new SqlServerConfigurationProviderException("Cannot build a SQL Server Configuration Provider without a connection string."),
25+
expectLogger: options.IsLoggerExpected,
26+
commandTimeout: TimeSpan.FromSeconds(options.CommandTimeout),
27+
refreshInterval: TimeSpan.FromSeconds(options.RefreshSeconds),
28+
schemaName: options.SchemaName ?? throw new SqlServerConfigurationProviderException("The schema name is required to use SQL Server Configuration."),
29+
tableName: options.TableName ?? throw new SqlServerConfigurationProviderException("The table name is required to use SQL Server Configuration."));
2930
}
3031

3132
private static void ApplyFromConnectionStringsSection(SqlServerConfigurationOptions options, IConfigurationRoot configRoot)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using Microsoft.Extensions.Logging;
3+
using Microsoft.Extensions.Logging.Abstractions;
4+
5+
namespace Stravaig.Configuration.SqlServer.Glue;
6+
7+
internal static class SqlServerConfigurationSourceExtensions
8+
{
9+
public static ILogger<SqlServerConfigurationProvider> CreateLogger(this SqlServerConfigurationSource source)
10+
{
11+
if (!source.ExpectLogger)
12+
return NullLogger<SqlServerConfigurationProvider>.Instance;
13+
14+
var logger = new ReplayLogger<SqlServerConfigurationProvider>();
15+
logger.InitialProviderDescription(
16+
source.ServerName,
17+
source.DatabaseName,
18+
source.SchemaName,
19+
source.TableName,
20+
frequency: source.RefreshInterval == TimeSpan.Zero ? "once only" : $"every {source.RefreshInterval.TotalSeconds} seconds",
21+
(int)source.ConnectionTimeout.TotalSeconds,
22+
(int)source.CommandTimeout.TotalSeconds);
23+
24+
if (source.ConnectionTimeout + source.CommandTimeout > source.RefreshInterval)
25+
logger.WarnOfCycleInterleave(
26+
(int)source.RefreshInterval.TotalSeconds,
27+
(int)source.ConnectionTimeout.TotalSeconds,
28+
(int)source.CommandTimeout.TotalSeconds);
29+
return logger;
30+
31+
}
32+
}

src/Stravaig.Configuration.SqlServer/SqlServerConfigurationOptions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public class SqlServerConfigurationOptions
1515
public string TableName { get; set; } = DefaultValues.TableName;
1616

1717
public int RefreshSeconds { get; set; } = DefaultValues.NoRefresh;
18+
19+
public int CommandTimeout { get; set; } = DefaultValues.CommandTimeout;
1820

1921
public SqlServerConfigurationOptions FromExistingConfiguration(string configurationSection = DefaultValues.ConfigurationSection)
2022
{

src/Stravaig.Configuration.SqlServer/SqlServerConfigurationProvider.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class SqlServerConfigurationProvider : ConfigurationProvider
1616
private readonly ISqlServerConfigurationWatcher _watcher;
1717

1818
public SqlServerConfigurationProvider(SqlServerConfigurationSource source)
19-
: this (source, new DataLoader(), NullSqlServerConfigurationWatcher.Instance, CreateLogger(source))
19+
: this (source, new DataLoader(), NullSqlServerConfigurationWatcher.Instance, source.CreateLogger())
2020
{
2121
_watcher = CreateWatcher(source, this);
2222
}
@@ -34,14 +34,7 @@ internal SqlServerConfigurationProvider(
3434
_watcher = watcher;
3535
_watcher.AttachLogger(_logger);
3636
}
37-
38-
private static ILogger<SqlServerConfigurationProvider> CreateLogger(SqlServerConfigurationSource source)
39-
{
40-
return source.ExpectLogger
41-
? new ReplayLogger<SqlServerConfigurationProvider>()
42-
: NullLogger<SqlServerConfigurationProvider>.Instance;
43-
}
44-
37+
4538
private static ISqlServerConfigurationWatcher CreateWatcher(SqlServerConfigurationSource source, SqlServerConfigurationProvider provider)
4639
{
4740
return source.RefreshInterval == TimeSpan.Zero

0 commit comments

Comments
 (0)