/// <summary> /// Processes all storage requests. /// </summary> private void ProcessStorageRequests() { DBResults results; bool storageUpdated = false; // Delete all storage requests that have finished and who's arbitrator has disconnected. m_databaseConnection.Query(@"DELETE FROM {0} WHERE (success=1 OR failed=1) AND (Select COUNT(*) FROM {1} AS b WHERE id=recieved_arbitrator_id) = 0 ", Settings.DB_TABLE_PENDING_STORE_REQUESTS, Settings.DB_TABLE_ACTIVE_ARBITRATORS); // Lock tables. m_databaseConnection.Query("LOCK TABLES {0} WRITE, {1} WRITE", Settings.DB_TABLE_PENDING_STORE_REQUESTS, Settings.DB_TABLE_ACCOUNTS); // Lets have a look at all requests and see if we find any we can commit. results = m_databaseConnection.Query(@"SELECT *, UNIX_TIMESTAMP()-recieved_time FROM {0} WHERE success=0 AND failed=0", Settings.DB_TABLE_PENDING_STORE_REQUESTS); // Group together rows by zone id / client id. List<int> updated_ids = new List<int>(); for (int i = 0; i < results.RowsAffected; i++) { DBRow row = results[i]; // Ignore this request if we have already processed it. if (updated_ids.Contains((int)row["id"])) { continue; } // Make a list of requests to update same client/zone. List<DBRow> sameRows = new List<DBRow>(); sameRows.Add(row); updated_ids.Add((int)row["id"]); // Look for other rows affecting the same zone and client. int oldest_row_elapsed = 0; for (int k = 0; k < results.RowsAffected; k++) { DBRow subrow = results[k]; // Ignore this request if we have already processed it. if (updated_ids.Contains((int)subrow["id"])) { continue; } if ((int)row["client_id"] == (int)subrow["client_id"] && (int)row["zone_id"] == (int)subrow["zone_id"]) { // Store oldest elapsed time for rows. int subrow_elapsed = (int)(long)row["UNIX_TIMESTAMP()-recieved_time"]; if (subrow_elapsed > oldest_row_elapsed) { oldest_row_elapsed = subrow_elapsed; } sameRows.Add(subrow); updated_ids.Add((int)subrow["id"]); } } // Do we have enough rows to commit. if (sameRows.Count >= Settings.ZoneSuperPeerCount || oldest_row_elapsed > Settings.STORE_REQUEST_TIMEOUT_INTERVAL / 1000) { Logger.Info("Found {0} matching storage requests for client #{1} in zone #{2}.", LoggerVerboseLevel.High, sameRows.Count, row["client_id"], row["zone_id"]); // Find the request with the most number of similar requests. This // is the row we will consider the correct row. int mostSimilarIndex = -1; int mostSimilarCount = 0; int mostSimilarSuperPeerID= 0; UserAccountPersistentState mostSimilarState = null; for (int j = 0; j < sameRows.Count; j++) { UserAccountPersistentState state1 = new UserAccountPersistentState(); state1.Deserialize((byte[])sameRows[j]["state"]); // Count number of similar row. int similarCount = 1; for (int l = 0; l < sameRows.Count; l++) { if (j == l) continue; UserAccountPersistentState state2 = new UserAccountPersistentState(); state2.Deserialize((byte[])sameRows[l]["state"]); if (state1.IsSimilarTo(state2)) { similarCount++; } } // Is this the most similar state so far? if (similarCount > mostSimilarCount || (similarCount == mostSimilarCount && (int)sameRows[j]["superpeer_id"] < mostSimilarSuperPeerID)) { mostSimilarState = state1; mostSimilarIndex = j; mostSimilarCount = similarCount; mostSimilarSuperPeerID = (int)sameRows[j]["superpeer_id"]; } } // If we only have a maximum of 1 match, everyone fails, we can't have // one user being authorative. if (mostSimilarCount <= 1) { Logger.Info("Of {0} matching storage requests for client #{1} in zone #{2}, only {3} were similar. Store request rejected.", LoggerVerboseLevel.Normal, sameRows.Count, row["client_id"], row["zone_id"], mostSimilarCount); m_databaseConnection.Query("UPDATE {0} SET failed=1 WHERE client_id={1} AND zone_id={2}", Settings.DB_TABLE_PENDING_STORE_REQUESTS, row["client_id"], row["zone_id"]); storageUpdated = true; } else { Logger.Info("Of {0} matching storage requests for client #{1} in zone #{2}, {3} were similar. Store request was accepted.", LoggerVerboseLevel.High, sameRows.Count, row["client_id"], row["zone_id"], mostSimilarCount); // Update state of storage requests. for (int j = 0; j < sameRows.Count; j++) { UserAccountPersistentState state2 = new UserAccountPersistentState(); state2.Deserialize((byte[])sameRows[j]["state"]); // If its similar to the correct state, success! if (state2.IsSimilarTo(mostSimilarState)) { m_databaseConnection.Query("UPDATE {0} SET success=1 WHERE id={1}", Settings.DB_TABLE_PENDING_STORE_REQUESTS, sameRows[j]["id"]); Logger.Info("Storage request by SuperPeer #{0} for Client #{1} in Zone #{2} was similar to correct states and was accepted.", LoggerVerboseLevel.High, sameRows[j]["superpeer_id"], sameRows[j]["client_id"], sameRows[j]["zone_id"]); } // Otherwise failure :(. else { m_databaseConnection.Query("UPDATE {0} SET failed=1 WHERE id={1}", Settings.DB_TABLE_PENDING_STORE_REQUESTS, sameRows[j]["id"]); Logger.Info("Storage request by SuperPeer #{0} for Client #{1} in Zone #{2} was not similar to correct states, and was ignored.", LoggerVerboseLevel.Normal, sameRows[j]["superpeer_id"], sameRows[j]["client_id"], sameRows[j]["zone_id"]); } } // Now actually store the data. m_databaseConnection.QueryParameterized("UPDATE {0} SET persistent_state=@parameter_1 WHERE id={1}", new object[] { mostSimilarState.Serialize() }, Settings.DB_TABLE_ACCOUNTS, row["account_id"]); storageUpdated = true; } } } // Dispose of timed out requests. results = m_databaseConnection.Query(@"UPDATE {0} SET failed=1 WHERE UNIX_TIMESTAMP() - recieved_time > {1}", Settings.DB_TABLE_PENDING_STORE_REQUESTS, Settings.STORE_REQUEST_TIMEOUT_INTERVAL / 1000); if (results.RowsAffected > 0) { storageUpdated = true; } // Unlock tables. m_databaseConnection.Query("UNLOCK TABLES"); }
/// <summary> /// Checks if this persistent state is similar enough to another state that it /// can be considered the same. /// </summary> /// <param name="state">Other state to check.</param> /// <returns>True if states are similar, otherwise false.</returns> public bool IsSimilarTo(UserAccountPersistentState state) { Type type = this.GetType(); foreach (FieldInfo info in type.GetFields()) { object val1 = info.GetValue(this); object val2 = info.GetValue(state); SimilarityThresholdAttribute attr = info.GetCustomAttribute(typeof(SimilarityThresholdAttribute)) as SimilarityThresholdAttribute; if (attr != null) { if (attr.CompareValues(val1, val2) == false) { return false; } } } return true; }