1. Thread safety of metadata structures ---------------------------------------- 1.1 Synchronization of read-only data ------------------------------------- Read-only data is data which is not modified after creation, like the actual binary metadata in the metadata tables. There are three kinds of threads with regards to read-only data: - readers - the creator of the data - the destroyer of the data Most threads are readers. - synchronization between readers is not neccesary - synchronization between the writers is done using locks. - synchronization between the readers and the creator is done by not exposing the data to readers before it is fully constructed. - synchronization between the readers and the destroyer: TBD. 1.2 Deadlock prevention plan ---------------------------- Hold locks for the shortest time possible. Avoid calling functions inside locks which might obtain global locks (i.e. locks known outside this module). 1.3 Locks ---------- 1.3.1 Simple locks ------------------ There are a lot of global data structures which can be protected by a 'simple' lock. Simple means: - the lock protects only this data structure or it only protects the data structures in a given C module. An example would be the appdomains list in domain.c - the lock is only held for a short amount of time, and no other lock is acquired inside this simple lock. Thus there is no possibility of deadlock. 1.3.2 The class loader lock --------------------------- This locks is held by the class loading routines in class.c and loader.c. It protects the various caches inside MonoImage which are used by these modules. 1.3.3 The domain lock --------------------- Each appdomain has a lock which protects the per-domain data structures. 1.3.4 The locking hierarchy --------------------------- It is useful to model locks by a locking hierarchy, which is a relation between locks, which is reflexive, transitive, and antisymmetric, in other words, a lattice. If a thread wants to acquire a lock B, while already holding A, it can only do it if A < B. If all threads work this way, then no deadlocks can occur. Our locking hierarchy so far looks like this: \ \ \ 1.4 Notes ---------- Some common scenarios: - if a function needs to access a data structure, then it should lock it itself, and do not count on its caller locking it. So for example, the image->class_cache hash table would be locked by mono_class_get(). - there are lots of places where a runtime data structure is created and stored in a cache. In these places, care must be taken to avoid multiple threads creating the same runtime structure, for example, two threads might call mono_class_get () with the same class name. There are two choices here: if (created) { return item } This is the easiest solution, but it requires holding the lock for the whole time which might create a scalability problem, and could also lead to deadlock. if (created) { return item } if (created) { /* Another thread already created and stored the same item */ return orig item } else { return item } This solution does not present scalability problems, but the created item might be hard to destroy (like a MonoClass). - lazy initialization of hashtables etc. is not thread safe