Skip to content

Commit 7cc21ea

Browse files
committed
Convert Mix.State to a process and use better storage
* Use ETS instead of going through an Agent * Use persistent_term for long term storage
1 parent 06f5f31 commit 7cc21ea

File tree

5 files changed

+63
-80
lines changed

5 files changed

+63
-80
lines changed

lib/mix/lib/mix/scm.ex

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,21 +128,20 @@ defmodule Mix.SCM do
128128
until a matching one is found.
129129
"""
130130
def available do
131-
{:ok, scm} = Mix.State.fetch(:scm)
132-
scm
131+
Mix.State.get(:scm)
133132
end
134133

135134
@doc """
136135
Prepends the given SCM module to the list of available SCMs.
137136
"""
138137
def prepend(mod) when is_atom(mod) do
139-
Mix.State.prepend_scm(mod)
138+
Mix.State.update(:scm, &[mod | List.delete(&1, mod)])
140139
end
141140

142141
@doc """
143142
Appends the given SCM module to the list of available SCMs.
144143
"""
145144
def append(mod) when is_atom(mod) do
146-
Mix.State.append_scm(mod)
145+
Mix.State.update(:scm, &(List.delete(&1, mod) ++ [mod]))
147146
end
148147
end

lib/mix/lib/mix/scm/git.ex

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ defmodule Mix.SCM.Git do
6767

6868
@impl true
6969
def lock_status(opts) do
70-
assert_git!()
7170
lock = opts[:lock]
7271

7372
cond do
@@ -102,7 +101,6 @@ defmodule Mix.SCM.Git do
102101

103102
@impl true
104103
def checkout(opts) do
105-
assert_git!()
106104
path = opts[:checkout]
107105
File.rm_rf!(path)
108106
File.mkdir_p!(opts[:dest])
@@ -116,7 +114,6 @@ defmodule Mix.SCM.Git do
116114

117115
@impl true
118116
def update(opts) do
119-
assert_git!()
120117
path = opts[:checkout]
121118
File.cd!(path, fn -> checkout(path, opts) end)
122119
end
@@ -286,7 +283,17 @@ defmodule Mix.SCM.Git do
286283
defp git!(args, into \\ default_into()) do
287284
opts = cmd_opts(into: into, stderr_to_stdout: true)
288285

289-
case System.cmd("git", args, opts) do
286+
try do
287+
System.cmd("git", args, opts)
288+
catch
289+
:error, :enoent ->
290+
Mix.raise(
291+
"Error fetching/updating Git repository: the \"git\" " <>
292+
"executable is not available in your PATH. Please install " <>
293+
"Git on this machine or pass --no-deps-check if you want to " <>
294+
"run a previously built application on a system without Git."
295+
)
296+
else
290297
{response, 0} ->
291298
response
292299

@@ -305,25 +312,18 @@ defmodule Mix.SCM.Git do
305312
end
306313
end
307314

308-
defp assert_git! do
309-
case Mix.State.fetch(:git_available) do
310-
{:ok, true} ->
311-
:ok
312-
313-
:error ->
314-
if System.find_executable("git") do
315-
Mix.State.put(:git_available, true)
316-
else
317-
Mix.raise(
318-
"Error fetching/updating Git repository: the \"git\" " <>
319-
"executable is not available in your PATH. Please install " <>
320-
"Git on this machine or pass --no-deps-check if you want to " <>
321-
"run a previously built application on a system without Git."
322-
)
323-
end
315+
# Attempt to set the current working directory by default.
316+
# This addresses an issue changing the working directory when executing from
317+
# within a secondary node since file I/O is done through the main node.
318+
defp cmd_opts(opts) do
319+
case File.cwd() do
320+
{:ok, cwd} -> Keyword.put(opts, :cd, cwd)
321+
_ -> opts
324322
end
325323
end
326324

325+
# Also invoked by lib/mix/test/test_helper.exs
326+
@doc false
327327
def git_version do
328328
case Mix.State.fetch(:git_version) do
329329
{:ok, version} ->
@@ -333,7 +333,7 @@ defmodule Mix.SCM.Git do
333333
version =
334334
["--version"]
335335
|> git!("")
336-
|> parse_version
336+
|> parse_version()
337337

338338
Mix.State.put(:git_version, version)
339339
version
@@ -352,14 +352,4 @@ defmodule Mix.SCM.Git do
352352
{int, _} = Integer.parse(string)
353353
int
354354
end
355-
356-
# Attempt to set the current working directory by default.
357-
# This addresses an issue changing the working directory when executing from
358-
# within a secondary node since file I/O is done through the main node.
359-
defp cmd_opts(opts) do
360-
case File.cwd() do
361-
{:ok, cwd} -> Keyword.put(opts, :cd, cwd)
362-
_ -> opts
363-
end
364-
end
365355
end

lib/mix/lib/mix/state.ex

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
defmodule Mix.State do
22
@moduledoc false
33
@name __MODULE__
4-
@timeout :infinity
54

6-
use Agent
5+
use GenServer
76

87
def start_link(_opts) do
9-
Agent.start_link(__MODULE__, :init, [], name: @name)
8+
GenServer.start_link(__MODULE__, :ok, name: @name)
109
end
1110

12-
def init() do
13-
%{
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,
1416
shell: Mix.Shell.IO,
1517
env: from_env("MIX_ENV", :dev),
1618
target: from_env("MIX_TARGET", :host),
17-
scm: [Mix.SCM.Git, Mix.SCM.Path],
18-
cache: :ets.new(@name, [:public, :set, :named_table, read_concurrency: true])
19-
}
19+
scm: [Mix.SCM.Git, Mix.SCM.Path]
20+
)
21+
22+
{:ok, table}
2023
end
2124

2225
defp from_env(varname, default) do
@@ -28,50 +31,43 @@ defmodule Mix.State do
2831
end
2932

3033
def fetch(key) do
31-
Agent.get(@name, Map, :fetch, [key], @timeout)
34+
case :ets.lookup(@name, key) do
35+
[{^key, value}] -> {:ok, value}
36+
[] -> :error
37+
end
3238
end
3339

3440
def get(key, default \\ nil) do
35-
Agent.get(@name, Map, :get, [key, default], @timeout)
41+
case :ets.lookup(@name, key) do
42+
[{^key, value}] -> value
43+
[] -> default
44+
end
3645
end
3746

3847
def put(key, value) do
39-
Agent.update(@name, Map, :put, [key, value], @timeout)
40-
end
41-
42-
def prepend_scm(value) do
43-
Agent.update(
44-
@name,
45-
fn state -> update_in(state.scm, &[value | List.delete(&1, value)]) end,
46-
@timeout
47-
)
48+
:ets.insert(@name, {key, value})
4849
end
4950

50-
def append_scm(value) do
51-
Agent.update(
52-
@name,
53-
fn state -> update_in(state.scm, &(List.delete(&1, value) ++ [value])) end,
54-
@timeout
55-
)
51+
def update(key, fun) do
52+
:ets.insert(@name, {key, fun.(:ets.lookup_element(@name, key, 2))})
5653
end
5754

5855
def read_cache(key) do
59-
case :ets.lookup(@name, key) do
60-
[{^key, value}] -> value
61-
[] -> nil
62-
end
56+
:persistent_term.get({__MODULE__, key}, nil)
6357
end
6458

6559
def write_cache(key, value) do
66-
:ets.insert(@name, {key, value})
60+
:persistent_term.put({__MODULE__, key}, value)
6761
value
6862
end
6963

7064
def delete_cache(key) do
71-
:ets.delete(@name, key)
65+
:persistent_term.erase({__MODULE__, key})
7266
end
7367

7468
def clear_cache do
75-
:ets.delete_all_objects(@name)
69+
for {{__MODULE__, _} = key, _value} <- :persistent_term.get() do
70+
:persistent_term.erase(key)
71+
end
7672
end
7773
end

lib/mix/lib/mix/tasks_server.ex

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
11
defmodule Mix.TasksServer do
22
@moduledoc false
33
@name __MODULE__
4-
@timeout :infinity
54

65
use Agent
76

87
def start_link(_opts) do
9-
Agent.start_link(fn -> %{} end, name: @name)
8+
Agent.start_link(
9+
fn -> :ets.new(@name, [:public, :set, :named_table]) end,
10+
name: @name
11+
)
1012
end
1113

1214
def run(tuple) do
13-
Agent.get_and_update(
14-
@name,
15-
fn set -> {not Map.has_key?(set, tuple), Map.put(set, tuple, true)} end,
16-
@timeout
17-
)
15+
:ets.insert_new(@name, {tuple})
1816
end
1917

2018
def put(tuple) do
21-
Agent.update(@name, &Map.put(&1, tuple, true), @timeout)
19+
:ets.insert(@name, {tuple})
2220
end
2321

2422
def get(tuple) do
25-
Agent.get(@name, &Map.get(&1, tuple), @timeout)
23+
:ets.member(@name, tuple)
2624
end
2725

2826
def delete_many(many) do
29-
Agent.update(@name, &Map.drop(&1, many), @timeout)
27+
Enum.each(many, &:ets.delete(@name, &1))
3028
end
3129

3230
def clear() do
33-
Agent.update(@name, fn _ -> %{} end, @timeout)
31+
:ets.delete_all_objects(@name)
3432
end
3533
end

lib/mix/test/mix/task_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ defmodule Mix.TaskTest do
9494
""")
9595

9696
# Clean up the tasks and update task
97-
Mix.TasksServer.clear()
97+
Mix.Task.clear()
9898

9999
# Task was found from deps loadpaths
100100
assert Mix.Task.run("task_hello") == "Hello World v1"
@@ -103,7 +103,7 @@ defmodule Mix.TaskTest do
103103
assert Mix.Task.run("compile") != :noop
104104

105105
# Clean up the tasks and update task
106-
Mix.TasksServer.clear()
106+
Mix.Task.clear()
107107

108108
File.write!("custom/raw_repo/lib/task_hello.ex", """
109109
defmodule Mix.Tasks.TaskHello do

0 commit comments

Comments
 (0)