Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

V12: Suppress execution context flow when queuing email task #14571

Merged
merged 1 commit into from Jul 19, 2023

Conversation

nikolajlauridsen
Copy link
Contributor

Suppresses the execution context flow when queuing a task for sending notification emails. This is required because otherwise, the execution context would leak into the async thread that won't be joined.

This caused issues when trying to replace the IEmailSender with a different implementation that creates a scope since the execution context was flowed, it gets added to the same scope stack as the other thread, leading to race conditions that would throw errors because a child scope on the other thread wasn't disposed. This should no longer happen.

It looks like there's a lot of changes in the PR, but all I did was wrapping the implementation of NotificationService.Process in a using (ExecutionContext.SuppressFlow()) block.

Testing

Since this is essentially a race condition it's pretty hard to test, but I was able to reproduce the issue somewhat reliably with the following approach (Thanks to the heartcore team for the test setup and heads up 馃挭):

  1. Add a custom IEmailSender that calls UserService.GetByEmail (see below for example)
  2. Replace it in ConfigureServices with: services.Replace(ServiceDescriptor.Singleton<IEmailSender, HeadlessEmailSender>());
  3. Create a simple document type and content node (doesn't need any content)
  4. Set up notifications on publish
  5. Restart Umbraco
  6. Click Save and publish as the first action

This will throw a System.InvalidOperationException, not all the time, but more often than not.

After checking out this branch the error should no longer occur.

Code snippet:

using Umbraco.Cms.Core.Mail;
using Umbraco.Cms.Core.Models.Email;
using Umbraco.Cms.Core.Services;

namespace Umbraco.Cms.Web.UI;

public class HeadlessEmailSender : IEmailSender
{
    private IUserService _userService;

    public HeadlessEmailSender(IUserService userService)
    {
        _userService = userService;
    }

    public bool CanSendRequiredEmail()
    {
        return true;
    }

    public Task SendAsync(EmailMessage message, string emailType)
    {
        _userService.GetByEmail(message.To.Single()!);
        return Task.CompletedTask;
    }

    public Task SendAsync(EmailMessage message, string emailType, bool enableNotification)
    {
        throw new NotImplementedException();
    }
}

@nikolajlauridsen nikolajlauridsen changed the base branch from contrib to v12/dev July 18, 2023 09:50
Copy link
Member

@elit0451 elit0451 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good and tests out good 馃檶

Merging!

@elit0451 elit0451 merged commit 4824fc8 into v12/dev Jul 19, 2023
14 checks passed
@elit0451 elit0451 deleted the v12/fix/fix_scope_disposed_error branch July 19, 2023 11:42
@Zero3
Copy link
Contributor

Zero3 commented Aug 24, 2023

Thanks for the fix guys! 馃憤

Glad to see that you found a solution and released it already. I was afraid that it was me that was seeing ghosts or messing something up badly somehow, given that my code sometimes worked and sometimes failed miserably.

@nikolajlauridsen
Copy link
Contributor Author

Yeah it was a bit of an ugly race condition, but thanks a lot for letting us know so we could get it fixed 馃挭

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants