public async Task CallWebHooks() { List <int> callIds = null; using (TolkDbContext context = _options.GetContext()) { callIds = await context.OutboundWebHookCalls .Where(e => e.DeliveredAt == null && e.FailedTries < NumberOfTries && !e.IsHandling) .Select(e => e.OutboundWebHookCallId) .ToListAsync(); if (callIds.Any()) { var calls = context.OutboundWebHookCalls .Where(e => callIds.Contains(e.OutboundWebHookCallId) && e.IsHandling == false) .Select(c => c); await calls.ForEachAsync(c => c.IsHandling = true); await context.SaveChangesAsync(); } } _logger.LogInformation("Found {count} outbound web hook calls to send: {callIds}", callIds.Count, string.Join(", ", callIds)); if (callIds.Any()) { var tasks = new List <Task>(); foreach (var callId in callIds) { tasks.Add(Task.Factory.StartNew(() => CallWebhook(callId), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current)); } await Task.Factory.ContinueWhenAny(tasks.ToArray(), r => { }); } }
public async Task SendEmails() { using TolkDbContext context = _options.GetContext(); var emailIds = await context.OutboundEmails .Where(e => e.DeliveredAt == null && !e.IsHandling) .Select(e => e.OutboundEmailId) .ToListAsync(); _logger.LogInformation("Found {count} emails to send: {emailIds}", emailIds.Count, string.Join(", ", emailIds)); if (emailIds.Any()) { try { var emails = context.OutboundEmails .Where(e => emailIds.Contains(e.OutboundEmailId) && e.IsHandling == false) .Select(c => c); await emails.ForEachAsync(c => c.IsHandling = true); await context.SaveChangesAsync(); using var client = new SmtpClient(); await client.ConnectAsync(_options.Smtp.Host, _options.Smtp.Port, _options.Smtp.UseAuthentication?SecureSocketOptions.StartTls : SecureSocketOptions.Auto); if (_options.Smtp.UseAuthentication) { await client.AuthenticateAsync(_options.Smtp.UserName, _options.Smtp.Password); } var from = new MailboxAddress(_senderPrepend + Constants.SystemName, _options.Smtp.FromAddress); foreach (var emailId in emailIds) { var email = await context.OutboundEmails .SingleOrDefaultAsync(e => e.OutboundEmailId == emailId && e.DeliveredAt == null); try { if (email == null) { _logger.LogInformation("Email {emailId} was in list to be sent, but now appears to have been sent.", emailId); } else { email.IsHandling = true; await context.SaveChangesAsync(); _logger.LogInformation("Sending email {emailId} to {recipient}", emailId, email.Recipient); await client.SendAsync(email.ToMimeKitMessage(from)); email.DeliveredAt = _clock.SwedenNow; } } catch (Exception ex) { _logger.LogError(ex, "Failure sending e-mail {emailId}"); } finally { email.IsHandling = false; await context.SaveChangesAsync(); } } } catch (Exception ex) { _logger.LogError(ex, "Something went wrong when sending emails"); } finally { //Making sure no emails are left hanging var emails = context.OutboundEmails .Where(e => emailIds.Contains(e.OutboundEmailId) && e.IsHandling == true) .Select(c => c); await emails.ForEachAsync(c => c.IsHandling = false); await context.SaveChangesAsync(); } } }
private async void Run(bool isInit = false) { _logger.LogTrace("EntityScheduler waking up."); if ((nextDailyRunTime - _clock.SwedenNow).TotalHours > 25 || nextDailyRunTime.Hour != timeToRun) { _logger.LogWarning("nextDailyRunTime set to invalid time, was {0}", nextDailyRunTime); DateTimeOffset now = _clock.SwedenNow; now -= now.TimeOfDay; nextDailyRunTime = now - now.TimeOfDay; nextDailyRunTime = nextDailyRunTime.AddHours(timeToRun); if (_clock.SwedenNow.Hour > timeToRun) { // Next remind is tomorrow nextDailyRunTime = nextDailyRunTime.AddDays(1); } } if (isInit) { using TolkDbContext context = _options.GetContext(); await context.OutboundEmails.Where(e => e.IsHandling == true) .Select(c => c).ForEachAsync(c => c.IsHandling = false); await context.OutboundWebHookCalls.Where(e => e.IsHandling == true) .Select(c => c).ForEachAsync(c => c.IsHandling = false); await context.SaveChangesAsync(); } try { if (nextRunIsNotifications) { //Separate these, to get a better parallellism for the notifications // They fail to run together with the other Continous jobs , due to recurring deadlocks around the email table... List <Task> tasksToRunNotifications = new List <Task> { Task.Factory.StartNew(() => _services.GetRequiredService <EmailService>().SendEmails(), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current), Task.Factory.StartNew(() => _services.GetRequiredService <WebHookService>().CallWebHooks(), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current) }; await Task.Factory.ContinueWhenAny(tasksToRunNotifications.ToArray(), r => { }); } else { //would like to have a timer here, to make it possible to get tighter runs if the last run ran for longer than 10 seconds or somethng... using var serviceScope = _services.CreateScope(); Task[] tasksToRun; if (_clock.SwedenNow > nextDailyRunTime) { nextDailyRunTime = nextDailyRunTime.AddDays(1); nextDailyRunTime -= nextDailyRunTime.TimeOfDay; nextDailyRunTime = nextDailyRunTime.AddHours(timeToRun); _logger.LogTrace("Running DailyRunTime, next run on {0}", nextDailyRunTime); tasksToRun = new Task[] { RunDailyJobs(serviceScope.ServiceProvider), }; } else { tasksToRun = new Task[] { RunContinousJobs(serviceScope.ServiceProvider), }; } if (!Task.WaitAll(tasksToRun, allotedTimeAllTasks)) { throw new InvalidOperationException($"All tasks instances didn't complete execution within the allotted time: {allotedTimeAllTasks / 1000} seconds"); } } } catch (Exception ex) { _logger.LogCritical(ex, "Entity Scheduler failed ({message}).", ex.Message); _ = _services.GetRequiredService <EmailService>().SendErrorEmail(nameof(EntityScheduler), nameof(Run), ex); } finally { nextRunIsNotifications = !nextRunIsNotifications; if (_services.GetRequiredService <ITolkBaseOptions>().RunEntityScheduler) { await Task.Delay(timeDelayContinousJobs).ContinueWith(t => Run(), TaskScheduler.Default); } } _logger.LogTrace($"EntityScheduler done, scheduled to wake up in {timeDelayContinousJobs / 1000} seconds again"); }