private async Task <List <UserExerciseSubmission> > TryGetExerciseSubmissions(int count) { var notSoLongAgo = DateTime.Now - TimeSpan.FromMinutes(15); List <UserExerciseSubmission> submissions; using (var transaction = db.Database.BeginTransaction(IsolationLevel.Serializable)) { submissions = db.UserExerciseSubmissions .AsNoTracking() .Where(s => s.Timestamp > notSoLongAgo && s.AutomaticChecking.Status == AutomaticExerciseCheckingStatus.Waiting) .OrderByDescending(s => s.Timestamp) .Take(count) .ToList(); foreach (var submission in submissions) { submission.AutomaticChecking.Status = AutomaticExerciseCheckingStatus.Running; } await SaveAll(submissions.Select(s => s.AutomaticChecking)); transaction.Commit(); db.ObjectContext().AcceptAllChanges(); } foreach (var submission in submissions) { unhandledSubmissions.TryRemove(submission.Id, out _); } return(submissions); }
private async Task <UserExerciseSubmission> TryGetExerciseSubmission(string agentName, SubmissionLanguage language) { var notSoLongAgo = DateTime.Now - TimeSpan.FromMinutes(15); UserExerciseSubmission submission; using (var transaction = db.Database.BeginTransaction(IsolationLevel.Serializable)) { var submissionsQueryable = db.UserExerciseSubmissions .AsNoTracking() .Where(s => s.Timestamp > notSoLongAgo && s.AutomaticChecking.Status == AutomaticExerciseCheckingStatus.Waiting && s.Language == language); if (!submissionsQueryable.Any()) { return(null); } var maxId = submissionsQueryable.Select(s => s.Id).DefaultIfEmpty(-1).Max(); submission = submissionsQueryable.FirstOrDefault(s => s.Id == maxId); if (submission == null) { return(null); } /* Mark submission as "running" */ submission.AutomaticChecking.Status = AutomaticExerciseCheckingStatus.Running; submission.AutomaticChecking.CheckingAgentName = agentName; await SaveAll(new List <AutomaticExerciseChecking> { submission.AutomaticChecking }); transaction.Commit(); db.ObjectContext().AcceptAllChanges(); } unhandledSubmissions.TryRemove(submission.Id, out _); return(submission); }
protected async Task SaveAll(IEnumerable <AutomaticExerciseChecking> checkings) { foreach (var checking in checkings) { log.Info($"Обновляю статус автоматической проверки #{checking.Id}: {checking.Status}"); db.AutomaticExerciseCheckings.AddOrUpdate(checking); UpdateIsRightAnswerForSubmission(checking); } try { await db.ObjectContext().SaveChangesAsync(SaveOptions.DetectChangesBeforeSave).ConfigureAwait(false); } catch (DbEntityValidationException e) { throw new Exception( string.Join("\r\n", e.EntityValidationErrors.SelectMany(v => v.ValidationErrors).Select(err => err.PropertyName + " " + err.ErrorMessage))); } }
// ReSharper disable once MemberCanBePrivate.Global public IQueryable <UserIdWrapper> GetUsersByNamePrefix(string name) { if (string.IsNullOrEmpty(name)) { return(db.Users.Where(u => !u.IsDeleted).Select(u => new UserIdWrapper(u.Id))); } var splittedName = name.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); var nameQuery = string.Join(" & ", splittedName.Select(s => "\"" + s.Trim().Replace("\"", "\\\"") + "*\"")); var nameParameter = new ObjectParameter("name", nameQuery); return(db.ObjectContext().CreateQuery <UserIdWrapper>($"[{nameof(GetUsersByNamePrefix)}](@name)", nameParameter)); }
// ReSharper disable once MemberCanBePrivate.Global public IQueryable <SubmissionIdWrapper> GetGraderSolutionByClientUserId(string clientUserId) { if (string.IsNullOrEmpty(clientUserId)) { return(db.ExerciseSolutionsByGrader.Select(s => new SubmissionIdWrapper(s.Id))); } var splittedUserId = clientUserId.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); var query = string.Join(" & ", splittedUserId.Select(s => "\"" + s.Trim().Replace("\"", "\\\"") + "*\"")); var parameter = new ObjectParameter("userId", query); return(db.ObjectContext().CreateQuery <SubmissionIdWrapper>($"[{nameof(GetGraderSolutionByClientUserId)}](@userId)", parameter)); }
private async Task TryUpdate(string userId, Guid slideId, string ltiRequestJson) { var ltiRequestModel = FindElement(userId, slideId); if (ltiRequestModel == null) { ltiRequestModel = new LtiSlideRequest { UserId = userId, SlideId = slideId, Request = ltiRequestJson }; } else { ltiRequestModel.Request = ltiRequestJson; } db.LtiRequests.AddOrUpdate(ltiRequestModel); await db.ObjectContext().SaveChangesAsync(SaveOptions.DetectChangesBeforeSave); db.ObjectContext().AcceptAllChanges(); }
private async Task<UserExerciseSubmission> TryGetExerciseSubmission(string agentName, IEnumerable<Language> languages) { var notSoLongAgo = DateTime.Now - TimeSpan.FromMinutes(15); var submissionsQueryable = db.UserExerciseSubmissions .AsNoTracking() .Where(s => s.Timestamp > notSoLongAgo && s.AutomaticChecking.Status == AutomaticExerciseCheckingStatus.Waiting && languages.Contains(s.Language)); var maxId = submissionsQueryable.Select(s => s.Id).DefaultIfEmpty(-1).Max(); if (maxId == -1) return null; // NOTE: Если транзакция здесь, а не в начале метода, может возникнуть ситуация, что maxId только что кто-то взял, и мы тоже взяли. // То, что мы не обработаем дважды, защищает проверка на Waiting внутри транзакции ниже. // Мы можем не взять из-за этого другой solution. Не стращно, попробуем снова сразу же с помощью WaitAnyUnhandledSubmissions (см. RunnerController.GetSubmissions). // RepeatableRead блокирует от изменения те строки, которые видел. // Serializable отличается от него только тем, что другая транзакция не добавит другую строку, которая тоже будет подходить под запрос, даже после того, как запрос совершен. // Нам важно только, чтобы не менялись виденные в транзакции строки, поэтому подходит RepeatableRead. // Хотя, здесь делается запрос просто по Id, поэтому в любом случае заблокированных строк мало. // Малое количество затронутых строк должно уменьшить возможность дедлоков. Теоретически, если обе прочитают одно и то же и заходят записать, должна сработать одна транзакция и одна откатиться. // Дедлоки всё-таки есть в большом количестве, поэтому поставил Semaphore log.Debug("GetUnhandledSubmission(): trying to acquire semaphore"); var semaphoreLocked = await getSubmissionSemaphore.WaitAsync(TimeSpan.FromSeconds(2)); if (!semaphoreLocked) { log.Error("TryGetExerciseSubmission(): Can't lock semaphore for 2 seconds"); return null; } log.Debug("GetUnhandledSubmission(): semaphore acquired!"); try { UserExerciseSubmission submission; using (var transaction = db.Database.BeginTransaction(IsolationLevel.RepeatableRead)) { submission = db.UserExerciseSubmissions.AsNoTracking().FirstOrDefault(s => s.Id == maxId); if (submission == null) return null; if (submission.AutomaticChecking.Status != AutomaticExerciseCheckingStatus.Waiting) return null; /* Mark submission as "running" */ submission.AutomaticChecking.Status = AutomaticExerciseCheckingStatus.Running; submission.AutomaticChecking.CheckingAgentName = agentName; await SaveAll(new List<AutomaticExerciseChecking> { submission.AutomaticChecking }).ConfigureAwait(false); transaction.Commit(); db.ObjectContext().AcceptAllChanges(); } unhandledSubmissions.TryRemove(submission.Id, out _); return submission; } catch (Exception e) { log.Error("TryGetExerciseSubmission() error", e); return null; } finally { log.Debug("GetUnhandledSubmission(): trying to release semaphore"); getSubmissionSemaphore.Release(); log.Debug("GetUnhandledSubmission(): semaphore released"); } }