public void StressTest() { var helper = new PrnHelper(new QuadraticResidueHelper()); var generatedPrns = new HashSet <string>(); var context = new WeeeContext(userContext, eventDispatcher); uint seed = (uint)GetCurrentSeed(context); var components = new PrnAsComponents(seed); // be careful how high you go with this limit or generatedPrns will fill up // and your computer will get stuck in page-fault limbo const int Limit = int.MaxValue / 100; for (int ii = 0; ii < Limit; ii++) { if (ii % (Limit / 10) == 0) { Debug.WriteLine("Done another ten per cent..."); } var resultingPrn = helper.CreateUniqueRandomVersionOfPrn(components); Assert.False(generatedPrns.Contains(resultingPrn), string.Format( "{0} was generated twice but is supposed to be unique for a very large range of seed values", resultingPrn)); generatedPrns.Add(resultingPrn); seed = components.ToSeedValue() + 1; components = new PrnAsComponents(seed); } }
public void PrnFromSeed_PassedZero_RecomputedSeedValueEqualsZero() { const uint Value = 0; PrnAsComponents prnAsComponents = new PrnAsComponents(Value); uint seedFromPrn = prnAsComponents.ToSeedValue(); Assert.Equal(Value, seedFromPrn); }
public void PrnFromSeed_PassedValueOutOfRange_RecomputedSeedValueIsInRange() { const uint Value = 9967; const uint NextValueInRange = 65536; PrnAsComponents prnAsComponents = new PrnAsComponents(Value); uint seedFromPrn = prnAsComponents.ToSeedValue(); Assert.Equal(NextValueInRange, seedFromPrn); }
/// <summary> /// Generates unique, pseudorandom PRNs with minimal database interaction. /// Works by: /// a) uniquely mapping each unsigned integer to another pseudorandom unsigned integer /// b) uniquely mapping each unsigned integer to a specific PRN /// Combining those two mappings, and using a sequential seed, we can obtain pseudorandom PRNs /// with assurance that we will not repeat ourselves for a very, very long time. /// </summary> /// <param name="context">The database context</param> /// <param name="numberOfPrnsNeeded">A non-negative integer</param> /// <returns></returns> public async Task<Queue<string>> ComputePrns(int numberOfPrnsNeeded) { bool succeeded = false; bool retry = false; IEnumerable<DbEntityEntry> staleValues = null; List<PrnAsComponents> generatedPrns = new List<PrnAsComponents>(); ExceptionDispatchInfo exceptionDispatchInfo = null; var prnHelper = new PrnHelper(new QuadraticResidueHelper()); try { succeeded = false; retry = false; // to avoid concurrency issues, we want to read the latest seed, 'reserve' some PRNs (figuring // out the resulting final seed as we go), and write the final seed back as quickly as possible uint originalLatestSeed = (uint)context.SystemData.Select(sd => sd.LatestPrnSeed).First(); uint currentSeed = originalLatestSeed; for (int ii = 0; ii < numberOfPrnsNeeded; ii++) { var prnFromSeed = new PrnAsComponents(currentSeed + 1); generatedPrns.Add(prnFromSeed); currentSeed = prnFromSeed.ToSeedValue(); } // we write back the next acceptable seed to the database, for next time // since there are some mathematical constraints on the acceptable values context.SystemData.First().LatestPrnSeed = currentSeed; await context.SaveChangesAsync(); succeeded = true; } catch (DbUpdateConcurrencyException ex) { staleValues = ex.Entries; retry = true; } catch (Exception ex) { // In .NET 4.5 it is not allowed to use "await" within catch blocks; this forces us to put // code after the catch block. As a result of that, we don't want to throw un-handled exceptions // here, so we capture the dispatch info and throw it at the end of this method. exceptionDispatchInfo = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex); } if (succeeded) { // now we're done with the fairly time sensitive database read/write, // we can 'randomise' the results at our leisure return new Queue<string>(generatedPrns.Select(p => prnHelper.CreateUniqueRandomVersionOfPrn(p))); } else if (retry) { // If we need to retry, we are probably in a race condition with another thread. // To avoid retrying indefinitely, we'll wait a few milliseconds to get out of sync // with the other thread. await Task.Delay(TimeSpan.FromMilliseconds(new Random().Next(100))); // If the database value for [LatestPrnSeed] was updated between the time we fetched the value // tried to update it with our new value, we will get a DbConcurrencyException. // To handle this we will just call this method again until it succeeds. // However, as dependency injection forces us to reuse the same WeeeContext, the SystemData // entity will already be attached to the context giving us the same stale value from when it // was first fetched. // The DbUpdateConcurrencyException gives us the ability to reload this entity from the database. foreach (var entry in staleValues) { if (entry.Entity is SystemData) { await entry.ReloadAsync(); } } // Now that we have the latest value loaded, we'll try calling this method again. return await ComputePrns(numberOfPrnsNeeded); } else { // Something else bad happened and it's not possible to fix that here. exceptionDispatchInfo.Throw(); throw new Exception("This will never be thrown."); } }
public void StressTest() { var helper = new PrnHelper(new QuadraticResidueHelper()); var generatedPrns = new HashSet<string>(); var context = new WeeeContext(userContext, eventDispatcher); uint seed = (uint)GetCurrentSeed(context); var components = new PrnAsComponents(seed); // be careful how high you go with this limit or generatedPrns will fill up // and your computer will get stuck in page-fault limbo const int Limit = int.MaxValue / 100; for (int ii = 0; ii < Limit; ii++) { if (ii % (Limit / 10) == 0) { Debug.WriteLine("Done another ten per cent..."); } var resultingPrn = helper.CreateUniqueRandomVersionOfPrn(components); Assert.False(generatedPrns.Contains(resultingPrn), string.Format( "{0} was generated twice but is supposed to be unique for a very large range of seed values", resultingPrn)); generatedPrns.Add(resultingPrn); seed = components.ToSeedValue() + 1; components = new PrnAsComponents(seed); } }
/// <summary> /// Generates unique, pseudorandom PRNs with minimal database interaction. /// Works by: /// a) uniquely mapping each unsigned integer to another pseudorandom unsigned integer /// b) uniquely mapping each unsigned integer to a specific PRN /// Combining those two mappings, and using a sequential seed, we can obtain pseudorandom PRNs /// with assurance that we will not repeat ourselves for a very, very long time. /// </summary> /// <param name="context">The database context</param> /// <param name="numberOfPrnsNeeded">A non-negative integer</param> /// <returns></returns> public async Task <Queue <string> > ComputePrns(int numberOfPrnsNeeded) { bool succeeded = false; bool retry = false; IEnumerable <DbEntityEntry> staleValues = null; List <PrnAsComponents> generatedPrns = new List <PrnAsComponents>(); ExceptionDispatchInfo exceptionDispatchInfo = null; var prnHelper = new PrnHelper(new QuadraticResidueHelper()); try { succeeded = false; retry = false; // to avoid concurrency issues, we want to read the latest seed, 'reserve' some PRNs (figuring // out the resulting final seed as we go), and write the final seed back as quickly as possible uint originalLatestSeed = (uint)context.SystemData.Select(sd => sd.LatestPrnSeed).First(); uint currentSeed = originalLatestSeed; for (int ii = 0; ii < numberOfPrnsNeeded; ii++) { var prnFromSeed = new PrnAsComponents(currentSeed + 1); generatedPrns.Add(prnFromSeed); currentSeed = prnFromSeed.ToSeedValue(); } // we write back the next acceptable seed to the database, for next time // since there are some mathematical constraints on the acceptable values context.SystemData.First().LatestPrnSeed = currentSeed; await context.SaveChangesAsync(); succeeded = true; } catch (DbUpdateConcurrencyException ex) { staleValues = ex.Entries; retry = true; } catch (Exception ex) { // In .NET 4.5 it is not allowed to use "await" within catch blocks; this forces us to put // code after the catch block. As a result of that, we don't want to throw un-handled exceptions // here, so we capture the dispatch info and throw it at the end of this method. exceptionDispatchInfo = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex); } if (succeeded) { // now we're done with the fairly time sensitive database read/write, // we can 'randomise' the results at our leisure return(new Queue <string>(generatedPrns.Select(p => prnHelper.CreateUniqueRandomVersionOfPrn(p)))); } else if (retry) { // If we need to retry, we are probably in a race condition with another thread. // To avoid retrying indefinitely, we'll wait a few milliseconds to get out of sync // with the other thread. await Task.Delay(TimeSpan.FromMilliseconds(new Random().Next(100))); // If the database value for [LatestPrnSeed] was updated between the time we fetched the value // tried to update it with our new value, we will get a DbConcurrencyException. // To handle this we will just call this method again until it succeeds. // However, as dependency injection forces us to reuse the same WeeeContext, the SystemData // entity will already be attached to the context giving us the same stale value from when it // was first fetched. // The DbUpdateConcurrencyException gives us the ability to reload this entity from the database. foreach (var entry in staleValues) { if (entry.Entity is SystemData) { await entry.ReloadAsync(); } } // Now that we have the latest value loaded, we'll try calling this method again. return(await ComputePrns(numberOfPrnsNeeded)); } else { // Something else bad happened and it's not possible to fix that here. exceptionDispatchInfo.Throw(); throw new Exception("This will never be thrown."); } }