private static void EnsureRedisCheckedIfNeeded() { if (_localRedisTested) { return; } lock (LocalRedisTestSynch) { if (_localRedisTested) { return; } try { var environmentVariableRedisConfig = Environment.GetEnvironmentVariable(RedisConfigurationEnvironmentVariable); //environmentVariableRedisConfig = ""; _redisConfiguration = string.IsNullOrWhiteSpace(environmentVariableRedisConfig) ? DefaultLocalRedisConfiguration : environmentVariableRedisConfig; // Skip tests instead of throwing errors if we are defaulting to localhost // if we have configured redis via REDIS_CONFIGURATION_FOR_UNIT_TESTS // we want a failed build/tests. _localRedisConfiguredForTests = !string.IsNullOrWhiteSpace(environmentVariableRedisConfig); _localRedisTestTime = DateTime.Now; using (var basicRedis = new BasicRedisWrapper(_redisConfiguration, false)) { basicRedis.Cache.StringSet( $"{typeof(TestRedisConfig).FullName}:{nameof(GetTestEnvironmentRedis)}:{Guid.NewGuid():N}", "value", TimeSpan.FromSeconds(3)); _localRedisAvailable = true; } } catch (Exception ex) { _localRedisTestFailDetails = ex.ToString(); _localRedisAvailable = false; } _localRedisTested = true; } }
public void Locks() { var redisConnection = TestRedisConfig.GetTestEnvironmentRedis(); var testId = Guid.NewGuid(); using (var basicRedis1 = new BasicRedisWrapper(redisConnection, false)) using (var basicRedis2 = new BasicRedisWrapper(redisConnection, false)) using (var basicRedis3 = new BasicRedisWrapper(redisConnection, false)) { using (var lck1A = basicRedis1.Lock.CreateLock($"{typeof(TestRedisConfig).FullName}:Lock1:{testId:N}", TimeSpan.FromSeconds(30))) using (var lck2 = basicRedis2.Lock.CreateLock($"{typeof(TestRedisConfig).FullName}:Lock2:{testId:N}", TimeSpan.FromSeconds(30))) using (var lck1B = basicRedis3.Lock.CreateLock($"{typeof(TestRedisConfig).FullName}:Lock1:{testId:N}", TimeSpan.FromSeconds(30))) { Assert.NotNull(lck1A); Assert.NotNull(lck2); Assert.Null(lck1B); } } }
/// <summary> /// Create an interceptor based on real redis connection(s) or a local in memory redis mock. /// Note: local in memory redis mock does not support "multiple connections" (there is no actual connecting going on) /// </summary> /// <param name="redisConfiguration">redis connection config (e.g. "localhost:6379") or "mock-{uniqueid}"</param> /// <param name="useMultipleRedisConnections">use a single redis connection vs multiple for different concerns (locking, caching, messaging)</param> public RedisInterceptOrMock(string redisConfiguration, bool useMultipleRedisConnections = false) { if (redisConfiguration.ToLowerInvariant().StartsWith("mock-")) { if (useMultipleRedisConnections) { throw new ArgumentException($"Mock redis and useMultipleRedisConnections is non-sensical"); } if (redisConfiguration.ToLowerInvariant() == "mock-") { throw new ArgumentException($"Mock redis id should be supplied (per fake redis instance)"); } _basicRedisWrapper = null; _redisMock = LocalMemMockOfRedis.Create(redisConfiguration); } else { _basicRedisWrapper = new BasicRedisWrapper(redisConfiguration, useMultipleRedisConnections); _redisMock = null; } }
static void Main(string[] args) { var rndSrc = new Random(2); var testIdSrc = 0; var uniqueCacheId = 1; var ttl = TimeSpan.FromSeconds(10); // 20 min equiv var regen = TimeSpan.FromSeconds(2.5); // 1 min equiv var regenDuration = TimeSpan.FromSeconds(1.25); // duration of call to generate value to store in cache var regenErrorPercentage = 0; var farmClockTollerance = 0.1; var p1Duration = TimeSpan.FromSeconds(10); var p1Frequency = TimeSpan.FromSeconds(0.5); var launchTwoInstances = false; var doRegTest = true; var doPerfTest = true; var writePerformanceCsv = false; var writeTraceOutputFile = false; var writeHtmlOutputFile = false; var outputFolder = "c:\\temp\\"; var perfTestCount = 1000000; var perfTestMeasure = 100000; var keyspace = $"keyspace{Guid.NewGuid():N}"; if (launchTwoInstances && args.Length == 0 && !Debugger.IsAttached) { Process.Start(Assembly.GetEntryAssembly().Location, $"nospawn 1 {keyspace}"); //Task.Delay(2000).Wait(); Process.Start(Assembly.GetEntryAssembly().Location, $"nospawn 2 {keyspace}"); return; } var id = 0; if (args.Length >= 3) { int.TryParse(args[1], out id); keyspace = args[2]; } var synchedConsole = new SynchedColouredConsoleTraceWriter( traceFileName: writeTraceOutputFile ? $"{outputFolder}RegenerativeCacheManagerDemo_{keyspace}_{id}.txt" : null, htmlFileName: writeHtmlOutputFile ? $"{outputFolder}RegenerativeCacheManagerDemo_{keyspace}_{id}.html" : null, performanceFileName: writePerformanceCsv ? $"{outputFolder}RegenerativeCacheManagerDemo_{keyspace}_{id}.csv" : null ); using (var ct1R = new BasicRedisWrapper("localhost")) using (var cacheTest1 = new RegenerativeCacheManager("keyspace3", ct1R.Cache, ct1R.Lock, ct1R.Bus)) using (var ct2R = new BasicRedisWrapper("localhost")) using (var cacheTest2 = new RegenerativeCacheManager("Keyspace3", ct2R.Cache, ct2R.Lock, ct2R.Bus)) { new List <IDisposable> { cacheTest1, ct1R, cacheTest2, ct2R }.ForEach(d => d.Dispose()); } synchedConsole.WriteLine("RegenerativeCacheManager dispose works.", ConsoleColor.Black, ConsoleColor.Green); var redis = new BasicRedisWrapper("localhost", true); var cache = new RegenerativeCacheManager(keyspace, redis.Cache, redis.Lock, redis.Bus, synchedConsole) { // low tollerences in this test due to extremely low cache item re-generation and expiry times, normally 30s to minutes CacheExpiryToleranceSeconds = 1, TriggerDelaySeconds = 1, MinimumForwardSchedulingSeconds = 1, FarmClockToleranceSeconds = farmClockTollerance, }; var generateFunc = new Func <string>(() => { Task.Delay(regenDuration).Wait(); if (rndSrc.Next(0, 100) < regenErrorPercentage) { throw new ApplicationException("Synthetic Error"); } return($" >>> CacheItemVal_{DateTime.Now:ss.fff}_{Interlocked.Increment(ref uniqueCacheId)} <<< "); }); if (doRegTest) { synchedConsole.WriteLine($"Inactive TTL: {ttl.TotalSeconds}s, Regeneration every: {regen.TotalSeconds}s, Regeneration performance: {regenDuration.TotalSeconds}s"); synchedConsole.WriteLine($"------- running get per second (in background) for {p1Duration.TotalSeconds} seconds"); synchedConsole.WriteLine("================================================="); var start = DateTime.UtcNow; while (DateTime.UtcNow.Subtract(start).TotalSeconds < p1Duration.TotalSeconds) { var testId = Interlocked.Increment(ref testIdSrc); Task.Run(() => synchedConsole.WriteLine($"* PART 1 * Cache Value: {MonitorWork(() => cache.GetOrAdd("test1", generateFunc, ttl, regen), synchedConsole, testId)}")); Task.Delay(p1Frequency).Wait(); } synchedConsole.WriteLine("================================================="); synchedConsole.DebugWait(6); var testX1 = Interlocked.Increment(ref testIdSrc); synchedConsole.WriteLine($"* PART 2 * Cache Value: {MonitorWork(() => cache.GetOrAdd("test1", generateFunc, ttl, regen), synchedConsole, testX1)}"); synchedConsole.WriteLine("================================================="); synchedConsole.DebugWait(15); var testX2 = Interlocked.Increment(ref testIdSrc); Task.Run(() => synchedConsole.WriteLine($"* PART 3 * Cache Value: {MonitorWork(() => cache.GetOrAdd("test1", generateFunc, ttl, regen), synchedConsole, testX2)}")); var testX3 = Interlocked.Increment(ref testIdSrc); Task.Run(() => synchedConsole.WriteLine($"* PART 3 * Cache Value: {MonitorWork(() => cache.GetOrAdd("test1", generateFunc, ttl, regen), synchedConsole, testX3)}")); var testX4 = Interlocked.Increment(ref testIdSrc); synchedConsole.WriteLine($"* PART 3 * Cache Value: {MonitorWork(() => cache.GetOrAdd("test1", generateFunc, ttl, regen), synchedConsole, testX4)}"); var testX5 = Interlocked.Increment(ref testIdSrc); synchedConsole.WriteLine($"* PART 3 * Cache Value: {MonitorWork(() => cache.GetOrAdd("test1", generateFunc, ttl, regen), synchedConsole, testX5)}"); synchedConsole.DebugWait(2); synchedConsole.DebugWait(20); ShowStats(synchedConsole); try { Task.WaitAll(MonitoredWorkBag.Select(t => t.Item1).ToArray()); } catch (Exception ex) { synchedConsole.WriteLine($"First error: {(ex as AggregateException)?.InnerExceptions.First().ToString() ?? ex.ToString()}", ConsoleColor.White, ConsoleColor.Red); } while (MonitoredWorkBag.TryTake(out Tuple <Task, TimeSpan> tr)) { tr.Item1.Dispose(); } } if (doRegTest && doPerfTest) { synchedConsole.OpenNewOutputFile(); } if (doPerfTest) { cache.Dispose(); _perTestMeasureEveryN = Math.Max(1, perfTestCount / perfTestMeasure); cache = new RegenerativeCacheManager(keyspace, redis.Cache, redis.Lock, redis.Bus, synchedConsole) { // low tollerences in this test due to extremely low cache item re-generation and expiry times, normally 30s to minutes TriggerDelaySeconds = 1, MinimumForwardSchedulingSeconds = 1, FarmClockToleranceSeconds = farmClockTollerance, CacheExpiryToleranceSeconds = 5, }; synchedConsole.WriteLine($"Running performance test of {perfTestCount:#,##0} cache calls to GetOrAdd in parallel.", ConsoleColor.White, ConsoleColor.DarkBlue); testIdSrc = 1; while (MonitoredWorkBag.TryTake(out Tuple <Task, TimeSpan> tr)) { ; } synchedConsole.ShowFullOutputToConsole = false; // warm up cache.GetOrAdd("test1", generateFunc, ttl, regen); var swWait = Stopwatch.StartNew(); var dop = 10; Parallel.ForEach(Enumerable.Range(0, dop), new ParallelOptions { MaxDegreeOfParallelism = dop }, (i) => { for (int j = 0; j < perfTestCount / dop; j++) { var testId = Interlocked.Increment(ref testIdSrc); if (j % 10 == 0) { Thread.Sleep(1); } synchedConsole.WriteLine($"* PART 4 * Cache Value: {MonitorWork(() => cache.GetOrAdd("test1", generateFunc, ttl, regen), synchedConsole, testId)}"); } }); swWait.Stop(); synchedConsole.WriteLine($"\r\n\r\n\t\t\t{perfTestCount:#,##0} cache GetOrAdd calls completed with {dop} degrees of parallelism." + $"\r\n\r\n\t\t\t Duration: {swWait.Elapsed.TotalMilliseconds * 1000:#,##0.0}us." + $"\r\n\t\t\t Requests/s: {perfTestCount / swWait.Elapsed.TotalSeconds:#,##0.0} ({perfTestCount / swWait.Elapsed.TotalHours:#,##0}/h)" + $"\r\n\r\n" , ConsoleColor.White, overrideShowOutput: true); ShowStats(synchedConsole); while (MonitoredWorkBag.TryTake(out Tuple <Task, TimeSpan> tr)) { tr.Item1.Dispose(); } GC.Collect(); } synchedConsole.CloseAndStopAllWriting(); if (launchTwoInstances) { synchedConsole.WriteLine("Press enter to exit.", overrideShowOutput: true); Console.ReadLine(); } }
public void Caching() { var redisConnection = TestRedisConfig.GetTestEnvironmentRedis(); using (var basicRedis1 = new BasicRedisWrapper(redisConnection, false)) using (var basicRedis2 = new BasicRedisWrapper(redisConnection, false)) { var cacheKey = $"{typeof(TestRedisConfig).FullName}:Cache:{Guid.NewGuid():N}"; var cacheKeyMissing = $"{typeof(TestRedisConfig).FullName}:CacheMissing:{Guid.NewGuid():N}"; var cacheVal1 = $"{typeof(TestRedisConfig).FullName}:CacheVal1:{Guid.NewGuid():N}"; var cacheVal2 = $"{typeof(TestRedisConfig).FullName}:CacheVal2:{Guid.NewGuid():N}"; // basic write/read basicRedis1.Cache.StringSet(cacheKey, cacheVal1, TimeSpan.FromSeconds(3)); var node2Val1 = basicRedis2.Cache.StringGetWithExpiry(cacheKey, out TimeSpan node2Val1Expires1); var node2Subval1 = basicRedis2.Cache.GetStringStart(cacheKey, 10); var node1Subval1 = basicRedis1.Cache.GetStringStart(cacheKey, 10); var node1Val1 = basicRedis1.Cache.StringGetWithExpiry(cacheKey, out TimeSpan node1Val1Expires1); Assert.Equal(node2Val1, node1Val1); Assert.Equal(node2Subval1, node1Subval1); Assert.NotEqual(node1Val1, node1Subval1); Assert.StartsWith(node1Subval1, node1Val1); basicRedis2.Cache.StringSet(cacheKey, cacheVal2, TimeSpan.FromSeconds(3)); var node2Val2 = basicRedis2.Cache.StringGetWithExpiry(cacheKey, out TimeSpan node2Val2Expires1); var node2Subval2 = basicRedis2.Cache.GetStringStart(cacheKey, 10); var node1Val2 = basicRedis1.Cache.StringGetWithExpiry(cacheKey, out TimeSpan node1Val2Expires1); var node1Subval2 = basicRedis1.Cache.GetStringStart(cacheKey, 10); Assert.NotEqual(node1Val1, node2Val2); Assert.Equal(node2Val2, node1Val2); Assert.Equal(node2Subval2, node1Subval2); Assert.NotEqual(node1Val2, node1Subval2); Assert.StartsWith(node1Subval2, node1Val2); Assert.Equal(node1Subval1, node1Subval2); Assert.Equal(node1Subval1, node2Subval1); Assert.Equal(node1Subval1, node1Subval2); Assert.Equal(node1Subval1, node2Subval2); Assert.True(node2Val1Expires1.TotalSeconds > 1); Assert.True(node1Val1Expires1.TotalSeconds > 1); Assert.True(node2Val2Expires1.TotalSeconds > 1); Assert.True(node1Val2Expires1.TotalSeconds > 1); var missingVal = basicRedis2.Cache.StringGetWithExpiry(cacheKeyMissing, out TimeSpan missingExpiryVal); var missingSubVal = basicRedis2.Cache.StringGetWithExpiry(cacheKeyMissing, out TimeSpan missingExpirySubVal); Assert.Null(missingVal); Assert.Null(missingSubVal); Thread.Sleep(3500); var expiredValNull = basicRedis2.Cache.StringGetWithExpiry(cacheKey, out TimeSpan expiredValExpiry); var expiredSubValNull = basicRedis2.Cache.GetStringStart(cacheKey, 10); Assert.Null(expiredValNull); Assert.Null(expiredSubValNull); } }
public void Messaging() { var redisConnection = TestRedisConfig.GetTestEnvironmentRedis(); var testId = Guid.NewGuid(); using (var basicRedis1 = new BasicRedisWrapper(redisConnection, false)) using (var basicRedis2 = new BasicRedisWrapper(redisConnection, false)) { var topic1 = $"{typeof(TestRedisConfig).FullName}:Bus:{Guid.NewGuid():N}"; var topic2 = $"{typeof(TestRedisConfig).FullName}:Bus:{Guid.NewGuid():N}"; string t1M1B1S1 = $"testmsg-{Guid.NewGuid():N}", t1M2B1S2 = $"testmsg-{Guid.NewGuid():N}", t1M3B1S2 = $"testmsg-{Guid.NewGuid():N}", t1M4B2S1 = $"testmsg-{Guid.NewGuid():N}", t1M5B2S1 = $"testmsg-{Guid.NewGuid():N}", t1M6B2S1 = $"testmsg-{Guid.NewGuid():N}", t2M7B1S1 = $"testmsg-{Guid.NewGuid():N}", t2M8B2S1 = $"testmsg-{Guid.NewGuid():N}", t1M9B1S1 = $"testmsg-{Guid.NewGuid():N}", x = $"testmsg-x-{Guid.NewGuid():N}"; var waitForMessage = new Action <List <string>, string>((bag, msg) => { var end = DateTime.Now.AddSeconds(1); while (!bag.Contains(msg) && DateTime.Now < end) { Thread.Sleep(100); } }); var msgsFromB1Sub1T1 = new List <string>(); var msgsFromB1Sub2T1 = new List <string>(); var msgsFromB2Sub1T1 = new List <string>(); var msgsFromB2Sub2T1 = new List <string>(); var msgsFromB1Sub1T2 = new List <string>(); var msgsFromB2Sub1T2 = new List <string>(); // bus 1 subscription 1 basicRedis1.Bus.Subscribe(topic1, s => { lock (msgsFromB1Sub1T1) msgsFromB1Sub1T1.Add(s); }); basicRedis1.Bus.Publish(topic1, t1M1B1S1); waitForMessage(msgsFromB1Sub1T1, t1M1B1S1); // bus 1 subscription 2 basicRedis1.Bus.Subscribe(topic1, s => { lock (msgsFromB1Sub2T1) msgsFromB1Sub2T1.Add(s); }); basicRedis1.Bus.Publish(topic1, t1M2B1S2); basicRedis1.Bus.Publish(topic1, t1M3B1S2); waitForMessage(msgsFromB1Sub2T1, t1M3B1S2); // bus 2 subscription 1 basicRedis2.Bus.Subscribe(topic1, s => { lock (msgsFromB2Sub1T1) msgsFromB2Sub1T1.Add(s); }); basicRedis2.Bus.Publish(topic1, t1M4B2S1); waitForMessage(msgsFromB2Sub1T1, t1M4B2S1); // bus 2 subscription 2 basicRedis2.Bus.Subscribe(topic1, s => { lock (msgsFromB2Sub2T1) msgsFromB2Sub2T1.Add(s); }); basicRedis2.Bus.Publish(topic1, t1M5B2S1); basicRedis2.Bus.Publish(topic1, t1M6B2S1); waitForMessage(msgsFromB2Sub2T1, t1M6B2S1); // topic2 via bus1 and bus2 basicRedis1.Bus.Subscribe(topic2, s => { lock (msgsFromB1Sub1T2) msgsFromB1Sub1T2.Add(s); }); basicRedis2.Bus.Subscribe(topic2, s => { lock (msgsFromB2Sub1T2) msgsFromB2Sub1T2.Add(s); }); basicRedis1.Bus.Publish(topic2, t2M7B1S1); basicRedis2.Bus.Publish(topic2, t2M8B2S1); waitForMessage(msgsFromB1Sub1T2, t2M8B2S1); Assert.Equal(new List <string>(new[] { t1M1B1S1, t1M2B1S2, t1M3B1S2, t1M4B2S1, t1M5B2S1, t1M6B2S1 }), msgsFromB1Sub1T1); Assert.Equal(new List <string>(new[] { t1M2B1S2, t1M3B1S2, t1M4B2S1, t1M5B2S1, t1M6B2S1 }), msgsFromB1Sub2T1); Assert.Equal(new List <string>(new[] { t1M4B2S1, t1M5B2S1, t1M6B2S1 }), msgsFromB2Sub1T1); Assert.Equal(new List <string>(new[] { t1M5B2S1, t1M6B2S1 }), msgsFromB2Sub2T1); Assert.Equal(new List <string>(new[] { t2M7B1S1, t2M8B2S1, }), msgsFromB1Sub1T2); Assert.Equal(new List <string>(new[] { t2M7B1S1, t2M8B2S1, }), msgsFromB2Sub1T2); } }