/// <summary> /// </summary> /// <param name="cancelToken"></param> /// <param name="coordinates"></param> /// <returns></returns> public async Task <MultidimensionalPointResult <TPoint> > TryGetOrCreatePointAsync(CancellationToken cancelToken, params string[] coordinates) { string pointMoniker = GetPointMoniker(coordinates); TPoint point; bool hasPoint = _points.TryGetValue(pointMoniker, out point); if (hasPoint) { var result = new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Success_ExistingPointRetrieved, point); return(result); } if (_totalPointsCount >= _totalPointsCountLimit) { var result = new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Failure_TotalPointsCountLimitReached, -1); return(result); } await _pointCreationLock.WaitAsync(cancelToken).ConfigureAwait(continueOnCapturedContext: false); try { MultidimensionalPointResult <TPoint> result = TryCreatePoint(coordinates, pointMoniker); return(result); } finally { _pointCreationLock.Release(); } }
/// <summary> /// </summary> /// <param name="coordinates"></param> /// <returns></returns> public MultidimensionalPointResult <TPoint> TryGetOrCreatePoint(params string[] coordinates) { string pointMoniker = GetPointMoniker(coordinates); TPoint point; bool hasPoint = _points.TryGetValue(pointMoniker, out point); if (hasPoint) { var result = new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Success_ExistingPointRetrieved, point); return(result); } if (_totalPointsCount >= _totalPointsCountLimit) { var result = new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Failure_TotalPointsCountLimitReached, -1); return(result); } _pointCreationLock.Wait(); try { MultidimensionalPointResult <TPoint> result = TryCreatePoint(coordinates, pointMoniker); return(result); } finally { _pointCreationLock.Release(); } }
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); }
public MultidimensionalPointResult <TPoint> TryGetVector(TDimensionValue[] coordinates) { Util.ValidateNotNull(coordinates, nameof(coordinates)); if (coordinates.Length != _ownerCube.DimensionsCount) { throw new ArgumentException( $"The specified {nameof(coordinates)}-vector has {coordinates.Length} dimensions." + $" However {nameof(_ownerCube)} has {_ownerCube.DimensionsCount} dimensions.", nameof(coordinates)); } MultidimensionalPointResult <TPoint> result = this.TryGetOrAddVectorInternal(coordinates, currentDim: 0, createIfNotExists: false); return(result); }
/// <summary> /// </summary> /// <param name="coordinates"></param> /// <returns></returns> public MultidimensionalPointResult <TPoint> TryGetPoint(params string[] coordinates) { string pointMoniker = GetPointMoniker(coordinates); TPoint point; bool hasPoint = _points.TryGetValue(pointMoniker, out point); if (hasPoint) { var result = new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Success_ExistingPointRetrieved, point); return(result); } else { var result = new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Failure_PointDoesNotExistCreationNotRequested, -1); return(result); } }
private MultidimensionalPointResult <TPoint> TryGetOrAddVectorInternal(TDimensionValue[] coordinates, int currentDim, bool createIfNotExists) { TDimensionValue subElementKey = coordinates[currentDim]; // Try and get the referenced element: object subElement; bool subElementExists = _elements.TryGetValue(subElementKey, out subElement); // If the referenced element exists, we can simply proceed: if (subElementExists) { if (_isLastDimensionLevel) { var result = new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Success_ExistingPointRetrieved, (TPoint)subElement); return(result); } else { MultidimensionalCubeDimension <TDimensionValue, TPoint> subDim = (MultidimensionalCubeDimension <TDimensionValue, TPoint>)subElement; MultidimensionalPointResult <TPoint> result = subDim.TryGetOrAddVectorInternal(coordinates, currentDim + 1, createIfNotExists); return(result); } } else // so - subElement does NOT exist: { // If we are not to create new elements, we are done: if (!createIfNotExists) { var result = new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Failure_PointDoesNotExistCreationNotRequested, currentDim); return(result); } else { MultidimensionalPointResult <TPoint> result = _isLastDimensionLevel ? this.TryAddPoint(coordinates, currentDim) : this.TryAddSubvector(coordinates, currentDim); return(result); } } }
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); }
private MultidimensionalPointResult <TPoint> TryCreatePoint(string[] coordinates, string pointMoniker) { // We already have tried getting the existng point and failed. // We also checked that _totalPointsCountLimit was not reached. // Lastly, we took a lock. // Now we can begin the slow path. // First, we need to try retrieving the point again, now under the lock: TPoint point; bool hasPoint = _points.TryGetValue(pointMoniker, out point); if (hasPoint) { var result = new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Success_ExistingPointRetrieved, point); return(result); } // Then, check total count again now that we are under lock: if (_totalPointsCount >= _totalPointsCountLimit) { var result = new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Failure_TotalPointsCountLimitReached, -1); return(result); } // Examine each dimension and see if it reached values count limit. If not, track the new value: int reachedValsLimitDim = -1; BitArray valueAddedToDims = new BitArray(length: coordinates.Length, defaultValue: false); for (int i = 0; i < coordinates.Length; i++) { HashSet <string> dimVals = _dimensionValues[i]; string coordinateVal = coordinates[i]; if ((dimVals.Count >= _dimensionValuesCountLimits[i]) && (false == dimVals.Contains(coordinateVal))) { reachedValsLimitDim = i; break; } bool added = dimVals.Add(coordinates[i]); valueAddedToDims.Set(i, added); } // We hit the _dimensionValuesCountLimits at some dimension. // Remove what we just added to dim value sets and give up. if (reachedValsLimitDim != -1) { for (int i = 0; i <= reachedValsLimitDim; i++) { if (valueAddedToDims.Get(i)) { _dimensionValues[i].Remove(coordinates[i]); } } var result = new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Failure_SubdimensionsCountLimitReached, reachedValsLimitDim); return(result); } // Create new point: try { point = _pointsFactory(coordinates); } catch (Exception ex) { // User code in _pointsFactory may throw. In that case we need to clean up from the added value containers: for (int i = 0; i <= reachedValsLimitDim; i++) { if (valueAddedToDims.Get(i)) { _dimensionValues[i].Remove(coordinates[i]); } } ExceptionDispatchInfo.Capture(ex).Throw(); throw; // This line will never be reached } { bool added = _points.TryAdd(pointMoniker, point); if (false == added) { throw new InvalidOperationException($"Internal Metrics SDK bug. Please report this! (pointMoniker: {pointMoniker})"); } } // Inc total points coint. _totalPointsCount++; { var result = new MultidimensionalPointResult <TPoint>(MultidimensionalPointResultCodes.Success_NewPointCreated, point); return(result); } }
/// <summary> /// /// </summary> /// <param name="coordinates"></param> /// <returns></returns> public MultidimensionalPointResult <TPoint> TryGetPoint(params TDimensionValue[] coordinates) { MultidimensionalPointResult <TPoint> result = _points.TryGetVector(coordinates); return(result); }