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