private void CtorTestImplementation( MultidimensionalCube <string, int> cube, ref int[] subdimensionsCountLimits, ref int factoryCallsCount, ref object[] lastFactoryCall, string suffix, int point, int totalPointsCountLimit) { Assert.IsNotNull(cube); Assert.AreEqual(subdimensionsCountLimits.Length, cube.DimensionsCount); for (int d = 0; d < subdimensionsCountLimits.Length; d++) { int limit = subdimensionsCountLimits[d]; Assert.AreEqual(limit, cube.GetSubdimensionsCountLimit(d)); } Assert.AreEqual(totalPointsCountLimit, cube.TotalPointsCountLimit); string[] newPointVector = new string[] { $"A{suffix}", $"B{suffix}", $"C{suffix}", $"D{suffix}" }; MultidimensionalPointResult <int> result = cube.TryGetOrCreatePoint(newPointVector); Assert.IsTrue(result.IsSuccess); Assert.AreEqual(point, result.Point); Util.AssertAreEqual(newPointVector, lastFactoryCall); }
private MultidimensionalPointResult <TPoint> TryAddSubvector(TDimensionValue[] coordinates, int currentDim) { // Note the comment near the top if TryAddPoint(..) about the applied minimal locking strategy. // Check if we reached the dimensions count limit. If we did, we give up. Otherwise we start tracking whether we need to undo the increment later: if (!this.TryIncSubdimensionsCount()) { return(new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Failure_SubdimensionsCountLimitReached, currentDim)); } bool mustRestoreSubdimensionsCount = true; try { TDimensionValue subElementKey = coordinates[currentDim]; // Do a soft-check to see if we reached the total points limit. If we do, there is no need to bother: // (We will do a hard check and pre-booking later when we actually about to create the point.) if (_ownerCube.TotalPointsCount >= _ownerCube.TotalPointsCountLimit) { return(new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Failure_TotalPointsCountLimitReached, failureCoordinateIndex: -1)); } // We are not at the last level. Create the subdimension. Note, we are not under lock, so someone might be creating the same dimention concurrently: int nextDim = currentDim + 1; bool isLastDimensionLevel = (nextDim == coordinates.Length - 1); var newSubDim = new MultidimensionalCubeDimension <TDimensionValue, TPoint>( _ownerCube, _ownerCube.GetSubdimensionsCountLimit(nextDim), isLastDimensionLevel); MultidimensionalPointResult <TPoint> newSubDimResult = newSubDim.TryGetOrAddVectorInternal(coordinates, nextDim, createIfNotExists: true); // Becasue we have not yet inserted newSubDim into _elements, any operations on newSubDim are not under concurrency. // There are no point-vectors yet pointing to the sub-space of newSubDim, so no DimensionValuesCountLimit can be reached. // So, hasNewPoint can be false only if TotalPointsCountLimit was reached. We just bail out: if (!newSubDimResult.IsSuccess) { return(newSubDimResult); } // The new point has been created and we need to add its sub-space to the list. // However, there is a race on someone calling GetOrAddVector(..) with the same coordinates. So newSubDim may or may not already be in the list. bool couldInsert = _elements.TryAdd(subElementKey, newSubDim); if (couldInsert) { // Success. We created and inserted a new sub-space: mustRestoreSubdimensionsCount = false; return(newSubDimResult); } else { // The point was already in the list. It's that other point (created by the race winner) that we want. // We need to discard the sub-space and the point we just created: Decrement total points count and Decrement the dimension value count. // After that we just call TryGetOrAddVectorInternal(..) again on the same recursion level. _ownerCube.DecTotalPointsCount(); } } finally { if (mustRestoreSubdimensionsCount) { this.DecSubdimensionsCount(); } } MultidimensionalPointResult <TPoint> retryResult = this.TryGetOrAddVectorInternal(coordinates, currentDim, createIfNotExists: true); return(retryResult); }