1
1
defmodule Mix.State do
2
2
@ moduledoc false
3
3
@ name __MODULE__
4
+ @ timeout :infinity
4
5
5
6
use GenServer
6
7
7
8
def start_link ( _opts ) do
8
9
GenServer . start_link ( __MODULE__ , :ok , name: @ name )
9
10
end
10
11
11
- @ impl true
12
- def init ( :ok ) do
13
- table = :ets . new ( @ name , [ :public , :set , :named_table , read_concurrency: true ] )
14
-
15
- :ets . insert ( table ,
16
- shell: Mix.Shell.IO ,
17
- env: from_env ( "MIX_ENV" , :dev ) ,
18
- target: from_env ( "MIX_TARGET" , :host ) ,
19
- scm: [ Mix.SCM.Git , Mix.SCM.Path ]
20
- )
21
-
22
- { :ok , table }
23
- end
24
-
25
- defp from_env ( varname , default ) do
26
- case System . get_env ( varname ) do
27
- nil -> default
28
- "" -> default
29
- value -> String . to_atom ( value )
12
+ def lock ( key , fun ) do
13
+ try do
14
+ GenServer . call ( @ name , { :lock , key } , @ timeout )
15
+ fun . ( )
16
+ after
17
+ GenServer . call ( @ name , { :unlock , key } , @ timeout )
30
18
end
31
19
end
32
20
21
+ ## ETS state storage (mutable, not cleared ion tests)
22
+
33
23
def fetch ( key ) do
34
24
case :ets . lookup ( @ name , key ) do
35
25
[ { ^ key , value } ] -> { :ok , value }
@@ -52,6 +42,8 @@ defmodule Mix.State do
52
42
:ets . insert ( @ name , { key , fun . ( :ets . lookup_element ( @ name , key , 2 ) ) } )
53
43
end
54
44
45
+ ## Persistent term cache (persistent, cleared in tests)
46
+
55
47
def read_cache ( key ) do
56
48
:persistent_term . get ( { __MODULE__ , key } , nil )
57
49
end
@@ -70,4 +62,84 @@ defmodule Mix.State do
70
62
:persistent_term . erase ( key )
71
63
end
72
64
end
65
+
66
+ ## Callbacks
67
+
68
+ @ impl true
69
+ def init ( :ok ) do
70
+ table = :ets . new ( @ name , [ :public , :set , :named_table , read_concurrency: true ] )
71
+
72
+ :ets . insert ( table ,
73
+ shell: Mix.Shell.IO ,
74
+ env: from_env ( "MIX_ENV" , :dev ) ,
75
+ target: from_env ( "MIX_TARGET" , :host ) ,
76
+ scm: [ Mix.SCM.Git , Mix.SCM.Path ]
77
+ )
78
+
79
+ { :ok , { % { } , % { } } }
80
+ end
81
+
82
+ defp from_env ( varname , default ) do
83
+ case System . get_env ( varname ) do
84
+ nil -> default
85
+ "" -> default
86
+ value -> String . to_atom ( value )
87
+ end
88
+ end
89
+
90
+ @ impl true
91
+ def handle_call ( { :lock , key } , { pid , _ } = from , { key_to_waiting , pid_to_key } ) do
92
+ key_to_waiting =
93
+ case key_to_waiting do
94
+ % { ^ key => { locked , waiting } } ->
95
+ Map . put ( key_to_waiting , key , { locked , :queue . in ( from , waiting ) } )
96
+
97
+ % { } ->
98
+ go! ( from )
99
+ Map . put ( key_to_waiting , key , { pid , :queue . new ( ) } )
100
+ end
101
+
102
+ ref = Process . monitor ( pid )
103
+ { :noreply , { key_to_waiting , Map . put ( pid_to_key , pid , { key , ref } ) } }
104
+ end
105
+
106
+ @ impl true
107
+ def handle_call ( { :unlock , key } , { pid , _ } , { key_to_waiting , pid_to_key } ) do
108
+ { { ^ key , ref } , pid_to_key } = Map . pop ( pid_to_key , pid )
109
+ Process . demonitor ( ref , [ :flush ] )
110
+ { :reply , :ok , { unlock ( key_to_waiting , pid_to_key , key ) , pid_to_key } }
111
+ end
112
+
113
+ @ impl true
114
+ def handle_info ( { :DOWN , ref , _type , pid , _reason } , { key_to_waiting , pid_to_key } ) do
115
+ { { key , ^ ref } , pid_to_key } = Map . pop ( pid_to_key , pid )
116
+
117
+ key_to_waiting =
118
+ case key_to_waiting do
119
+ % { ^ key => { ^ pid , _ } } ->
120
+ unlock ( key_to_waiting , pid_to_key , key )
121
+
122
+ % { ^ key => { locked , waiting } } ->
123
+ Map . put ( key_to_waiting , key , { locked , List . keydelete ( waiting , pid , 0 ) } )
124
+ end
125
+
126
+ { :noreply , { key_to_waiting , pid_to_key } }
127
+ end
128
+
129
+ defp unlock ( key_to_waiting , pid_to_key , key ) do
130
+ % { ^ key => { _locked , waiting } } = key_to_waiting
131
+
132
+ case :queue . out ( waiting ) do
133
+ { { :value , { pid , _ } = from } , waiting } ->
134
+ # Assert that we still know this PID
135
+ _ = Map . fetch! ( pid_to_key , pid )
136
+ go! ( from )
137
+ Map . put ( key_to_waiting , key , { pid , waiting } )
138
+
139
+ { :empty , _waiting } ->
140
+ Map . delete ( key_to_waiting , key )
141
+ end
142
+ end
143
+
144
+ defp go! ( from ) , do: GenServer . reply ( from , :ok )
73
145
end
0 commit comments