/
NOTES
113 lines (87 loc) · 3.61 KB
/
NOTES
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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