11defmodule Mix.State do
22 @ moduledoc false
33 @ name __MODULE__
4+ @ timeout :infinity
45
56 use GenServer
67
78 def start_link ( _opts ) do
89 GenServer . start_link ( __MODULE__ , :ok , name: @ name )
910 end
1011
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 )
3018 end
3119 end
3220
21+ ## ETS state storage (mutable, not cleared ion tests)
22+
3323 def fetch ( key ) do
3424 case :ets . lookup ( @ name , key ) do
3525 [ { ^ key , value } ] -> { :ok , value }
@@ -52,6 +42,8 @@ defmodule Mix.State do
5242 :ets . insert ( @ name , { key , fun . ( :ets . lookup_element ( @ name , key , 2 ) ) } )
5343 end
5444
45+ ## Persistent term cache (persistent, cleared in tests)
46+
5547 def read_cache ( key ) do
5648 :persistent_term . get ( { __MODULE__ , key } , nil )
5749 end
@@ -70,4 +62,84 @@ defmodule Mix.State do
7062 :persistent_term . erase ( key )
7163 end
7264 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 )
73145end
0 commit comments