| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- 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:
- <DOMAIN LOCK>
- \
- <CLASS LOADER LOCK>
- \ \
- <SIMPLE LOCK 1> <SIMPLE LOCK 2>
- 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:
- <enter mutex>
- <check that item is created>
- if (created) {
- <leave mutex>
- return item
- }
- <create item>
- <store it in cache>
- <leave mutex>
- 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.
- <enter mutex>
- <check that item is created>
- <leave mutex>
- if (created) {
- return item
- }
- <create item>
- <enter mutex>
- <check that item is created>
- if (created) {
- /* Another thread already created and stored the same item */
- <free our item>
- <leave mutex>
- return orig item
- }
- else {
- <store item in cache>
- <leave mutex>
- 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
|