@@ -588,14 +588,36 @@ defmodule Registry do
588588 def keys ( registry , pid ) when is_atom ( registry ) and is_pid ( pid ) do
589589 { kind , partitions , _ , pid_ets , _ } = info! ( registry )
590590 { _ , pid_ets } = pid_ets || pid_ets! ( registry , pid , partitions )
591- keys = safe_lookup_second ( pid_ets , pid )
591+
592+ keys = try do
593+ spec = [ { { pid , :'$1' , :'$2' } , [ ] , [ { { :'$1' , :'$2' } } ] } ]
594+ :ets . select ( pid_ets , spec )
595+ catch
596+ :error , :badarg -> [ ]
597+ end
598+
599+ # Handle the possibility of fake keys
600+ keys = gather_keys ( keys , [ ] , false )
592601
593602 cond do
594603 kind == :unique -> Enum . uniq ( keys )
595604 true -> keys
596605 end
597606 end
598607
608+ defp gather_keys ( [ { key , { _ , remaining } } | rest ] , acc , _fake ) do
609+ gather_keys ( rest , [ key | acc ] , { key , remaining } )
610+ end
611+ defp gather_keys ( [ { key , _ } | rest ] , acc , fake ) do
612+ gather_keys ( rest , [ key | acc ] , fake )
613+ end
614+ defp gather_keys ( [ ] , acc , { key , remaining } ) do
615+ List . duplicate ( key , remaining ) ++ Enum . reject ( acc , & & 1 === key )
616+ end
617+ defp gather_keys ( [ ] , acc , false ) do
618+ acc
619+ end
620+
599621 @ doc """
600622 Unregisters all entries for the given `key` associated to the current
601623 process in `registry`.
@@ -653,6 +675,95 @@ defmodule Registry do
653675 :ok
654676 end
655677
678+ @ doc """
679+ Unregister entries for a given key matching a pattern.
680+
681+ ## Examples
682+
683+ For unique registries it can be used to conditionally unregister a key on
684+ the basis of whether or not it matches a particular value.
685+
686+ iex> Registry.start_link(:unique, Registry.UniqueUnregisterMatchTest)
687+ iex> Registry.register(Registry.UniqueUnregisterMatchTest, "hello", :world)
688+ iex> Registry.keys(Registry.UniqueUnregisterMatchTest, self())
689+ ["hello"]
690+ iex> Registry.unregister_match(Registry.UniqueUnregisterMatchTest, "hello", :foo)
691+ :ok
692+ iex> Registry.keys(Registry.UniqueUnregisterMatchTest, self())
693+ ["hello"]
694+ iex> Registry.unregister_match(Registry.UniqueUnregisterMatchTest, "hello", :world)
695+ :ok
696+ iex> Registry.keys(Registry.UniqueUnregisterMatchTest, self())
697+ []
698+
699+ For duplicate registries:
700+
701+ iex> Registry.start_link(:duplicate, Registry.DuplicateUnregisterMatchTest)
702+ iex> Registry.register(Registry.DuplicateUnregisterMatchTest, "hello", :world_a)
703+ iex> Registry.register(Registry.DuplicateUnregisterMatchTest, "hello", :world_b)
704+ iex> Registry.register(Registry.DuplicateUnregisterMatchTest, "hello", :world_c)
705+ iex> Registry.keys(Registry.DuplicateUnregisterMatchTest, self())
706+ ["hello", "hello", "hello"]
707+ iex> Registry.unregister_match(Registry.DuplicateUnregisterMatchTest, "hello", :world_a)
708+ :ok
709+ iex> Registry.keys(Registry.DuplicateUnregisterMatchTest, self())
710+ ["hello", "hello"]
711+ iex> Registry.lookup(Registry.DuplicateUnregisterMatchTest, "hello")
712+ [{self(), :world_b}, {self(), :world_c}]
713+ """
714+ def unregister_match ( registry , key , pattern , guards \\ [ ] ) when is_list ( guards ) do
715+ self = self ( )
716+
717+ { kind , partitions , key_ets , pid_ets , listeners } = info! ( registry )
718+ { key_partition , pid_partition } = partitions ( kind , key , self , partitions )
719+ key_ets = key_ets || key_ets! ( registry , key_partition )
720+ { pid_server , pid_ets } = pid_ets || pid_ets! ( registry , pid_partition )
721+
722+ # Remove first from the key_ets because in case of crashes
723+ # the pid_ets will still be able to clean up. The last step is
724+ # to clean if we have no more entries.
725+
726+ # Here we want to count all entries for this pid under this key, regardless
727+ # of pattern.
728+ underscore_guard = { :"=:=" , { :element , 1 , :"$_" } , { :const , key } }
729+ total_spec = [ { { :_ , { self , :_ } } , [ underscore_guard ] , [ true ] } ]
730+ total = :ets . select_count ( key_ets , total_spec )
731+
732+ # We only want to delete things that match the pattern
733+ delete_spec = [ { { :_ , { self , pattern } } , [ underscore_guard | guards ] , [ true ] } ]
734+ case :ets . select_delete ( key_ets , delete_spec ) do
735+ # We deleted everything, we can just delete the object
736+ ^ total ->
737+ true = :ets . delete_object ( pid_ets , { self , key , key_ets } )
738+
739+ unlink_if_unregistered ( pid_server , pid_ets , self )
740+
741+ for listener <- listeners do
742+ Kernel . send ( listener , { :unregister , registry , key , self } )
743+ end
744+
745+ 0 ->
746+ :ok
747+
748+ deleted ->
749+ # There are still entries remaining for this pid. delete_object/2 with
750+ # duplicate_bag tables will remove every entry, but we only want to
751+ # remove those we have deleted. The solution is to introduce a temp_entry
752+ # that indicates how many keys WILL be remaining after the delete operation.
753+ remaining = total - deleted
754+ temp_entry = { self , key , { key_ets , remaining } }
755+ true = :ets . insert ( pid_ets , temp_entry )
756+ true = :ets . delete_object ( pid_ets , { self , key , key_ets } )
757+ real_keys = List . duplicate ( { self , key , key_ets } , remaining )
758+ true = :ets . insert ( pid_ets , real_keys )
759+ # We've recreated the real remaining key entries, so we can now delete
760+ # our temporary entry.
761+ true = :ets . delete_object ( pid_ets , temp_entry )
762+ end
763+
764+ :ok
765+ end
766+
656767 @ doc """
657768 Registers the current process under the given `key` in `registry`.
658769
@@ -977,6 +1088,12 @@ defmodule Registry.Partition do
9771088 def handle_info ( { :EXIT , pid , _reason } , ets ) do
9781089 entries = :ets . take ( ets , pid )
9791090 for { _pid , key , key_ets } <- entries do
1091+ key_ets = case key_ets do
1092+ # In case the fake key ets is being used. See unregister_match/2.
1093+ { key_ets , _ } -> key_ets
1094+ _ -> key_ets
1095+ end
1096+
9801097 try do
9811098 :ets . match_delete ( key_ets , { key , { pid , :_ } } )
9821099 catch
0 commit comments