Skip to content

AccessViolation in SQLiteDistributedLock #79

Description

@ax0l0tl

Version 0.4.2, .net 8:

We encounter AccessViolationExceptions in SQLiteDistributedLock.UpdateExpiration. Mainly in debug mode, but we also encouter problems with the release version, were hangfire storage gets corrupted which might be caused by this exception.

It looks like two threads are accessing the same SQLiteConnection instance at the same time. First one is the heartbeat timer from SQLiteDistributedLock the second one in our case is RecurringJobScheduler tryiing to Release another DistributedLock.

Image

Image

Thread 29892 and thread 34292 use the same connection instance.

I think this can always happen, because the connection is shared between the consumer of the lock an the lock itself.

Can be reproduced with the following test:

[Fact]
//this will result in an access violation exception caused by multithreaded usage of the same connection
//(distributed lock heartbeat is trying to update expiry on owners connection).
public void Use_Connection_When_Heartbeat_Fires()
{
    UseConnection(database =>
    {
        using var slock = SQLiteDistributedLock.Acquire("resource1", TimeSpan.FromSeconds(10), database, new SQLiteStorageOptions
        {
            DistributedLockLifetime = TimeSpan.FromSeconds(5) //heartbeat every second
        });        

        var start = DateTimeOffset.Now;
        //generate some pressure on connection to increase probability of race condition with heartbeat timer thread
        while (DateTimeOffset.Now - start < TimeSpan.FromMinutes(1)) 
        {
            database.Database.Insert(new JobParameter()
            {
                ExpireAt = start.AddSeconds(15).UtcDateTime,
                JobId = 13,
                Name = "MyParameter",
                Value = "MyValue"
            });
        }
    });
}

Using SQLiteOpenFlags.FullMutex is a workaround for the problem, but I think that should not be the final solution.

UseSQLiteStorage(
	new SQLiteDbConnectionFactory(() => new SQLiteConnection(
		databasePath: databasePath,
		openFlags: SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.FullMutex,
		storeDateTimeAsTicks: true
		)
		{
			BusyTimeout = TimeSpan.FromSeconds(value: 10)
		}
	)
);

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions