ServerProcessExtensions is really using ThreadPool

There is a big comment in the ServerProcessExtensions class:

// Long-running tasks are based on custom threads (not threadpool ones) as in
// .NET Framework 4.5, so we can try to set custom thread name to simplify the
// debugging experience.

Pretty sure this is not accurate. LongRunning is frequently misused and does not guarantee custom threads will be used. This is because the Task.Factory.StartNew requires to provide a TaskScheduler otherwise it uses the default one, which is the ThreadPoolTaskScheduler.

Great article here: https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html

Don’t worry, 1.7.0-beta1 is already using pool of really custom threads (via this PR) to avoid any confusion and possible changes in future, and even has a custom TaskScheduler for future works on asynchronous processing. Regarding the ThreadPoolTaskScheduler, don’t worry too, it create custom threads for the LongRunning option.

I have a problem where creating jobs with RecurringJob.AddOrUpdate seems to reuse threads of a threadpool because my structuremap context is reused, causing me bad IoC injections (I created custom JobActivator and JobActivatorScope.
).

This is why I checked the hangfire source code that leads to my original post.
I update my nuget to the version 1.7.0-beta1 just see if my problem is still there and yes. Normally new threads had fresh structuremap cache… I am sad…

If this new version actually creates new thread sand never reuse old ones, all me thoeries fall appart. Considering using hangfire only as a scheduler to fire message queueing message handled by daemons that actually do the workload…

What’s your background job method look like? Could you post the code?

This is how I register the jobs:

public static void Register(string jobSchedule)
{
RecurringJob.AddOrUpdate(
“O365SubscriptionSyncJob” + Sites.Canada,
j => j.Execute(),
jobSchedule,
queue: Queues.Canada);

        RecurringJob.AddOrUpdate<O365SubscriptionSyncJobUS>(
            "O365SubscriptionSyncJob" + Sites.UnitedStates,
            j => j.Execute(),
            jobSchedule,
            queue: Queues.UnitedStates);

        RecurringJob.AddOrUpdate<O365SubscriptionSyncJobEU>(
            "O365SubscriptionSyncJob" + Sites.Europe,
            j => j.Execute(),
            jobSchedule,
            queue: Queues.Europe);
    }

This is one job:

[Site(Sites.Canada)]
public class O365SubscriptionSyncJobCA : IJob
{
private readonly O365SubscriptionSyncJob _o365SubscriptionSyncJob;

    public O365SubscriptionSyncJobCA(O365SubscriptionSyncJob o365SubscriptionSyncJob)
    {
        this._o365SubscriptionSyncJob = o365SubscriptionSyncJob;            
    }

    [DisableConcurrentExecution(0)]
    [AutomaticRetry(Attempts = 0, LogEvents = false)]
    public void Execute()
    {
        this._o365SubscriptionSyncJob.Execute();
    }
}

Custom StructureMap stuff:

using System;
using System.Reflection;
using System.Threading;

using Hangfire;

using StructureMap;

using Core.O365.Entities.Platform;
using Core.O365.Entities.Platform.Organizations;
using Core.O365.Repositories.Factories;
using Core.O365.Repositories.Gateways.Subscriptions;
using Core.O365.Services.Subscriptions;
using Validation;

namespace Core.O365.Front.WebApi.HangfireJobs
{
// Source: https://github.com/cocowalla/Hangfire.StructureMap/edit/master/src/Hangfire.StructureMap/StructureMapJobActivator.cs
public class StructureMapJobActivator : JobActivator
{
private readonly IContainer _container;

    public StructureMapJobActivator(IContainer container)
    {
        this._container = Validate.Is.Not.Null(container, nameof(container));
    }

    public override object ActivateJob(Type jobType)
    {
        throw new NotImplementedException("Contact Ben if this is called");
    }

    public override JobActivatorScope BeginScope(JobActivatorContext context)
    {
        return new StructureMapDependencyScope(this._container.GetNestedContainer(), this._container);
    }

    private class StructureMapDependencyScope : JobActivatorScope
    {
        private readonly IContainer _container;

        private readonly IContainer _parentContainer;

        public StructureMapDependencyScope(IContainer container, IContainer parentContainer)
        {
            this._container = container;
            this._parentContainer = parentContainer;
        }

        public override object Resolve(Type type)
        {
            this.ConfigureSiteScopedDependencies(type.GetCustomAttribute<SiteAttribute>()?.Value);
            return this._container.GetInstance(type);
        }

        public override void DisposeScope()
        {
            this._container.Dispose();
        }

        private void ConfigureSiteScopedDependencies(string siteCode)
        {
            if (siteCode == null)
            {
                return;
            }

            
            this._container.Configure(
                x =>
                {
                    x.For<IRepositoryFactory>()
                        .HybridHttpOrThreadLocalScoped()
                        .Use(y => y.GetInstance<IRepositoryFactory>(siteCode.ToUpper()));

                    x.For<IOrganizationSiteScopedRepository>()
                        .HybridHttpOrThreadLocalScoped()
                        .Use(y => y.GetInstance<IOrganizationSiteScopedRepository>(siteCode.ToUpper()));

                    x.For<ISubscriptionImportService>()
                        .HybridHttpOrThreadLocalScoped()
                        .Use(y => y.GetInstance<ISubscriptionImportService>(siteCode.ToUpper()));
                });



            var subscriptionGateway = this._container.GetInstance<ISubscriptionGateway>();
            var site = subscriptionGateway.Site;


            if (Sites.Parse(site) != siteCode)
            {
               /// IoC problem, thread reused!
            }
        }
    }
}

}

Threads aren’t transitioned magically. It’s either this._o365SubscriptionSyncJob.Execute() is doing something strange with threads, or your issue isn’t related to threading at all. So it’s better to check other possible reasons rather than sticking with that comment.