示例#1
0
        public async Task Execute(long reportId, CancellationToken cancellationToken)
        {
            var report = await database.CrashReports.FindAsync(new object[] { reportId }, cancellationToken);

            if (report == null)
            {
                logger.LogWarning("Can't delete dump file for non-existent report: {ReportId}", reportId);
                return;
            }

            if (report.DumpLocalFileName == null)
            {
                logger.LogInformation("Crash report dump file is already deleted for report: {ReportId}", reportId);
                return;
            }

            await DeleteReportTempFile(report, localTempFileLocks, logger, cancellationToken);

            await database.LogEntries.AddAsync(new LogEntry()
            {
                Message = $"Deleted crash dump file for {report.Id}",
            }, cancellationToken);

            report.DumpLocalFileName = null;
            report.BumpUpdatedAt();

            await database.SaveChangesWithConflictResolvingAsync(
                conflictEntries =>
            {
                DatabaseConcurrencyHelpers.ResolveSingleEntityConcurrencyConflict(conflictEntries, report);
                report.DumpLocalFileName = null;
                report.BumpUpdatedAt();
            }, cancellationToken);
        }
        public async Task SaveConcurrencyErrorResolving_WorksWithCrashReportApproach()
        {
            var database = fixture.Database;

            await using var transaction = await database.Database.BeginTransactionAsync();

            var item1 = new CrashReport()
            {
                Description       = "Test description",
                DumpLocalFileName = "test.dmp",
                UploadedFrom      = new IPAddress(1234),
                ExitCodeOrSignal  = "SIGSEGV",
                Logs = "logs",
            };

            await database.CrashReports.AddAsync(item1);

            await database.SaveChangesAsync();

            var item1Instance2 = await database.CrashReports.FindAsync(item1.Id);

            Assert.NotNull(item1Instance2);
            Assert.Equal(item1.Id, item1Instance2.Id);

            Assert.NotEqual(UpdatedDescription, item1.Description);
            Assert.Equal(item1.Description, item1Instance2.Description);
            item1Instance2.Description = UpdatedDescription;
            Assert.NotNull(item1Instance2.DumpLocalFileName);

            // Intentionally cause a conflict
            await fixture.Database.Database.ExecuteSqlInterpolatedAsync(
                $"UPDATE crash_reports SET dump_local_file_name = NULL WHERE id = {item1.Id}");

            await Assert.ThrowsAsync <DbUpdateConcurrencyException>(() => database.SaveChangesAsync());

            Assert.NotNull(item1Instance2.DumpLocalFileName);

            await database.SaveChangesWithConflictResolvingAsync(
                conflictEntries =>
            {
                Assert.Equal(UpdatedDescription, item1Instance2.Description);
                DatabaseConcurrencyHelpers.ResolveSingleEntityConcurrencyConflict(conflictEntries, item1Instance2);
                Assert.NotEqual(UpdatedDescription, item1Instance2.Description);
                item1Instance2.Description = UpdatedDescription;
            }, CancellationToken.None, false);

            Assert.Equal(UpdatedDescription, item1Instance2.Description);

            Assert.Null(item1Instance2.DumpLocalFileName);
        }
        public async Task <IActionResult> ClearReporterEmail([Required] long id)
        {
            var report = await database.CrashReports.FindAsync(id);

            if (report == null)
            {
                return(NotFound());
            }

            var user = HttpContext.AuthenticatedUserOrThrow();

            if (report.ReporterEmail == null)
            {
                return(Ok("Reporter email was not set"));
            }

            await database.AdminActions.AddAsync(new AdminAction()
            {
                Message = $"Crash report {report.Id} reporter email cleared",

                // TODO: there could be an extra info property where the description is stored
                PerformedById = user.Id,
            });

            report.ReporterEmail = null;
            report.BumpUpdatedAt();

            await database.SaveChangesWithConflictResolvingAsync(
                conflictEntries =>
            {
                DatabaseConcurrencyHelpers.ResolveSingleEntityConcurrencyConflict(conflictEntries, report);
                report.ReporterEmail = null;
                report.BumpUpdatedAt();
            }, CancellationToken.None);

            logger.LogInformation("Crash report {Id} reporter email cleared by {Email}", report.Id,
                                  user.Email);
            return(Ok());
        }
示例#4
0
        public async Task Execute(long reportId, CancellationToken cancellationToken)
        {
            var report = await database.CrashReports.FindAsync(new object[] { reportId }, cancellationToken);

            if (report == null)
            {
                logger.LogWarning("Can't auto check report if it is a duplicate as it doesn't exist: {ReportId}",
                                  reportId);
                return;
            }

            // Only open reports can be considered duplicates
            if (report.State != ReportState.Open)
            {
                return;
            }

            if (report.CondensedCallstack == null)
            {
                logger.LogWarning(
                    "Report is missing primary (condensed) callstack, can't check whether it is a " +
                    "duplicate automatically: {ReportId}",
                    reportId);
                return;
            }

            // Don't detect reports that have no valid callstack as duplicates
            if (report.CondensedCallstack.Contains("<no frames>"))
            {
                logger.LogWarning(
                    "Report {ReportId} doesn't have detected callstack frames, skipping duplicate check",
                    reportId);
                return;
            }

            // TODO: should this use the first 75% of the stack lines to find duplicates (but at least 3)?

            // TODO: if this is a public report, it should not become a duplicate of a private report
            var potentiallyDuplicateOf = await database.CrashReports.Where(r =>
                                                                           r.CondensedCallstack != null && r.Id != report.Id && r.State != ReportState.Duplicate &&
                                                                           r.CondensedCallstack.Contains(report.CondensedCallstack))
                                         .OrderBy(r => r.Id).FirstOrDefaultAsync(cancellationToken);

            if (potentiallyDuplicateOf == null)
            {
                logger.LogInformation("Report {ReportId} is not a duplicate", reportId);
                return;
            }

            SetReportData(report, potentiallyDuplicateOf.Id);

            await database.SaveChangesWithConflictResolvingAsync(
                conflictEntries =>
            {
                DatabaseConcurrencyHelpers.ResolveSingleEntityConcurrencyConflict(conflictEntries, report);
                SetReportData(report, potentiallyDuplicateOf.Id);
            }, cancellationToken);

            logger.LogInformation("Report {ReportId} seems to be a duplicate of {Id}", reportId,
                                  potentiallyDuplicateOf.Id);

            jobClient.Enqueue <SendCrashReportWatcherNotificationsJob>(x => x.Execute(reportId,
                                                                                      $"Report was detected to be a duplicate of #{potentiallyDuplicateOf.Id} automatically",
                                                                                      CancellationToken.None));

            discordNotifications.NotifyCrashReportStateChanged(report, baseUrl);
        }
        public async Task Execute(long reportId, CancellationToken cancellationToken)
        {
            if (!stackwalk.Configured)
            {
                throw new Exception("Stackwalk is not configured");
            }

            var report = await database.CrashReports.FindAsync(new object[] { reportId }, cancellationToken);

            if (report == null)
            {
                logger.LogError("Can't stackwalk on non-existent report: {ReportId}", reportId);
                return;
            }

            if (string.IsNullOrEmpty(report.DumpLocalFileName))
            {
                logger.LogError("Can't stackwalk on report that no longer has local dump: {ReportId}", reportId);
                return;
            }

            var symbolPrepareTask = symbolPreparer.PrepareSymbolsInFolder(symbolFolder, cancellationToken);

            logger.LogInformation("Starting stackwalk on report {ReportId}", reportId);

            var semaphore =
                localTempFileLocks.GetTempFilePath(CrashReport.CrashReportTempStorageFolderName, out string baseFolder);

            var filePath = Path.Combine(baseFolder, report.DumpLocalFileName);

            FileStream?dump = null;

            // On Linux an open file should not impact deleting etc. so I'm pretty sure this is pretty safe
            await semaphore.WaitAsync(cancellationToken);

            try
            {
                if (File.Exists(filePath))
                {
                    dump = File.OpenRead(filePath);
                }
            }
            finally
            {
                semaphore.Release();
            }

            await symbolPrepareTask;

            if (report.DumpLocalFileName == null || dump == null)
            {
                logger.LogError("Can't stackwalk on report with missing dump file: {ReportId}", reportId);
                return;
            }

            var startTime = DateTime.UtcNow;

            // TODO: implement an async API in the stackwalk service and swap to using that here
            var result = await stackwalk.PerformBlockingStackwalk(dump, report.Platform, cancellationToken);

            var primaryCallstack   = stackwalk.FindPrimaryCallstack(result);
            var condensedCallstack = stackwalk.CondenseCallstack(primaryCallstack);

            cancellationToken.ThrowIfCancellationRequested();

            var duration = DateTime.UtcNow - startTime;

            logger.LogInformation("Stackwalking took: {Duration}", duration);

            await database.LogEntries.AddAsync(new LogEntry()
            {
                Message = $"Stackwalking performed on report {report.Id}, result length: {result.Length}, " +
                          $"duration: {duration}",
            }, cancellationToken);

            if (string.IsNullOrWhiteSpace(result))
            {
                result = "Resulting decoded crash dump is empty";
            }

            report.UpdateProcessedDumpIfChanged(result, primaryCallstack, condensedCallstack);

            await database.SaveChangesWithConflictResolvingAsync(
                conflictEntries =>
            {
                DatabaseConcurrencyHelpers.ResolveSingleEntityConcurrencyConflict(conflictEntries, report);
                report.UpdateProcessedDumpIfChanged(result, primaryCallstack, condensedCallstack);
            }, cancellationToken);

            jobClient.Schedule <CheckCrashReportDuplicatesJob>(x => x.Execute(report.Id, CancellationToken.None),
                                                               TimeSpan.FromSeconds(10));
        }