From 785c5f6f79028024bd3cc22bd8da25af858a98f8 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Thu, 3 Oct 2019 21:05:14 -1000 Subject: [PATCH 1/4] Add throttle to file watcher Ideally we could make use of `Kernel.ParallelCompiler.compile/2` but that would require larger changes to code. --- lib/cortex/file_watcher.ex | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/cortex/file_watcher.ex b/lib/cortex/file_watcher.ex index 2b42935..00012ea 100644 --- a/lib/cortex/file_watcher.ex +++ b/lib/cortex/file_watcher.ex @@ -8,6 +8,11 @@ defmodule Cortex.FileWatcher do use GenServer @watched_dirs ["lib/", "test/", "apps/"] + @throttle_timeout_ms 100 + + defmodule State do + defstruct [:watcher_pid, :file_events, :throttle_timer] + end ########################################## # Public API @@ -26,14 +31,21 @@ defmodule Cortex.FileWatcher do FileSystem.subscribe(watcher_pid) - {:ok, %{watcher_pid: watcher_pid}} + {:ok, %State{watcher_pid: watcher_pid, throttle_timer: nil, file_events: %{}}} end def handle_info( {:file_event, watcher_pid, {path, _events}}, %{watcher_pid: watcher_pid} = state ) do - GenServer.cast(Controller, {:file_changed, file_type(path), path}) + %State{file_events: file_events, throttle_timer: throttle_timer} = state + + unless throttle_timer do + Process.send_after(self(), :throttle_timer_complete, @throttle_timeout_ms) + end + + file_events = Map.put(file_events, path, file_type(path)) + state = %State{state | file_events: file_events} {:noreply, state} end @@ -44,6 +56,16 @@ defmodule Cortex.FileWatcher do {:noreply, state} end + def handle_info(:throttle_timer_complete, state) do + %State{file_events: file_events} = state + + Enum.each(file_events, fn {path, file_type} -> + GenServer.cast(Controller, {:file_changed, file_type, path}) + end) + + {:noreply, %State{state | file_events: %{}}} + end + def handle_info(data, state) do Logger.info("Get unexcepted message #{inspect(data)}, ignore...") From 3889ba0645816eaeab061d57a7977aab915ccb33 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Fri, 4 Oct 2019 07:42:28 -1000 Subject: [PATCH 2/4] Update the history file --- History.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/History.md b/History.md index ad40668..426c883 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +Unreleased +========== + + * feat: File throttling to prevent files being compiled multiple times in quick succession and tests being run multiple times due to one "change". 0.5.0 / 2018-05-01 ================== From da0100f7420e7ab5a862ea99c5a29a5c9e9fac5a Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Thu, 10 Oct 2019 21:19:46 -1000 Subject: [PATCH 3/4] Actually store and clear the throttle timer --- lib/cortex/file_watcher.ex | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/cortex/file_watcher.ex b/lib/cortex/file_watcher.ex index 00012ea..ddabcc3 100644 --- a/lib/cortex/file_watcher.ex +++ b/lib/cortex/file_watcher.ex @@ -40,12 +40,13 @@ defmodule Cortex.FileWatcher do ) do %State{file_events: file_events, throttle_timer: throttle_timer} = state - unless throttle_timer do - Process.send_after(self(), :throttle_timer_complete, @throttle_timeout_ms) - end + throttle_timer = + unless throttle_timer do + Process.send_after(self(), :throttle_timer_complete, @throttle_timeout_ms) + end file_events = Map.put(file_events, path, file_type(path)) - state = %State{state | file_events: file_events} + state = %State{state | file_events: file_events, throttle_timer: throttle_timer} {:noreply, state} end @@ -63,7 +64,7 @@ defmodule Cortex.FileWatcher do GenServer.cast(Controller, {:file_changed, file_type, path}) end) - {:noreply, %State{state | file_events: %{}}} + {:noreply, %State{state | file_events: %{}, throttle_timer: nil}} end def handle_info(data, state) do From 675dd770d1feec09d3ae46347db69f9bec2b9b97 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Sat, 12 Oct 2019 08:26:10 -0700 Subject: [PATCH 4/4] Ensure throttle_timer is tracking is not unset on second event --- lib/cortex/file_watcher.ex | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/cortex/file_watcher.ex b/lib/cortex/file_watcher.ex index ddabcc3..554cde0 100644 --- a/lib/cortex/file_watcher.ex +++ b/lib/cortex/file_watcher.ex @@ -38,15 +38,10 @@ defmodule Cortex.FileWatcher do {:file_event, watcher_pid, {path, _events}}, %{watcher_pid: watcher_pid} = state ) do - %State{file_events: file_events, throttle_timer: throttle_timer} = state - - throttle_timer = - unless throttle_timer do - Process.send_after(self(), :throttle_timer_complete, @throttle_timeout_ms) - end - - file_events = Map.put(file_events, path, file_type(path)) - state = %State{state | file_events: file_events, throttle_timer: throttle_timer} + state = + state + |> maybe_update_throttle_timer() + |> track_file_events(path) {:noreply, state} end @@ -77,6 +72,18 @@ defmodule Cortex.FileWatcher do # Private Helpers ########################################## + defp maybe_update_throttle_timer(%State{throttle_timer: nil} = state) do + throttle_timer = Process.send_after(self(), :throttle_timer_complete, @throttle_timeout_ms) + %State{state | throttle_timer: throttle_timer} + end + + defp maybe_update_throttle_timer(state), do: state + + defp track_file_events(%State{file_events: file_events} = state, path) do + file_events = Map.put(file_events, path, file_type(path)) + %State{state | file_events: file_events} + end + # public only because it is tested @spec file_type(Path.t()) :: :lib | :test | :unknown def file_type(path) do