The Inquisitive Coder – Davy Brion's Blog

Trying to walk that thin line between intelligence and ignorance

Wanna Review My Code?

Posted by Davy Brion on February 23rd, 2010

I just wrote some code, and i’d like your opinion on it.  The thing is, i’m not going to provide any context as to what it does or why certain decisions were made since i know you guys like to be challenged.  You also might want to keep the following in mind when reading it:

  1. it might contain bugs that i’m not aware of
  2. it contains weird parts that were either brainfarts on my part, things i did on purpose to avoid possible issues, or both
  3. it contains at least one bug that i know about, yet don’t care about
  4. i removed the comments that i originally put in it to make things more interesting
  5. i haven’t tested the code yet
  6. i haven’t even executed it yet
  7. i think it’s ok… but i’m not sure

Questions will be answered if you have them (and i’m sure you will) though i can’t make any promises as to how soon i can answer them… I will post a follow-up post to discuss the code in its entirety later on, though i’ll probably wait a few days to do so.

This is the code:

    public class TenantSessionFactoryManager : ITenantSessionFactoryManager

    {

        private readonly ITenantContext tenantContext;

        private readonly ITenantInfoHolder tenantInfoHolder;

        private readonly string mappingAssemblyName;

 

        private readonly object writeLock = new object();

        private Dictionary<Guid, ISessionFactory> sessionFactories;

 

        public TenantSessionFactoryManager(ITenantContext tenantContext, ITenantInfoHolder tenantInfoHolder, string mappingAssemblyName)

        {

            this.tenantContext = tenantContext;

            this.tenantInfoHolder = tenantInfoHolder;

            this.mappingAssemblyName = mappingAssemblyName;

            sessionFactories = new Dictionary<Guid, ISessionFactory>();

        }

 

        public ISession CreateSessionForCurrentTenant()

        {

            var tenantId = tenantContext.CurrentTenantId;

 

            if (!sessionFactories.ContainsKey(tenantId))

            {

                CreateSessionFactoryForCurrentTenant();

            }

 

            return sessionFactories[tenantId].OpenSession();

        }

 

        private void CreateSessionFactoryForCurrentTenant()

        {

            lock (writeLock)

            {

                var tenantId = tenantContext.CurrentTenantId;

 

                if (!sessionFactories.ContainsKey(tenantId))

                {

                    var connectionString = tenantInfoHolder.GetDatabaseConnectionString(tenantId);

 

                    var sessionFactory = new Configuration()

                        .Configure()

                        .AddProperties(new Dictionary<string, string>

                                {

                                       { "connection.connection_string", connectionString },

                                    { "cache.region_prefix", "Tenant_" + tenantId }

                                })

                        .AddAssembly(mappingAssemblyName)

                        .BuildSessionFactory();

 

                    var newDictionary = new Dictionary<Guid, ISessionFactory>(sessionFactories);

                    newDictionary[tenantId] = sessionFactory;

                    sessionFactories = newDictionary;

                }

            }

        }

 

        public void RemoveSessionFactoryForTenant(Guid tenantId)

        {

            if (!sessionFactories.ContainsKey(tenantId))

            {

                return;

            }

 

            lock (writeLock)

            {

                if (!sessionFactories.ContainsKey(tenantId))

                {

                    return;

                }

 

                var sessionFactory = sessionFactories[tenantId];

 

                var newDictionary = new Dictionary<Guid, ISessionFactory>(sessionFactories);

                newDictionary.Remove(tenantId);

                sessionFactories = newDictionary;

 

                sessionFactory.Dispose();

            }

        }

    }

16 Responses to “Wanna Review My Code?”

  1. Ken Egozi Says:

    just a couple of points:
    1. in RemoveSessionFactoryForTenant() you do a “reverse if” – return if the post-condition is ok, otherwise do the work, while in CreateSessionFactoryForCurrentTenant() you go the other way around, which adds an annoying nesting level. I’d reverse the if on CreateSessionFactoryForCurrentTenant() also
    2. I’d also move the first Contains check into CreateSessionFactoryForCurrentTenant(), for two reasons:
    i) will make the main method clearer
    ii) will make it clear that we follow the two-phase check
    3. question: why use an external lock object, and not the .SyncLock of the dictionary?

  2. JeroenH Says:

    Why do you replace the sessionFactories dictionary with a new copy every time you need to add or remove a new SessionFactory?

  3. Awkward Coder Says:

    Isn’t there a race condition between creating & removing a session?

    You could be in the middle of assigning the new session into the dictionary (which you replace everytime) and you request the (new) session be removed but it won’t be cos you’re checking outside of the lock in the remove…

  4. Awkward Coder Says:

    Not that I think the it would happen in reality…

  5. Stefan Says:

    @Akward,

    He repeats the check inside of the lock also…

  6. Awkward Coder Says:

    @Stefan – that’s not the problem…

  7. pho Says:

    my refactorings based on 2 minutes of apathically staring at this code:
    1. rename method CreateSessionFactoryForCurrentTenant() to EnsureSessionFactoryForCurrentTenantExists() because you’re only conditionally creating a new session factory.
    2. remove the contains check in CreateSessionForCurrentTenant(). if you did this to avoid writelocks as much as possible, move the check to the method that does the actual lock. in both cases –> remove the temp variable in the public method
    3. (this one is on the extremely subjective side and purely to improve readability) i’m not really a fan of implicitly adding new key value pairs and prefer using the Add(K,V) method, especially if contexts like this where you know you are adding and are certain a keyvaluepair for a given key does not yet exist.

    you probably have your reasons why this isn’t already the case i guess :-)

    as for the known bug, i guess theoretically it could be that between CreateSessionFactoryForCurrentTenant() and the actual dictionary lookup, the key value pair for the current tenantid is already removed? won’t happen though.

  8. Davy Brion Says:

    @Ken

    if i use the SyncRoot, i have to use it for each access to the dictionary

    @JeroenH

    it’s a copy-on-write implementation… reading from the dictionary is thread-safe, as long as no other thread mutates the state of the dictionary. when a ISessionFactory needs to be added to or removed from the dictionary, i create a new one, add the new sessionfactory to it or remove the old one from it, and then replace the field that holds the dictionary. during this entire operation, any other thread can still safely perform the read since the field that is used by the thread always references a dictionary that is never mutated.

    @Awkward Coder

    sessions aren’t stored in the dictionary… they’re sessionfactories. i don’t _think_ there’s a race condition between creating a new one, and removing one from the dictionary but i definitly could be wrong… creating and removing is both done within a lock on the same object, so i think it’s safe… if i’m wrong there, i’d really appreciate you pointing out what i missed :)

    @Pho

    you are correct about the bug :)
    normally, it shouldn’t occur because by the time we need to remove a tenant, the users of that tenant shouldn’t even have access to the application anymore. and even if they did still have access, they’re no longer a paying customer (hence the removal of the tenant) so any exception they run into isn’t really something i’d be worried about

  9. Awkward Coder Says:

    if one thread is in the middle of executing CreateSessionFactoryForCurrentTenant – inside the lock and hasn’t yet updated the dictionary and another thread executes RemoveSessionFactoryForTenant for the same tenant id then the first if statement outside of the lock statement will return true and exit the method, whilst the other thread completes and updates the dictionary with the new SessionFactory.

    Chances of this actually happening are zero though.

    or I could be complete wrong, it’s been known :)

  10. Davy Brion Says:

    @Awkward Coder

    aha, that is indeed technically possible :)

  11. Awkward Coder Says:

    plus I would change the CreateSessionForCurrentTenant to remove the other race condition…

    private ISession CreateSessionFactoryForCurrentTenant()
    {
    lock (writeLock)
    {
    var tenantId = tenantContext.CurrentTenantId;
    if (sessionFactories.ContainsKey(tenantId))
    {
    return sessionFactories[tenantId].OpenSession();
    }

    var connectionString = tenantInfoHolder.GetDatabaseConnectionString(tenantId);
    var sessionFactory = new Configuration()
    .Configure()
    .AddProperties(new Dictionary
    {

    { \connection.connection_string\, connectionString },

    { \cache.region_prefix\, \Tenant_\ + tenantId }

    })
    .AddAssembly(mappingAssemblyName)
    .BuildSessionFactory();
    var newDictionary = new Dictionary(sessionFactories);
    newDictionary[tenantId] = sessionFactory;
    sessionFactories = newDictionary;

    return sessionFactory;
    }
    }

  12. Davy Brion Says:

    @Awkward

    i think you got something mixed up there because that either returns an ISession or an ISessionFactory ;)

  13. Awkward Coder Says:

    I blame notepad :)

  14. Tiendq Says:

    I will use IDictionary and will not use {} for single IF statement.

  15. Adam Says:

    If CreateSessionForCurrentTenant and RemoveSessionFactoryForTenant execute at the same time with an unreasonable amount of bad luck it’s possible that Dispose gets called on the sessionfactory right before OpenSession.
    I don’t see how that can be avoided without using the same lock for reading the dictionary as well.

  16. Davy Brion Says:

    @Adam

    true… but it also falls under the ” they’re no longer a paying customer so i no longer care about them” category ;)

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>