/// <summary> /// Applies a set of updates to the indexes defined on the grain /// </summary> /// <param name="interfaceToUpdatesMap">the dictionary of indexes to their corresponding updates</param> /// <param name="updateIndexesEagerly">whether indexes should be updated eagerly or lazily; must always be true for transactional indexes</param> /// <param name="onlyUniqueIndexesWereUpdated">a flag to determine whether only unique indexes were updated; unused for transactional indexes</param> /// <param name="numberOfUniqueIndexesUpdated">determine the number of updated unique indexes; unused for transactional indexes</param> /// <param name="writeStateIfConstraintsAreNotViolated">whether the state should be written to storage if no constraint is violated; /// must always be true for transactional indexes</param> private protected override async Task ApplyIndexUpdates(InterfaceToUpdatesMap interfaceToUpdatesMap, bool updateIndexesEagerly, bool onlyUniqueIndexesWereUpdated, int numberOfUniqueIndexesUpdated, bool writeStateIfConstraintsAreNotViolated) { Debug.Assert(writeStateIfConstraintsAreNotViolated, "Transactional index writes must only be called when updating the grain state (not on activation change)."); // For Transactional, the grain-state write has already been done by the time we get here. if (!interfaceToUpdatesMap.IsEmpty) { Debug.Assert(updateIndexesEagerly, "Transactional indexes cannot be configured to be lazy; this misconfiguration should have been caught in ValidateSingleIndex."); IEnumerable <Task> getIndexUpdateTasks(Type grainInterfaceType, IReadOnlyDictionary <string, IMemberUpdate> updates) { var indexInterfaces = this._grainIndexes[grainInterfaceType]; foreach (var(indexName, mu) in updates.Where(kvp => kvp.Value.OperationType != IndexOperationType.None).OrderBy(kvp => kvp.Key)) { var indexInfo = indexInterfaces.NamedIndexes[indexName]; var updateToIndex = new MemberUpdateOverriddenMode(mu, IndexUpdateMode.Transactional) as IMemberUpdate; yield return(indexInfo.IndexInterface.ApplyIndexUpdate(this.SiloIndexManager, this.iIndexableGrain, updateToIndex.AsImmutable(), indexInfo.MetaData, base.BaseSiloAddress)); } } // Execute each index update individually, in an invariant sequence, to avoid deadlocks when locking multiple index buckets. // The invariant sequence must apply across all grains, since a single indexed interface may be on multiple grains. // Therefore, order by interface name and within that by index name. // TODO performance: safely execute multiple index updates in parallel. foreach (var updateTask in interfaceToUpdatesMap.OrderBy(kvp => kvp.Key.FullName).SelectMany(kvp => getIndexUpdateTasks(kvp.Key, kvp.Value))) { await updateTask; } } }
/// <summary> /// Applies a set of updates to the indexes defined on the grain /// </summary> /// <param name="interfaceToUpdatesMap">the dictionary of indexes to their corresponding updates</param> /// <param name="updateIndexesEagerly">whether indexes should be updated eagerly or lazily</param> /// <param name="onlyUniqueIndexesWereUpdated">a flag to determine whether only unique indexes were updated</param> /// <param name="numberOfUniqueIndexesUpdated">determine the number of updated unique indexes</param> /// <param name="writeStateIfConstraintsAreNotViolated">whether the state should be written to storage if no constraint is violated</param> private protected override async Task ApplyIndexUpdates(InterfaceToUpdatesMap interfaceToUpdatesMap, bool updateIndexesEagerly, bool onlyUniqueIndexesWereUpdated, int numberOfUniqueIndexesUpdated, bool writeStateIfConstraintsAreNotViolated) { // If there is no update to the indexes, we should only write back the state of the grain, if requested. if (interfaceToUpdatesMap.IsEmpty) { if (writeStateIfConstraintsAreNotViolated) { await base.writeGrainStateFunc(); } return; } // HashIndexBucketState will not actually perform an index removal (Delete) if the index is not marked tentative. // Therefore we must do a two-step approach here; mark a tentative Delete, then do the non-tentative Delete. var updateEagerUniqueIndexesTentatively = numberOfUniqueIndexesUpdated > 1 || interfaceToUpdatesMap.HasAnyDeletes; // Apply any unique index updates eagerly. if (numberOfUniqueIndexesUpdated > 0) { try { // If there is more than one unique index to update, then updates to the unique indexes should be tentative // so they are not visible to readers before making sure that all uniqueness constraints are satisfied. await this.ApplyIndexUpdatesEagerly(interfaceToUpdatesMap, UpdateIndexType.Unique, updateEagerUniqueIndexesTentatively); } catch (UniquenessConstraintViolatedException ex) { // If any uniqueness constraint is violated and we have more than one unique index defined, then all tentative // updates must be undone, then the exception is thrown back to the user code. if (updateEagerUniqueIndexesTentatively) { await this.UndoTentativeChangesToUniqueIndexesEagerly(interfaceToUpdatesMap); } throw ex; } } if (updateIndexesEagerly) { var updateIndexTypes = UpdateIndexType.None; if (updateEagerUniqueIndexesTentatively) { updateIndexTypes |= UpdateIndexType.Unique; } if (!onlyUniqueIndexesWereUpdated) { updateIndexTypes |= UpdateIndexType.NonUnique; } if (updateIndexTypes != UpdateIndexType.None || writeStateIfConstraintsAreNotViolated) { await Task.WhenAll(new[] { updateIndexTypes != UpdateIndexType.None ? base.ApplyIndexUpdatesEagerly(interfaceToUpdatesMap, updateIndexTypes, isTentative: false) : null, writeStateIfConstraintsAreNotViolated ? base.writeGrainStateFunc() : null }.Coalesce()); } } else // !updateIndexesEagerly { this.ApplyIndexUpdatesLazilyWithoutWait(interfaceToUpdatesMap); if (writeStateIfConstraintsAreNotViolated) { await base.writeGrainStateFunc(); } } // If everything was successful, the before images are updated this.UpdateBeforeImages(interfaceToUpdatesMap); }
private void ApplyIndexUpdatesLazilyWithoutWait(InterfaceToUpdatesMap updatesByInterface) => base.ApplyIndexUpdatesLazily(updatesByInterface).Ignore();
private Task UndoTentativeChangesToUniqueIndexesEagerly(InterfaceToUpdatesMap interfaceToUpdatesMap) => Task.WhenAll(interfaceToUpdatesMap.Select(kvp => base.ApplyIndexUpdatesEagerly(kvp.Key, MemberUpdateReverseTentative.Reverse(kvp.Value), UpdateIndexType.Unique, isTentative: false)));
private protected void UpdateBeforeImages(InterfaceToUpdatesMap interfaceToUpdatesMap) => this._grainIndexes.UpdateBeforeImages(interfaceToUpdatesMap);
private protected Task ApplyIndexUpdatesLazily(InterfaceToUpdatesMap interfaceToUpdatesMap) => Task.WhenAll(interfaceToUpdatesMap.Select(kvp => this.GetWorkflowQueue(kvp.Key).AddToQueue(new IndexWorkflowRecord(interfaceToUpdatesMap.WorkflowIds[kvp.Key], base.iIndexableGrain, kvp.Value).AsImmutable())));
private protected Task ApplyIndexUpdatesEagerly(InterfaceToUpdatesMap interfaceToUpdatesMap, UpdateIndexType updateIndexTypes, bool isTentative = false) => Task.WhenAll(interfaceToUpdatesMap.Select(kvp => this.ApplyIndexUpdatesEagerly(kvp.Key, kvp.Value, updateIndexTypes, isTentative)));
/// <summary> /// Applies a set of updates to the indexes defined on the grain /// </summary> /// <param name="interfaceToUpdatesMap">the dictionary of indexes to their corresponding updates</param> /// <param name="updateIndexesEagerly">whether indexes should be updated eagerly or lazily</param> /// <param name="onlyUniqueIndexesWereUpdated">a flag to determine whether only unique indexes were updated</param> /// <param name="numberOfUniqueIndexesUpdated">determine the number of updated unique indexes</param> /// <param name="writeStateIfConstraintsAreNotViolated">whether writing back /// the state to the storage should be done if no constraint is violated</param> private protected abstract Task ApplyIndexUpdates(InterfaceToUpdatesMap interfaceToUpdatesMap, bool updateIndexesEagerly, bool onlyUniqueIndexesWereUpdated, int numberOfUniqueIndexesUpdated, bool writeStateIfConstraintsAreNotViolated);