1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2017-2020. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21-module(lcnt_SUITE). 22 23-include_lib("common_test/include/ct.hrl"). 24 25-export( 26 [all/0, suite/0, 27 init_per_suite/1, end_per_suite/1]). 28 29-export( 30 [toggle_lock_counting/1, error_on_invalid_category/1, preserve_locks/1, 31 registered_processes/1, registered_db_tables/1]). 32 33suite() -> 34 [{ct_hooks,[ts_install_cth]}, 35 {timetrap, {seconds, 10}}]. 36 37all() -> 38 [toggle_lock_counting, error_on_invalid_category, preserve_locks, 39 registered_processes, registered_db_tables]. 40 41init_per_suite(Config) -> 42 case erlang:system_info(lock_counting) of 43 true -> 44 %% The tests will run straight over these properties, so we have to 45 %% preserve them to avoid tainting the other tests. 46 OldCopySave = erts_debug:lcnt_control(copy_save), 47 OldMask = erts_debug:lcnt_control(mask), 48 [{lcnt_SUITE, {OldCopySave, OldMask}} | Config]; 49 _ -> 50 {skip, "Lock counting is not enabled"} 51 end. 52 53end_per_suite(Config) -> 54 {OldCopySave, OldMask} = proplists:get_value(lcnt_SUITE, Config), 55 56 erts_debug:lcnt_control(copy_save, OldCopySave), 57 OldCopySave = erts_debug:lcnt_control(copy_save), 58 59 erts_debug:lcnt_control(mask, OldMask), 60 OldMask = erts_debug:lcnt_control(mask), 61 62 erts_debug:lcnt_clear(), 63 ok. 64 65disable_lock_counting() -> 66 ok = erts_debug:lcnt_control(copy_save, false), 67 ok = erts_debug:lcnt_control(mask, []), 68 ok = erts_debug:lcnt_clear(), 69 70 %% Sanity check. 71 false = erts_debug:lcnt_control(copy_save), 72 [] = erts_debug:lcnt_control(mask), 73 74 %% The above commands rely on some lazy operations, so we'll have to wait 75 %% for the list to clear. 76 ok = wait_for_empty_lock_list(). 77 78wait_for_empty_lock_list() -> 79 wait_for_empty_lock_list(10). 80wait_for_empty_lock_list(Tries) when Tries > 0 -> 81 try_flush_cleanup_ops(), 82 [{duration, _}, {locks, Locks}] = erts_debug:lcnt_collect(), 83 case remove_untoggleable_locks(Locks) of 84 [] -> 85 ok; 86 _ -> 87 timer:sleep(50), 88 wait_for_empty_lock_list(Tries - 1) 89 end; 90wait_for_empty_lock_list(0) -> 91 [{duration, _}, {locks, Locks0}] = erts_debug:lcnt_collect(), 92 Locks = remove_untoggleable_locks(Locks0), 93 ct:fail("Lock list failed to clear after disabling lock counting.~n\t~p", 94 [Locks]). 95 96%% Queue up a lot of thread progress cleanup ops in a vain attempt to 97%% flush the lock list. 98try_flush_cleanup_ops() -> 99 false = lists:member(process, erts_debug:lcnt_control(mask)), 100 [spawn(fun() -> ok end) || _ <- lists:seq(1, 1000)]. 101 102%% 103%% Test cases 104%% 105 106toggle_lock_counting(Config) when is_list(Config) -> 107 ok = disable_lock_counting(), 108 109 Categories = 110 [allocator, db, debug, distribution, generic, io, process, scheduler], 111 lists:foreach( 112 fun(Category) -> 113 Locks = get_lock_info_for(Category), 114 if 115 Locks =/= [] -> 116 disable_lock_counting(); 117 Locks =:= [] -> 118 ct:fail("Failed to toggle ~p locks.", [Category]) 119 end 120 end, Categories). 121 122get_lock_info_for(Categories) when is_list(Categories) -> 123 ok = erts_debug:lcnt_control(mask, Categories), 124 [{duration, _}, {locks, Locks}] = erts_debug:lcnt_collect(), 125 remove_untoggleable_locks(Locks); 126 127get_lock_info_for(Category) when is_atom(Category) -> 128 get_lock_info_for([Category]). 129 130preserve_locks(Config) when is_list(Config) -> 131 ok = disable_lock_counting(), 132 133 erts_debug:lcnt_control(mask, [process]), 134 135 erts_debug:lcnt_control(copy_save, true), 136 [spawn(fun() -> ok end) || _ <- lists:seq(1, 1000)], 137 138 %% Wait for the processes to be fully destroyed before disabling copy_save, 139 %% then remove all active locks from the list. (There's no foolproof method 140 %% to do this; sleeping before/after is the best way we have) 141 timer:sleep(500), 142 143 erts_debug:lcnt_control(copy_save, false), 144 erts_debug:lcnt_control(mask, []), 145 146 try_flush_cleanup_ops(), 147 timer:sleep(500), 148 149 case erts_debug:lcnt_collect() of 150 [{duration, _}, {locks, Locks}] when length(Locks) > 0 -> 151 ct:pal("Preserved ~p locks.", [length(Locks)]); 152 [{duration, _}, {locks, []}] -> 153 ct:fail("copy_save didn't preserve any locks.") 154 end. 155 156error_on_invalid_category(Config) when is_list(Config) -> 157 ok = disable_lock_counting(), 158 159 {error, badarg, q_invalid} = erts_debug:lcnt_control(mask, [q_invalid]), 160 ok. 161 162registered_processes(Config) when is_list(Config) -> 163 ok = disable_lock_counting(), 164 165 %% There ought to be at least one registered process (init/code_server) 166 erts_debug:lcnt_control(mask, [process]), 167 [_, {locks, ProcLocks}] = erts_debug:lcnt_collect(), 168 true = lists:any( 169 fun 170 ({proc_main, RegName, _, _}) when is_atom(RegName) -> true; 171 (_Lock) -> false 172 end, ProcLocks), 173 ok. 174 175registered_db_tables(Config) when is_list(Config) -> 176 ok = disable_lock_counting(), 177 178 %% There ought to be at least one registered table (code) 179 erts_debug:lcnt_control(mask, [db]), 180 [_, {locks, DbLocks}] = erts_debug:lcnt_collect(), 181 true = lists:any( 182 fun 183 ({db_tab, RegName, _, _}) when is_atom(RegName) -> true; 184 (_Lock) -> false 185 end, DbLocks), 186 ok. 187 188%% Not all locks can be toggled on or off due to technical limitations, so we 189%% need to filter them out when checking whether we successfully disabled lock 190%% counting. 191remove_untoggleable_locks([]) -> 192 []; 193remove_untoggleable_locks([{resource_monitors, _, _, _} | T]) -> 194 remove_untoggleable_locks(T); 195remove_untoggleable_locks([{'esock[gcnt]', _, _, _} | T]) -> 196 %% Global lock used by socket NIF 197 remove_untoggleable_locks(T); 198remove_untoggleable_locks([H | T]) -> 199 [H | remove_untoggleable_locks(T)]. 200