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);
        }
Example #2
0
        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);
        }