@@ -77,22 +77,6 @@ defmodule Realtime.RateCounter do
7777 )
7878 end
7979
80- @ spec stop ( term ( ) ) :: :ok
81- def stop ( tenant_id ) do
82- keys =
83- Registry . select ( Realtime.Registry.Unique , [
84- { { { :"$1" , :_ , { :_ , :_ , :"$2" } } , :"$3" , :_ } , [ { :== , :"$1" , __MODULE__ } , { :== , :"$2" , tenant_id } ] , [ :"$_" ] }
85- ] )
86-
87- Enum . each ( keys , fn { { _ , _ , key } , { pid , _ } } ->
88- if Process . alive? ( pid ) , do: GenServer . stop ( pid )
89- GenCounter . delete ( key )
90- Cachex . del! ( @ cache , key )
91- end )
92-
93- :ok
94- end
95-
9680 @ doc """
9781 Starts a new RateCounter under a DynamicSupervisor
9882 """
@@ -108,6 +92,10 @@ defmodule Realtime.RateCounter do
10892 } )
10993 end
11094
95+ @ doc "Publish an update to the RateCounter with the given id"
96+ @ spec publish_update ( term ( ) ) :: :ok
97+ def publish_update ( id ) , do: Phoenix.PubSub . broadcast ( Realtime.PubSub , update_topic ( id ) , :update )
98+
11199 @ doc """
112100 Gets the state of the RateCounter.
113101
@@ -136,6 +124,8 @@ defmodule Realtime.RateCounter do
136124 end
137125 end
138126
127+ defp update_topic ( id ) , do: "rate_counter:#{ inspect ( id ) } "
128+
139129 @ impl true
140130 def init ( args ) do
141131 id = Keyword . fetch! ( args , :id )
@@ -151,6 +141,8 @@ defmodule Realtime.RateCounter do
151141 # a RateCounter running to calculate avg and buckets
152142 GenCounter . reset ( id )
153143
144+ :ok = Phoenix.PubSub . subscribe ( Realtime.PubSub , update_topic ( id ) )
145+
154146 telemetry =
155147 if telem_opts do
156148 Logger . metadata ( telem_opts . metadata )
@@ -228,30 +220,41 @@ defmodule Realtime.RateCounter do
228220 { :noreply , state }
229221 end
230222
231- @ impl true
232223 def handle_info ( :idle_shutdown , state ) do
233224 if Enum . all? ( state . bucket , & ( & 1 == 0 ) ) do
234225 # All the buckets are empty, so we can assume this RateCounter has not been useful recently
235226 Logger . warning ( "#{ __MODULE__ } idle_shutdown reached for: #{ inspect ( state . id ) } " )
236- GenCounter . delete ( state . id )
237- # We are expiring in the near future instead of deleting so that
238- # The process dies before the cache information disappears
239- # If we were using Cachex.delete instead then the following rare scenario would be possible:
240- # * RateCounter.get/2 is called;
241- # * Cache was deleted but the process has not stopped yet;
242- # * RateCounter.get/2 will then try to start a new RateCounter but the supervisor will return :already_started;
243- # * Process finally stops;
244- # * The cache is still empty because no new process was started causing an error
245-
246- Cachex . expire ( @ cache , state . id , :timer . seconds ( 1 ) )
247- { :stop , :normal , state }
227+ shutdown ( state )
248228 else
249229 Process . cancel_timer ( state . idle_shutdown_ref )
250230 idle_shutdown_ref = shutdown_after ( state . idle_shutdown )
251231 { :noreply , % { state | idle_shutdown_ref: idle_shutdown_ref } }
252232 end
253233 end
254234
235+ def handle_info ( :update , state ) do
236+ # When we get an update message we shutdown so that this RateCounter
237+ # can be restarted with new parameters
238+ shutdown ( state )
239+ end
240+
241+ def handle_info ( _ , state ) , do: { :noreply , state }
242+
243+ defp shutdown ( state ) do
244+ GenCounter . delete ( state . id )
245+ # We are expiring in the near future instead of deleting so that
246+ # The process dies before the cache information disappears
247+ # If we were using Cachex.delete instead then the following rare scenario would be possible:
248+ # * RateCounter.get/2 is called;
249+ # * Cache was deleted but the process has not stopped yet;
250+ # * RateCounter.get/2 will then try to start a new RateCounter but the supervisor will return :already_started;
251+ # * Process finally stops;
252+ # * The cache is still empty because no new process was started causing an error
253+
254+ Cachex . expire ( @ cache , state . id , :timer . seconds ( 1 ) )
255+ { :stop , :normal , state }
256+ end
257+
255258 defp maybe_trigger_limit ( % { limit: % { log: false } } = state ) , do: state
256259
257260 defp maybe_trigger_limit ( % { limit: % { triggered: true , measurement: measurement } } = state ) do
0 commit comments