HangfireIO / Hangfire

An easy way to perform background job processing in .NET and .NET Core applications. No Windows Service or separate process required
https://www.hangfire.io
Other
9.4k stars 1.7k forks source link

Jobs mixed-up when running parallel integration tests with ASP.NET Core in a single process #2151

Open corentinaltepe opened 1 year ago

corentinaltepe commented 1 year ago

Hello,

We have an ASP.NET Core app which runs hangfire client and server. We have an integration tests suite using the WebApplicationFactory pattern and it works great.

When we run this test suite in six parallel processes with NCrunch to have it run faster, it works properly.

However, I've tried to run tests in parallel in a single process using xUnit and NUnit and both give errors. Let's say we have two instances of the app created with new WebApplicationFactory<Startup>().CreateClient() running concurrently on a same process, then we have two Hangfire servers on the same process. It looks like these two servers workers pick each other's jobs, messing with the test results, as the app is not designed to allow more than one worker per app.

It looks like Hangfire server has some static state, which is shared accross the different instances of the apps under tests. Is this behavior expected or am I doing something wrong here? I have looked on the internet for similar issues but couldn't find answers.

The things I have tried so far:

I've also tried several combinations of these but only mocking IBackgroundJobClient (bypassing Hangfire entirely) has brought results.

Would you have any input or recommendation? Thank you in advance.

corentinaltepe commented 8 months ago

Hi,

I've hit this issue again. I tried injecting a different instance of JobStorage in each application instance under test (test scope). But I still get the jobs mixed up. I believe this is cause by this specific line, which assigns the JobStorage into a static variable, which then crosses boundaries set by the test scopes.

public static IGlobalConfiguration<TStorage> UseStorage<TStorage>(
  [NotNull] this IGlobalConfiguration configuration,
  [NotNull] TStorage storage)
  where TStorage : JobStorage
  {
    if (configuration == null) throw new ArgumentNullException(nameof(configuration));
    if (storage == null) throw new ArgumentNullException(nameof(storage));

    return configuration.Use(storage, x => JobStorage.Current = x);
  }
jufa2401 commented 5 months ago

@corentinaltepe

Did you manage to find a work-around? Our team is suspicious we have run into the same issue 😁

domn1995 commented 2 months ago

Running into this issue as well. Would love to know if anyone has a workaround or if we have a suggested fix.

corentinaltepe commented 2 months ago

hi @jufa2401, hi @domn1995. Unfortunately I haven't been able to fix the issue nor work around it.

I still believe this line is reponsible for the static singleton pattern affecting parallel execution of several Hangfire Servers within the same process.

@odinserj, would you accept a PR to change the behavior within the Hangfire repository directly? The idea would be to replace the static singleton pattern with a non-static IoC container singleton pattern when possible. We'd have to be super careful about side-effects, as it would be a sensitive part of the code, and I'm not familiar with it.

domn1995 commented 2 months ago

@corentinaltepe After deep diving the code I agree with you. @odinserj I would be happy to contribute a PR improving this design, because our code using hangfire is essentially untestable at the moment :(