Example #1
0
        /// <summary>
        /// The entrypoint method to the program.
        /// </summary>
        public static void Main()
        {
            var exitEvent    = new ManualResetEvent(false);
            var cancelSource = new CancellationTokenSource();

            Console.CancelKeyPress += (sender, eventArgs) =>
            {
                // Cancel the exit, as we need to cleanup first.
                eventArgs.Cancel = true;
                cancelSource.Cancel();
                exitEvent.Set();
            };

            var gameData = GameData.Parse(@"GameData.json");

            // Set up configuration.
            var builder = new ConfigurationBuilder()
                          .SetBasePath(Environment.CurrentDirectory)
                          .AddJsonFile("appsettings.json")
                          .AddApplicationInsightsSettings(developerMode: true);
            var configuration = builder.Build();

            var databaseSettings = new DatabaseSettings();

            configuration.GetSection("Database").Bind(databaseSettings);
            var databaseSettingsOptions = Options.Create(databaseSettings);

            var queueClient = CloudStorageAccount.Parse(configuration["Storage:ConnectionString"]).CreateCloudQueueClient();

            // Set up telemetry
            var telemetryConfiguration = TelemetryConfiguration.CreateDefault();

            telemetryConfiguration.InstrumentationKey = configuration["ApplicationInsights:InstrumentationKey"];
            telemetryConfiguration.TelemetryProcessorChainBuilder.Use(next => new ConsoleLoggingProcessor(next)).Build();
            var telemetryClient = new TelemetryClient(telemetryConfiguration);

            if (ScheduleRecompute)
            {
                Task.Run(async() =>
                {
                    var uploadIds = new List <int>();

                    const string CommandText   = "SELECT Id FROM Uploads WHERE LastComputeTime < '" + LastComputeTime + "' ORDER BY LastComputeTime DESC";
                    var databaseCommandFactory = new DatabaseCommandFactory(databaseSettingsOptions);
                    using (var command = databaseCommandFactory.Create(CommandText))
                        using (var reader = await command.ExecuteReaderAsync())
                        {
                            while (reader.Read())
                            {
                                var uploadId = Convert.ToInt32(reader["Id"]);
                                uploadIds.Add(uploadId);
                            }
                        }

                    Console.WriteLine($"Found {uploadIds.Count} uploads to schedule");
                    var uploadScheduler = new AzureStorageUploadScheduler(queueClient);
                    for (var i = 0; i < uploadIds.Count; i++)
                    {
                        if (cancelSource.IsCancellationRequested)
                        {
                            break;
                        }

                        Console.WriteLine($"Scheduling message {i + 1} of {uploadIds.Count} - {100 * (i + 1) / uploadIds.Count}%");
                        var message = new UploadProcessingMessage {
                            UploadId = uploadIds[i], Requester = "Ad-hoc Recompute", Priority = UploadProcessingMessagePriority.Low
                        };
                        await uploadScheduler.ScheduleAsync(message);
                    }

                    Console.WriteLine("Stopped scheduling");
                });
            }

            var processors = new ConcurrentBag <UploadProcessor>();

            // Spin up one per logical core
            Console.WriteLine($"Number of processors: {Environment.ProcessorCount}");
            var tasks = new Task[Environment.ProcessorCount];

            for (var i = 0; i < tasks.Length; i++)
            {
                var processor = new UploadProcessor(databaseSettingsOptions, gameData, telemetryClient, queueClient);
                processors.Add(processor);
                tasks[i] = processor.ProcessAsync(cancelSource.Token);
            }

            // Let it run until Ctrl+C
            exitEvent.WaitOne();

            const int DrainTimeMs = 60000;

            Console.WriteLine($"Waiting for up to {DrainTimeMs}ms for processing to drain...");
            if (Task.WaitAll(tasks, DrainTimeMs))
            {
                Console.WriteLine("Processing drained successfully");
            }
            else
            {
                Console.WriteLine("Some processing was stuck");
                foreach (var processor in processors)
                {
                    var currentUploadId = processor.CurrentUploadId;
                    if (currentUploadId != null)
                    {
                        var properties = new Dictionary <string, string>
                        {
                            { "UploadId", currentUploadId.Value.ToString() },
                        };
                        telemetryClient.TrackEvent("UploadProcessor-Abandoned-Stuck", properties);
                    }
                }
            }

            telemetryClient.Flush();
        }
 protected override void Arrange() => _sut = new DatabaseCommandFactory(_databaseCommands);
Example #3
0
        private bool ProcessMessage(CloudQueueMessage queueMessage)
        {
            var properties = new Dictionary <string, string>
            {
                { "CloudQueueMessage-DequeueCount", queueMessage.DequeueCount.ToString() },
                { "CloudQueueMessage-InsertionTime", queueMessage.InsertionTime.ToString() },
                { "CloudQueueMessage-Id", queueMessage.Id },
            };

            this.telemetryClient.TrackEvent("UploadProcessor-Recieved", properties);

            using (var counterProvider = new CounterProvider(this.telemetryClient))
                using (var databaseCommandFactory = new DatabaseCommandFactory(
                           this.databaseSettingsOptions,
                           counterProvider))
                    using (counterProvider.Measure(Counter.ProcessUpload))
                    {
                        int uploadId = -1;
                        try
                        {
                            var message = JsonConvert.DeserializeObject <UploadProcessingMessage>(queueMessage.AsString);
                            if (message == null)
                            {
                                this.telemetryClient.TrackEvent("UploadProcessor-Abandoned-ParseMessage", properties);
                                return(false);
                            }

                            uploadId = message.UploadId;
                            properties.Add("UploadId", uploadId.ToString());

                            this.CurrentUploadId = uploadId;

                            GetUploadDetails(databaseCommandFactory, uploadId, out var uploadContent, out var userId, out var playStyle);
                            properties.Add("UserId", userId);
                            if (string.IsNullOrWhiteSpace(uploadContent))
                            {
                                this.telemetryClient.TrackEvent("UploadProcessor-Abandoned-FetchUpload", properties);
                                return(false);
                            }

                            // Handle legacy uplaods where the upload content was missing.
                            if (uploadContent.Equals("LEGACY", StringComparison.OrdinalIgnoreCase))
                            {
                                this.telemetryClient.TrackEvent("UploadProcessor-Complete-Legacy", properties);
                                using (var command = databaseCommandFactory.Create())
                                {
                                    command.CommandText = "UPDATE Uploads SET LastComputeTime = getutcdate() WHERE Id = " + uploadId;
                                    command.ExecuteNonQuery();
                                }

                                return(true);
                            }

                            var savedGame = SavedGame.Parse(uploadContent);
                            if (savedGame == null)
                            {
                                this.telemetryClient.TrackEvent("UploadProcessor-Abandoned-ParseUpload", properties);
                                return(false);
                            }

                            this.telemetryClient.TrackEvent("UploadProcessor-Processing", properties);
                            var ancientLevels = new AncientLevelsModel(
                                this.gameData,
                                savedGame,
                                this.telemetryClient);
                            var outsiderLevels = new OutsiderLevelsModel(
                                this.gameData,
                                savedGame,
                                this.telemetryClient);
                            var miscellaneousStatsModel = new MiscellaneousStatsModel(savedGame);

                            /* Build a query that looks like this:
                             *  MERGE INTO AncientLevels WITH (HOLDLOCK)
                             *  USING
                             *      (VALUES (123, 1, 100), (123, 2, 100), ... )
                             *          AS Input(UploadId, AncientId, Level)
                             *      ON AncientLevels.UploadId = Input.UploadId
                             *      AND AncientLevels.AncientId = Input.AncientId
                             *  WHEN MATCHED THEN
                             *      UPDATE
                             *      SET
                             *          Level = Input.Level
                             *  WHEN NOT MATCHED THEN
                             *      INSERT (UploadId, AncientId, Level)
                             *      VALUES (Input.UploadId, Input.AncientId, Input.Level);
                             */
                            var ancientLevelsCommandText = new StringBuilder();
                            if (ancientLevels.AncientLevels.Count > 0)
                            {
                                ancientLevelsCommandText.Append(@"
                        MERGE INTO AncientLevels WITH (HOLDLOCK)
                        USING
                            ( VALUES ");
                                var isFirst = true;
                                foreach (var pair in ancientLevels.AncientLevels)
                                {
                                    if (!isFirst)
                                    {
                                        ancientLevelsCommandText.Append(",");
                                    }

                                    // No need to sanitize, these are all just numbers
                                    ancientLevelsCommandText.Append("(");
                                    ancientLevelsCommandText.Append(uploadId);
                                    ancientLevelsCommandText.Append(",");
                                    ancientLevelsCommandText.Append(pair.Key);
                                    ancientLevelsCommandText.Append(",");
                                    ancientLevelsCommandText.Append(pair.Value.AncientLevel);
                                    ancientLevelsCommandText.Append(")");

                                    isFirst = false;
                                }

                                ancientLevelsCommandText.Append(@"
                            )
                                AS Input(UploadId, AncientId, Level)
                            ON AncientLevels.UploadId = Input.UploadId
                            AND AncientLevels.AncientId = Input.AncientId
                        WHEN MATCHED THEN
                            UPDATE
                            SET
                                Level = Input.Level
                        WHEN NOT MATCHED THEN
                            INSERT (UploadId, AncientId, Level)
                            VALUES (Input.UploadId, Input.AncientId, Input.Level);");
                            }

                            /* Build a query that looks like this:
                             *  MERGE INTO OutsiderLevels WITH (HOLDLOCK)
                             *  USING
                             *      (VALUES (123, 1, 100), (123, 2, 100), ... )
                             *          AS Input(UploadId, OutsiderId, Level)
                             *      ON OutsiderLevels.UploadId = Input.UploadId
                             *      AND OutsiderLevels.OutsiderId = Input.OutsiderId
                             *  WHEN MATCHED THEN
                             *      UPDATE
                             *      SET
                             *          Level = Input.Level
                             *  WHEN NOT MATCHED THEN
                             *      INSERT (UploadId, OutsiderId, Level)
                             *      VALUES (Input.UploadId, Input.OutsiderId, Input.Level);
                             */
                            var outsiderLevelsCommandText = new StringBuilder();
                            if (outsiderLevels.OutsiderLevels.Count > 0)
                            {
                                outsiderLevelsCommandText.Append(@"
                        MERGE INTO OutsiderLevels WITH (HOLDLOCK)
                        USING
                            ( VALUES ");
                                var isFirst = true;
                                foreach (var pair in outsiderLevels.OutsiderLevels)
                                {
                                    if (!isFirst)
                                    {
                                        outsiderLevelsCommandText.Append(",");
                                    }

                                    // No need to sanitize, these are all just numbers
                                    outsiderLevelsCommandText.Append("(");
                                    outsiderLevelsCommandText.Append(uploadId);
                                    outsiderLevelsCommandText.Append(",");
                                    outsiderLevelsCommandText.Append(pair.Key);
                                    outsiderLevelsCommandText.Append(",");
                                    outsiderLevelsCommandText.Append(pair.Value.Level);
                                    outsiderLevelsCommandText.Append(")");

                                    isFirst = false;
                                }

                                outsiderLevelsCommandText.Append(@"
                            )
                                AS Input(UploadId, OutsiderId, Level)
                            ON OutsiderLevels.UploadId = Input.UploadId
                            AND OutsiderLevels.OutsiderId = Input.OutsiderId
                        WHEN MATCHED THEN
                            UPDATE
                            SET
                                Level = Input.Level
                        WHEN NOT MATCHED THEN
                            INSERT (UploadId, OutsiderId, Level)
                            VALUES (Input.UploadId, Input.OutsiderId, Input.Level);");
                            }

                            const string ComputedStatsCommandText       = @"
                        MERGE INTO ComputedStats WITH (HOLDLOCK)
                        USING
                            (VALUES (
                                    @UploadId,
                                    @TitanDamage,
                                    @SoulsSpent,
                                    @HeroSoulsSacrificed,
                                    @TotalAncientSouls,
                                    @TranscendentPower,
                                    @Rubies,
                                    @HighestZoneThisTranscension,
                                    @HighestZoneLifetime,
                                    @AscensionsThisTranscension,
                                    @AscensionsLifetime,
                                    @MaxTranscendentPrimalReward,
                                    @BossLevelToTranscendentPrimalCap) )
                                AS Input(
                                    UploadId,
                                    TitanDamage,
                                    SoulsSpent,
                                    HeroSoulsSacrificed,
                                    TotalAncientSouls,
                                    TranscendentPower,
                                    Rubies,
                                    HighestZoneThisTranscension,
                                    HighestZoneLifetime,
                                    AscensionsThisTranscension,
                                    AscensionsLifetime,
                                    MaxTranscendentPrimalReward,
                                    BossLevelToTranscendentPrimalCap)
                            ON ComputedStats.UploadId = Input.UploadId
                        WHEN MATCHED THEN
                            UPDATE
                            SET
                                TitanDamage = Input.TitanDamage,
                                SoulsSpent = Input.SoulsSpent,
                                HeroSoulsSacrificed = Input.HeroSoulsSacrificed,
                                TotalAncientSouls = Input.TotalAncientSouls,
                                TranscendentPower = Input.TranscendentPower,
                                Rubies = Input.Rubies,
                                HighestZoneThisTranscension = Input.HighestZoneThisTranscension,
                                HighestZoneLifetime = Input.HighestZoneLifetime,
                                AscensionsThisTranscension = Input.AscensionsThisTranscension,
                                AscensionsLifetime = Input.AscensionsLifetime,
                                MaxTranscendentPrimalReward = Input.MaxTranscendentPrimalReward,
                                BossLevelToTranscendentPrimalCap = Input.BossLevelToTranscendentPrimalCap
                        WHEN NOT MATCHED THEN
                            INSERT (
                                UploadId,
                                TitanDamage,
                                SoulsSpent,
                                HeroSoulsSacrificed,
                                TotalAncientSouls,
                                TranscendentPower,
                                Rubies,
                                HighestZoneThisTranscension,
                                HighestZoneLifetime,
                                AscensionsThisTranscension,
                                AscensionsLifetime,
                                MaxTranscendentPrimalReward,
                                BossLevelToTranscendentPrimalCap)
                            VALUES (
                                Input.UploadId,
                                Input.TitanDamage,
                                Input.SoulsSpent,
                                Input.HeroSoulsSacrificed,
                                Input.TotalAncientSouls,
                                Input.TranscendentPower,
                                Input.Rubies,
                                Input.HighestZoneThisTranscension,
                                Input.HighestZoneLifetime,
                                Input.AscensionsThisTranscension,
                                Input.AscensionsLifetime,
                                Input.MaxTranscendentPrimalReward,
                                Input.BossLevelToTranscendentPrimalCap);";
                            var          computedStatsCommandParameters = new Dictionary <string, object>
                            {
                                { "@UploadId", uploadId },
                                { "@TitanDamage", miscellaneousStatsModel.TitanDamage },
                                { "@SoulsSpent", miscellaneousStatsModel.HeroSoulsSpent },
                                { "@HeroSoulsSacrificed", miscellaneousStatsModel.HeroSoulsSacrificed },
                                { "@TotalAncientSouls", miscellaneousStatsModel.TotalAncientSouls },
                                { "@TranscendentPower", miscellaneousStatsModel.TranscendentPower },
                                { "@Rubies", miscellaneousStatsModel.Rubies },
                                { "@HighestZoneThisTranscension", miscellaneousStatsModel.HighestZoneThisTranscension },
                                { "@HighestZoneLifetime", miscellaneousStatsModel.HighestZoneLifetime },
                                { "@AscensionsThisTranscension", miscellaneousStatsModel.AscensionsThisTranscension },
                                { "@AscensionsLifetime", miscellaneousStatsModel.AscensionsLifetime },
                                { "@MaxTranscendentPrimalReward", miscellaneousStatsModel.MaxTranscendentPrimalReward },
                                { "@BossLevelToTranscendentPrimalCap", miscellaneousStatsModel.BossLevelToTranscendentPrimalCap },
                            };

                            using (var command = databaseCommandFactory.Create())
                            {
                                command.BeginTransaction();

                                if (ancientLevelsCommandText.Length > 0)
                                {
                                    command.CommandText = ancientLevelsCommandText.ToString();
                                    command.ExecuteNonQuery();
                                }

                                if (outsiderLevelsCommandText.Length > 0)
                                {
                                    command.CommandText = outsiderLevelsCommandText.ToString();
                                    command.ExecuteNonQuery();
                                }

                                command.CommandText = ComputedStatsCommandText;
                                command.Parameters  = computedStatsCommandParameters;
                                command.ExecuteNonQuery();

                                command.CommandText = "UPDATE Uploads SET LastComputeTime = getutcdate() WHERE Id = " + uploadId;
                                command.ExecuteNonQuery();

                                var commited = command.CommitTransaction();
                                if (!commited)
                                {
                                    this.telemetryClient.TrackEvent("UploadProcessor-Abandoned-CommitTransaction", properties);
                                    return(false);
                                }
                            }

                            this.telemetryClient.TrackEvent("UploadProcessor-Complete", properties);
                            return(true);
                        }
                        catch (Exception e)
                        {
                            this.telemetryClient.TrackException(e, properties);
                            return(false);
                        }
                        finally
                        {
                            this.CurrentUploadId = null;
                        }
                    }
        }
 public SqlDatabaseGateway(DatabaseCommandFactory command_factory)
 {
     this.command_factory = command_factory;
 }