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()); }
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)); }