简体   繁体   中英

Why this lock does not work in service, called from asp.net?

I inherited an app which has a service class which is called from asp.net application like this:

class PeopleService : IPeopleService
{
    //...
    public void SavePerson(Person person)
    {
        person.UniqueId = _idGenerationService.GetNextId(person);
        //...
        _dbContext.InsertOrUpdate(person);
        _dbContext.Commit();
    }
}

class IdGenerationService : IIdGenerationService
{
    //...
    public string GetNextId(Person person) 
    {
        int nextId = _dbContext.People.Count(x => x.Gender == person.Gender) + 1;
        return string.Format("AB{0}", nextId);
    }
}

The implementation of GetNextId(Person) is not thread safe, and we had a lot of Person objects with duplicated ids, like AB1, AB2, AB2, etc...

The solution was to apply lock to SavePerson(Person) like this:

class PeopleService : IPeopleService
{
    private static readonly Object ReadIdLock = new Object();
    //...
    public void SavePerson(Person person)
    {
        lock(ReadIdLock) 
        {
            person.UniqueId = _idGenerationService.GetNextId(person);
            //...
            _dbContext.InsertOrUpdate(person);
            _dbContext.Commit();
        }
    }
}

Now we tried to test the code by hitting the Save button simultaneously in asp.net app. But the fix didn't work! Interestingly, only the first records seem to be duplicates, like AB1, AB1, AB2, AB3...

How it's possible that multiple requests have accessed the locked statements? Should I use Mutex instead?

(Please note this example is not a production code, it's a simplified code to convey the problem that we are having. Also, small point, we are using StructureMap as DI).

Since the lock is a static variable there's only one such lock object per appdomain which excludes a lot of possible bugs. Here's the only reason for this problem I can think of:

Your lock only works in one appdomain. ASP.NET can run apps in multiple app domains at the same time for example when recycling, deploying or in web garden mode. Also, you might have multiple servers.

Best fix is to make _idGenerationService.GetNextId thread-safe. Probably, you need to apply proper locking to the database query that underlies this method.

Also, you lock region is too long. You also cover the insert into the database. This starves concurrency and can cause distributed deadlocks.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM