@@ -588,14 +588,36 @@ defmodule Registry do
588
588
def keys ( registry , pid ) when is_atom ( registry ) and is_pid ( pid ) do
589
589
{ kind , partitions , _ , pid_ets , _ } = info! ( registry )
590
590
{ _ , 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 )
592
601
593
602
cond do
594
603
kind == :unique -> Enum . uniq ( keys )
595
604
true -> keys
596
605
end
597
606
end
598
607
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
+
599
621
@ doc """
600
622
Unregisters all entries for the given `key` associated to the current
601
623
process in `registry`.
@@ -653,6 +675,95 @@ defmodule Registry do
653
675
:ok
654
676
end
655
677
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
+
656
767
@ doc """
657
768
Registers the current process under the given `key` in `registry`.
658
769
@@ -977,6 +1088,12 @@ defmodule Registry.Partition do
977
1088
def handle_info ( { :EXIT , pid , _reason } , ets ) do
978
1089
entries = :ets . take ( ets , pid )
979
1090
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
+
980
1097
try do
981
1098
:ets . match_delete ( key_ets , { key , { pid , :_ } } )
982
1099
catch
0 commit comments