Open Closed

DbMigrator will not be functional anymore if you decide to use Hangfire as Background Worker/Job #2940


User avatar
0
raif created
  • ABP Framework version: v5.2.0
  • UI type: Blazor
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): yes
  • Exception message and stack trace: [11:19:59 INF] Start installing Hangfire SQL objects... [11:20:11 WRN] An exception occurred while trying to perform the migration. Retrying... System.Data.SqlClient.SqlException (0x80131904): Cannot open database "hangfiretest" requested by the login. The login failed. Login failed for user 'AD001\z003dr8m'. at System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, Object providerInfo, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling) at System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) at System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions) at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource1 retry, DbConnectionOptions userOptions) at System.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource1 retry, DbConnectionOptions userOptions) at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource1 retry) at System.Data.SqlClient.SqlConnection.Open() at Hangfire.SqlServer.SqlServerStorage.CreateAndOpenConnection() at Hangfire.SqlServer.SqlServerStorage.UseConnection[T](DbConnection dedicatedConnection, Func2 func) at Hangfire.SqlServer.SqlServerStorage.Initialize() ClientConnectionId:818770b2-7b19-493b-974a-ffce2693698f Error Number:4060,State:1,Class:11
  • Steps to reproduce the issue:"
  1. Create new application by using application template
  2. Add Hangfire.SqlServer package for job storage persistence
  3. Add Volo.Abp.HangFire for AbpHangfireOptions
  4. Define connection string for job storage e.g :
	GlobalConfiguration.Configuration
		// Use custom connection string
		.UseSqlServerStorage(configuration.GetConnectionString("Default"));
  1. Even if you try to disable it by using AbpBackgroundJobOptions and AbpBackgroundWorkerOptions options, AbpBackgroundWorkersHangfireModule and AbpBackgroundJobsHangfireModule will try to create Job Server (CreateOnlyEnqueueJobServer)
	public override void ConfigureServices(ServiceConfigurationContext context)
	{
		var configuration = context.Services.GetConfiguration();

		Configure<AbpBackgroundJobOptions>(options =>
		{
			options.IsJobExecutionEnabled = false;
		});

		Configure<AbpBackgroundWorkerOptions>(options =>
		{
			options.IsEnabled = false;
		});
	}

AbpBackgroundWorkersHangfireModule on pre application initilization stage :

public async override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
{
    var options = context.ServiceProvider.GetRequiredService&lt;IOptions&lt;AbpBackgroundWorkerOptions&gt;>().Value;
    if (!options.IsEnabled)
    {
        var hangfireOptions = context.ServiceProvider.GetRequiredService&lt;IOptions&lt;AbpHangfireOptions&gt;>().Value;
        hangfireOptions.BackgroundJobServerFactory = CreateOnlyEnqueueJobServer;
    }
    
    await context.ServiceProvider
        .GetRequiredService&lt;IBackgroundWorkerManager&gt;()
        .StartAsync(); 
}
  1. GG. As you can imagine, at that stage DB is not created and Hangfire SQL intsall scripts are going to fail.

5 Answer(s)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    I think you should create a database first, hangfire will not create a database for you.

    See: https://github.com/HangfireIO/Hangfire/blob/master/src/Hangfire.SqlServer/Install.sql

  • User Avatar
    0
    raif created

    Hi,

    I think you should create a database first, hangfire will not create a database for you.

    See: https://github.com/HangfireIO/Hangfire/blob/master/src/Hangfire.SqlServer/Install.sql

    Hi, Of course we need to create DB first in place. Dbmigrator console application already exists to facilitate this database create operation, right ?

       public async Task StartAsync(CancellationToken cancellationToken)
        {
            using (var application = await AbpApplicationFactory.CreateAsync<hangfireDbMigratorModule>(options =>
            {
               options.Services.ReplaceConfiguration(_configuration);
               options.UseAutofac();
               options.Services.AddLogging(c => c.AddSerilog());
            }))
            {
                **await application.InitializeAsync();**
    
                await application
                    .ServiceProvider
                    .GetRequiredService<hangfireDbMigrationService>()
                    .MigrateAsync();
    
                await application.ShutdownAsync();
    
                _hostApplicationLifetime.StopApplication();
            }
        }
    

    So let me go step by step. Console application is going to initialize modules. Right ?

    That means whenever you use Volo.Abp.BackgroundWorkers.HangFire and/or Volo.Abp.BackgroundJobs.HangFire you will also run OnPreApplicationInitialization method. Right ?

    public async override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
    {
        var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value;
        if (!options.IsEnabled)
        {
            var hangfireOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpHangfireOptions>>().Value;
            hangfireOptions.BackgroundJobServerFactory = CreateOnlyEnqueueJobServer;
        }
        
        await context.ServiceProvider
            .GetRequiredService<IBackgroundWorkerManager>()
            .StartAsync(); 
    }
    
    public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
    {
        AsyncHelper.RunSync(() => OnPreApplicationInitializationAsync(context));
    }
    

    More or less whenever you start BackgroundWorkerManager you will also going to try to create **BackgroundJobServer **

    await context.ServiceProvider
        .GetRequiredService<IBackgroundWorkerManager>()
        .StartAsync();
    

    And **BackgroundJobServer **ceration requires JobStorage .Btw BackgroundJobServer constructor used in the abp package is the obsolete one

    private BackgroundJobServer CreateJobServer(IServiceProvider serviceProvider)
    {
        Storage = Storage ?? serviceProvider.GetRequiredService<JobStorage>();
        ServerOptions = ServerOptions ?? serviceProvider.GetService<BackgroundJobServerOptions>() ?? new BackgroundJobServerOptions();
        AdditionalProcesses = AdditionalProcesses ?? serviceProvider.GetServices<IBackgroundProcess>();
    
        return new BackgroundJobServer(ServerOptions, Storage, AdditionalProcesses,
            ServerOptions.FilterProvider ?? serviceProvider.GetRequiredService<IJobFilterProvider>(),
            ServerOptions.Activator ?? serviceProvider.GetRequiredService<JobActivator>(),
            serviceProvider.GetService<IBackgroundJobFactory>(),
            serviceProvider.GetService<IBackgroundJobPerformer>(),
            serviceProvider.GetService<IBackgroundJobStateChanger>()
        );
    }
    

    If you don't define any JobStorage that will cause exception about JobStorage can't be null exception

    GlobalConfiguration.Configuration
        .UseSqlServerStorage(configuration.GetConnectionString("Default"));
    

    If you define JobStorage then it will going to execute https://github.com/HangfireIO/Hangfire/blob/master/src/Hangfire.SqlServer/Install.sql scripts.

    And as you already aware of that, scripts requires database in first place.

    And if you remember we are about create DB :)

    Long of short: Your Volo.Abp.BackgroundWorkers.HangFire and/or Volo.Abp.BackgroundJobs.HangFire are killing DbMigrator functionality by their nature of design.

    Steps to reproduce the issue :

    1. Create new application by using application template
    2. Add Hangfire.SqlServer package for job persistence
    3. Add Volo.Abp.HangFire (this should enough to force convert existing background jobs into the hangfire one like tokencleanup etc)
    4. Define connection string for job storage
    5. Run Db Migrator

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    I'm sorry for that, yes the DBMigrator is used to create a database and tables.

    But it is only used to create ABP's database tables, not Hangfire.

    It looks like that Hangfire will prioritize execution and break the application, but this is designed by hangfire, I recommend you to use the second database for Hangfire server.

    If you want to use DBMigrator to create both an application database and a hangfire database, you need to do some extra things.

    For example:

    private string GetHangfireConnectionString()
    {
        // Server=(LocalDb)\\MSSQLLocalDB;Database={0};Trusted_Connection=True
        var connectionStringFormat = configuration.GetConnectionString("Hangfire");
        // Hangfire
        var dbName = configuration["HangfireDbName"];
    
        using (var connexion = new SqlConnection(string.Format(connectionStringFormat, "master")))
        {
            connexion.Open();
    
            using (var command = new SqlCommand(string.Format(
                @"IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = N'{0}') 
                                create database [{0}];
                  ", dbName), connexion))
            {
                command.ExecuteNonQuery();
            }
        }
    
        return string.Format(connectionStringFormat, dbName);
    }
    
    var connectionString = GetHangfireConnectionString();
    GlobalConfiguration.Configuration.UseSqlServerStorage(connectionString); 
    
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    BTW, I recommend that you install Volo.Abp.BackgroundWorkers.HangFire package in the startup project(.MVC, .HttpApi.Host etc..). this way it does not affect the DbMigrator project.

  • User Avatar
    0
    raif created

    I recommend you to use the second database for Hangfire server.

    Yeah, you are right using separate database for Hangfire most effective solution.

    BTW, I recommend that you install Volo.Abp.BackgroundWorkers.HangFire package in the startup project(.MVC, .HttpApi.Host etc..). this way it does not affect the DbMigrator project

    Noted.

    Cheers

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11