Wrap sass execution in a bash script to address zombie processes (#1) * Simplify path functions * Add wrapper bash script to address zombie processes when --watch is given
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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 375a94e..01a3d99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# CHANGELOG
+## v0.1.1 (Unreleased)
+
+ * Add wrapper script to address zombie processes
+
## v0.1.0 (2021-07-25)
* First release
diff --git a/lib/dart_sass.ex b/lib/dart_sass.ex
index 6e4d158..db2bca7 100644
--- a/lib/dart_sass.ex
+++ b/lib/dart_sass.ex
@@ -90,16 +90,6 @@ defmodule DartSass do
end
@doc """
- Returns the path to the executable.
-
- The executable may not be available if it was not yet installed.
- """
- def bin_path do
- {path, _snapshot} = bin_paths()
- path
- end
-
- @doc """
Returns the path to the executable and optional snapshot.
Depending on your environment, sass may be invoked through a
@@ -130,6 +120,12 @@ defmodule DartSass do
Path.join(Path.dirname(Mix.Project.build_path()), "dart")
end
+ # TODO: Remove when dart-sass will exit when stdin is closed.
+ @doc false
+ def script_path() do
+ Path.join(:code.priv_dir(:dart_sass), "dart_sass.bash")
+ end
+
@doc """
Returns the version of the dart_sass executable.
@@ -148,9 +144,20 @@ defmodule DartSass do
end
defp sass(args) do
- case bin_paths() do
- {sass, nil} -> {sass, args}
- {vm, snapshot} -> {vm, [snapshot] ++ args}
+ {path, args} =
+ case bin_paths() do
+ {sass, nil} -> {sass, args}
+ {vm, snapshot} -> {vm, [snapshot] ++ args}
+ end
+
+ # TODO: Remove when dart-sass will exit when stdin is closed.
+ # Link: https://github.com/sass/dart-sass/pull/1411
+ cond do
+ "--watch" in args and not match?({:win32, _}, :os.type()) ->
+ {script_path(), [path] ++ args}
+
+ true ->
+ {path, args}
end
end
@@ -172,9 +179,9 @@ defmodule DartSass do
stderr_to_stdout: true
]
- {sass_path, args} = sass(args ++ extra_args)
+ {path, args} = sass(args ++ extra_args)
- sass_path
+ path
|> System.cmd(args, opts)
|> elem(1)
end
@@ -210,7 +217,7 @@ defmodule DartSass do
other -> raise "couldn't unpack archive: #{inspect(other)}"
end
- bin_path = DartSass.bin_path()
+ sass_path = DartSass.sass_path()
snapshot_path = DartSass.snapshot_path()
vm_path = DartSass.vm_path()
@@ -224,7 +231,7 @@ defmodule DartSass do
File.cp!(Path.join([tmp_dir, "dart-sass", "src", "sass.snapshot"]), snapshot_path)
_ ->
- File.cp!(Path.join([tmp_dir, "dart-sass", "sass"]), bin_path)
+ File.cp!(Path.join([tmp_dir, "dart-sass", "sass"]), sass_path)
end
end
diff --git a/priv/dart_sass.bash b/priv/dart_sass.bash
new file mode 100755
index 0000000..0f97f90
--- /dev/null
+++ b/priv/dart_sass.bash
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+# This script was taken from the Elixir Port guide. It is used to ensure
+# graceful termination of the `sass` process when it detects that stdin
+# has been closed.
+# Link: https://hexdocs.pm/elixir/Port.html#module-zombie-operating-system-processes
+#
+# This script is required until dart-sass supports listening on stdin and
+# gracefully terminating when stdin is closed. There is currently a PR for
+# this behaviour: https://github.com/sass/dart-sass/pull/1411
+#
+# Start the program in the background
+exec "$@" &
+pid1=$!
+
+# Silence warnings from here on
+exec >/dev/null 2>&1
+
+# Read from stdin in the background and
+# kill running program when stdin closes
+exec 0<&0 $(
+ while read; do :; done
+ kill -KILL $pid1
+) &
+pid2=$!
+
+# Clean up
+wait $pid1
+ret=$?
+kill -KILL $pid2
+exit $ret