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(persistent_term_SUITE). 22-include_lib("common_test/include/ct.hrl"). 23 24-export([all/0,suite/0,init_per_suite/1,end_per_suite/1, 25 init_per_testcase/2, end_per_testcase/2, 26 basic/1,purging/1,sharing/1,get_trapping/1, 27 destruction/1, 28 get_all_race/1, 29 info/1,info_trapping/1,killed_while_trapping/1, 30 off_heap_values/1,keys/1,collisions/1, 31 init_restart/1, put_erase_trapping/1, 32 killed_while_trapping_put/1, 33 killed_while_trapping_erase/1, 34 error_info/1, 35 whole_message/1, 36 shared_magic_ref/1, 37 non_message_signal/1]). 38 39%% 40-export([test_init_restart_cmd/1]). 41 42%% Test writing helper 43-export([find_colliding_keys/0]). 44 45 46suite() -> 47 [{ct_hooks,[ts_install_cth]}, 48 {timetrap,{minutes,10}}]. 49 50all() -> 51 [basic,purging,sharing,get_trapping,info,info_trapping, 52 destruction, 53 get_all_race, 54 killed_while_trapping,off_heap_values,keys,collisions, 55 init_restart, put_erase_trapping, killed_while_trapping_put, 56 killed_while_trapping_erase, 57 error_info, 58 whole_message, 59 shared_magic_ref, 60 non_message_signal]. 61 62init_per_suite(Config) -> 63 erts_debug:set_internal_state(available_internal_state, true), 64 %% Put a term in the dict so that we know that the testcases handle 65 %% stray terms left by stdlib or other test suites. 66 persistent_term:put(init_per_suite, {?MODULE}), 67 Config. 68 69end_per_suite(Config) -> 70 persistent_term:erase(init_per_suite), 71 erts_debug:set_internal_state(available_internal_state, false), 72 Config. 73 74init_per_testcase(_, Config) -> 75 Config. 76 77end_per_testcase(_, _Config) -> 78 ok; 79end_per_testcase(get_all_race, _Config) -> 80 get_all_race_cleanup(), 81 ok. 82 83basic(_Config) -> 84 Chk = chk(), 85 N = 777, 86 Seq = lists:seq(1, N), 87 par(2, N, Seq, Chk), 88 seq(3, Seq, Chk), 89 seq(3, Seq, Chk), %Same values. 90 _ = [begin 91 Key = {?MODULE,{key,I}}, 92 true = persistent_term:erase(Key), 93 false = persistent_term:erase(Key), 94 {'EXIT',{badarg,_}} = (catch persistent_term:get(Key)), 95 {not_present,Key} = persistent_term:get(Key, {not_present,Key}) 96 end || I <- Seq], 97 [] = [P || {{?MODULE,_},_}=P <- pget(Chk)], 98 chk(Chk). 99 100par(C, N, Seq, Chk) -> 101 _ = [spawn_link(fun() -> 102 ok = persistent_term:put({?MODULE,{key,I}}, 103 {value,C*I}) 104 end) || I <- Seq], 105 Result = wait(N, Chk), 106 _ = [begin 107 Double = C*I, 108 {{?MODULE,{key,I}},{value,Double}} = Res 109 end || {I,Res} <- lists:zip(Seq, Result)], 110 ok. 111 112seq(C, Seq, Chk) -> 113 _ = [ok = persistent_term:put({?MODULE,{key,I}}, {value,C*I}) || 114 I <- Seq], 115 All = pget(Chk), 116 All = [P || {{?MODULE,_},_}=P <- All], 117 All = [{Key,persistent_term:get(Key)} || {Key,_} <- All], 118 Result = lists:sort(All), 119 _ = [begin 120 Double = C*I, 121 {{?MODULE,{key,I}},{value,Double}} = Res 122 end || {I,Res} <- lists:zip(Seq, Result)], 123 ok. 124 125wait(N, Chk) -> 126 All = [P || {{?MODULE,_},_}=P <- pget(Chk)], 127 case length(All) of 128 N -> 129 All = [{Key,persistent_term:get(Key)} || {Key,_} <- All], 130 lists:sort(All); 131 _ -> 132 receive after 10 -> ok end, 133 wait(N, Chk) 134 end. 135 136%% Make sure that terms that have been erased are copied into all 137%% processes that still hold a pointer to them. 138 139purging(_Config) -> 140 Chk = chk(), 141 do_purging(fun(K) -> persistent_term:put(K, {?MODULE,new}) end, 142 replaced), 143 do_purging(fun persistent_term:erase/1, erased), 144 chk(Chk). 145 146do_purging(Eraser, Type) -> 147 Parent = self(), 148 Key = {?MODULE,?FUNCTION_NAME}, 149 ok = persistent_term:put(Key, {term,[<<"abc",0:777/unit:8>>]}), 150 Ps0 = [spawn_monitor(fun() -> purging_tester(Parent, Key) end) || 151 _ <- lists:seq(1, 50)], 152 Ps = maps:from_list(Ps0), 153 purging_recv(gotten, Ps), 154 Eraser(Key), 155 _ = [P ! {Parent,Type} || P <- maps:keys(Ps)], 156 purging_wait(Ps). 157 158purging_recv(Tag, Ps) when map_size(Ps) > 0 -> 159 receive 160 {Pid,Tag} -> 161 true = is_map_key(Pid, Ps), 162 purging_recv(Tag, maps:remove(Pid, Ps)) 163 end; 164purging_recv(_, _) -> ok. 165 166purging_wait(Ps) when map_size(Ps) > 0 -> 167 receive 168 {'DOWN',Ref,process,Pid,Reason} -> 169 normal = Reason, 170 Ref = map_get(Pid, Ps), 171 purging_wait(maps:remove(Pid, Ps)) 172 end; 173purging_wait(_) -> ok. 174 175purging_tester(Parent, Key) -> 176 Term = persistent_term:get(Key), 177 purging_check_term(Term), 178 0 = erts_debug:size_shared(Term), 179 Parent ! {self(),gotten}, 180 receive 181 {Parent,erased} -> 182 {'EXIT',{badarg,_}} = (catch persistent_term:get(Key)), 183 purging_tester_1(Term, 1); 184 {Parent,replaced} -> 185 {?MODULE,new} = persistent_term:get(Key), 186 purging_tester_1(Term, 1) 187 end. 188 189%% Wait for the term to be copied into this process. 190purging_tester_1(Term, Timeout) -> 191 purging_check_term(Term), 192 receive after Timeout -> ok end, 193 case erts_debug:size_shared(Term) of 194 0 -> 195 case Timeout of 196 1000 -> 197 flush_later_ops(), 198 purging_tester_1(Term, 1); 199 _ -> 200 purging_tester_1(Term, Timeout*10) 201 end; 202 Size -> 203 %% The term has been copied into this process. 204 purging_check_term(Term), 205 Size = erts_debug:size(Term) 206 end. 207 208purging_check_term({term,[<<"abc",0:777/unit:8>>]}) -> 209 ok. 210 211%% Make sure terms are really deallocated when overwritten or erased. 212destruction(Config) -> 213 ok = erts_test_destructor:init(Config), 214 215 NKeys = 100, 216 Keys = lists:seq(0,NKeys-1), 217 [begin 218 V = erts_test_destructor:send(self(), K), 219 persistent_term:put({?MODULE,K}, V) 220 end 221 || K <- Keys], 222 223 %% Erase or overwrite all keys in "random" order. 224 lists:foldl(fun(_, K) -> 225 case erlang:phash2(K) band 1 of 226 0 -> 227 %%io:format("erase key ~p\n", [K]), 228 persistent_term:erase({?MODULE,K}); 229 1 -> 230 %%io:format("replace key ~p\n", [K]), 231 persistent_term:put({?MODULE,K}, value) 232 end, 233 (K + 13) rem NKeys 234 end, 235 17, Keys), 236 237 destruction_1(Keys). 238 239destruction_1(Keys) -> 240 erlang:garbage_collect(), 241 242 %% Receive all destruction messages 243 MsgLst = destruction_recv(length(Keys), [], 2), 244 ok = case lists:sort(MsgLst) of 245 Keys -> 246 ok; 247 _ -> 248 io:format("GOT ~p\n", [MsgLst]), 249 io:format("MISSING ~p\n", [Keys -- MsgLst]), 250 error 251 end, 252 253 %% Cleanup all remaining 254 [persistent_term:erase({?MODULE,K}) || K <- Keys], 255 ok. 256 257destruction_recv(0, Acc, _) -> 258 Acc; 259destruction_recv(N, Acc, Flush) -> 260 receive M -> 261 destruction_recv(N-1, [M | Acc], Flush) 262 after 1000 -> 263 io:format("TIMEOUT. Missing ~p destruction messages.\n", [N]), 264 case Flush of 265 0 -> 266 Acc; 267 _ -> 268 io:format("Try flush last literal area cleanup...\n"), 269 flush_later_ops(), 270 destruction_recv(N, Acc, Flush-1) 271 end 272 end. 273 274%% Both persistent_term itself and erts_literal_are_collector use 275%% erts_schedule_thr_prgr_later_cleanup_op() to schedule purge and deallocation 276%% of literals. To avoid waiting forever on sleeping schedulers we flush 277%% all later ops to make these cleanup jobs go through. 278flush_later_ops() -> 279 try 280 erts_debug:set_internal_state(wait, thread_progress) 281 catch 282 error:system_limit -> 283 ok % already ongoing; called by other process 284 end, 285 ok. 286 287 288%% Test that sharing is preserved when storing terms. 289 290sharing(_Config) -> 291 Chk = chk(), 292 Depth = 10, 293 Size = 2*Depth, 294 Shared = lists:foldl(fun(_, A) -> [A|A] end, 295 [], lists:seq(1, Depth)), 296 Size = erts_debug:size(Shared), 297 Key = {?MODULE,?FUNCTION_NAME}, 298 ok = persistent_term:put(Key, Shared), 299 SharedStored = persistent_term:get(Key), 300 Size = erts_debug:size(SharedStored), 301 0 = erts_debug:size_shared(SharedStored), 302 303 {Pid,Ref} = spawn_monitor(fun() -> 304 Term = persistent_term:get(Key), 305 Size = erts_debug:size(Term), 306 0 = erts_debug:size_shared(Term), 307 true = Term =:= SharedStored 308 end), 309 receive 310 {'DOWN',Ref,process,Pid,normal} -> 311 true = persistent_term:erase(Key), 312 Size = erts_debug:size(SharedStored), 313 chk(Chk) 314 end. 315 316%% Test trapping of persistent_term:get/0. 317 318get_trapping(_Config) -> 319 Chk = chk(), 320 321 %% Assume that the get/0 traps after 4000 iterations 322 %% in a non-debug emulator. 323 N = case test_server:timetrap_scale_factor() of 324 1 -> 10000; 325 _ -> 1000 326 end, 327 spawn_link(fun() -> get_trapping_create(N) end), 328 All = do_get_trapping(N, [], Chk), 329 N = get_trapping_check_result(lists:sort(All), 1), 330 erlang:garbage_collect(), 331 get_trapping_erase(N), 332 chk(Chk). 333 334do_get_trapping(N, Prev, Chk) -> 335 case pget(Chk) of 336 Prev when length(Prev) >= N -> 337 All = [P || {{?MODULE,{get_trapping,_}},_}=P <- Prev], 338 case length(All) of 339 N -> All; 340 _ -> do_get_trapping(N, Prev, Chk) 341 end; 342 New -> 343 receive after 1 -> ok end, 344 do_get_trapping(N, New, Chk) 345 end. 346 347get_trapping_create(0) -> 348 ok; 349get_trapping_create(N) -> 350 ok = persistent_term:put({?MODULE,{get_trapping,N}}, N), 351 get_trapping_create(N-1). 352 353get_trapping_check_result([{{?MODULE,{get_trapping,N}},N}|T], N) -> 354 get_trapping_check_result(T, N+1); 355get_trapping_check_result([], N) -> N-1. 356 357get_trapping_erase(0) -> 358 ok; 359get_trapping_erase(N) -> 360 true = persistent_term:erase({?MODULE,{get_trapping,N}}), 361 get_trapping_erase(N-1). 362 363%% Test retrieving information about persistent terms. 364 365info(_Config) -> 366 Chk = chk(), 367 368 %% White box test of info/0. 369 N = 100, 370 try 371 Overhead = info_literal_area_overhead(), 372 io:format("Overhead = ~p\n", [Overhead]), 373 info_wb(N, Overhead, info_info()) 374 after 375 _ = [_ = persistent_term:erase({?MODULE,I}) || 376 I <- lists:seq(1, N)] 377 end, 378 379 chk(Chk). 380 381%% White box test of persistent_term:info/0. We take into account 382%% that there might already exist persistent terms (created by the 383%% OTP standard libraries), but we assume that they are not 384%% changed during the execution of this test case. 385 386info_wb(0, _, _) -> 387 ok; 388info_wb(N, Overhead, {BaseCount,BaseMemory}) -> 389 Key = {?MODULE,N}, 390 Value = lists:seq(1, N), 391 ok = persistent_term:put(Key, Value), 392 393 %% Calculate the extra memory needed for this term. 394 WordSize = erlang:system_info(wordsize), 395 ExtraMemory = Overhead + 2 * N * WordSize, 396 397 %% Call persistent_term:info/0. 398 {Count,Memory} = info_info(), 399 400 %% There should be one more persistent term. 401 Count = BaseCount + 1, 402 403 %% Verify that the amount of memory is correct. 404 case BaseMemory + ExtraMemory of 405 Memory -> 406 %% Exactly right. The size of the hash table was not changed. 407 ok; 408 Expected -> 409 %% The size of the hash table has been doubled to avoid filling 410 %% the table to more than 50 percent. The previous number 411 %% of entries must have been exactly half the size of the 412 %% hash table. The expected number of extra words added by 413 %% the resizing will be twice that number. 414 ExtraWords = BaseCount * 2, 415 true = ExtraWords * WordSize =:= (Memory - Expected) 416 end, 417 info_wb(N-1, Overhead, {Count,Memory}). 418 419info_info() -> 420 #{count:=Count,memory:=Memory} = persistent_term:info(), 421 true = is_integer(Count) andalso Count >= 0, 422 true = is_integer(Memory) andalso Memory >= 0, 423 {Count,Memory}. 424 425%% Calculate the number of extra bytes needed for storing each term in 426%% the literal, assuming that the key is a tuple of size 2 with 427%% immediate elements. The calculated number is the size of the 428%% ErtsLiteralArea struct excluding the storage for the literal term 429%% itself. 430 431info_literal_area_overhead() -> 432 Key1 = {?MODULE,1}, 433 Key2 = {?MODULE,2}, 434 #{memory:=Mem0} = persistent_term:info(), 435 ok = persistent_term:put(Key1, literal), 436 #{memory:=Mem1} = persistent_term:info(), 437 ok = persistent_term:put(Key2, literal), 438 #{memory:=Mem2} = persistent_term:info(), 439 true = persistent_term:erase(Key1), 440 true = persistent_term:erase(Key2), 441 442 %% The size of the hash table may have doubled when inserting 443 %% one of the keys. To avoiding counting the change in the hash 444 %% table size, take the smaller size increase. 445 min(Mem2-Mem1, Mem1-Mem0). 446 447%% Test trapping of persistent_term:info/0. 448 449info_trapping(_Config) -> 450 Chk = chk(), 451 452 %% Assume that the info/0 traps after 4000 iterations 453 %% in a non-debug emulator. 454 N = case test_server:timetrap_scale_factor() of 455 1 -> 10000; 456 _ -> 1000 457 end, 458 spawn_link(fun() -> info_trapping_create(N) end), 459 All = do_info_trapping(N, 0, Chk), 460 N = info_trapping_check_result(lists:sort(All), 1), 461 erlang:garbage_collect(), 462 info_trapping_erase(N), 463 chk(Chk). 464 465do_info_trapping(N, PrevMem, Chk) -> 466 case info_info() of 467 {M,Mem} when M >= N -> 468 true = Mem >= PrevMem, 469 All = [P || {{?MODULE,{info_trapping,_}},_}=P <- pget(Chk)], 470 case length(All) of 471 N -> All; 472 _ -> do_info_trapping(N, PrevMem, Chk) 473 end; 474 {_,Mem} -> 475 true = Mem >= PrevMem, 476 receive after 1 -> ok end, 477 do_info_trapping(N, Mem, Chk) 478 end. 479 480info_trapping_create(0) -> 481 ok; 482info_trapping_create(N) -> 483 ok = persistent_term:put({?MODULE,{info_trapping,N}}, N), 484 info_trapping_create(N-1). 485 486info_trapping_check_result([{{?MODULE,{info_trapping,N}},N}|T], N) -> 487 info_trapping_check_result(T, N+1); 488info_trapping_check_result([], N) -> N-1. 489 490info_trapping_erase(0) -> 491 ok; 492info_trapping_erase(N) -> 493 true = persistent_term:erase({?MODULE,{info_trapping,N}}), 494 info_trapping_erase(N-1). 495 496%% Test that hash tables are deallocated if a process running 497%% persistent_term:get/0 is killed. 498 499killed_while_trapping(_Config) -> 500 Chk = chk(), 501 N = case test_server:timetrap_scale_factor() of 502 1 -> 20000; 503 _ -> 2000 504 end, 505 kwt_put(N), 506 kwt_spawn(10), 507 kwt_erase(N), 508 chk(Chk). 509 510kwt_put(0) -> 511 ok; 512kwt_put(N) -> 513 ok = persistent_term:put({?MODULE,{kwt,N}}, N), 514 kwt_put(N-1). 515 516kwt_spawn(0) -> 517 ok; 518kwt_spawn(N) -> 519 Pids = [spawn(fun kwt_getter/0) || _ <- lists:seq(1, 20)], 520 erlang:yield(), 521 _ = [exit(Pid, kill) || Pid <- Pids], 522 kwt_spawn(N-1). 523 524kwt_getter() -> 525 _ = persistent_term:get(), 526 kwt_getter(). 527 528kwt_erase(0) -> 529 ok; 530kwt_erase(N) -> 531 true = persistent_term:erase({?MODULE,{kwt,N}}), 532 kwt_erase(N-1). 533 534%% Test storing off heap values (such as ref-counted binaries). 535 536off_heap_values(_Config) -> 537 Chk = chk(), 538 Key = {?MODULE,?FUNCTION_NAME}, 539 Val = {a,list_to_binary(lists:seq(0, 255)),make_ref(),fun() -> ok end}, 540 ok = persistent_term:put(Key, Val), 541 FetchedVal = persistent_term:get(Key), 542 Val = FetchedVal, 543 true = persistent_term:erase(Key), 544 off_heap_values_wait(FetchedVal, Val), 545 chk(Chk). 546 547off_heap_values_wait(FetchedVal, Val) -> 548 case erts_debug:size_shared(FetchedVal) of 549 0 -> 550 Val = FetchedVal, 551 ok; 552 _ -> 553 erlang:yield(), 554 off_heap_values_wait(FetchedVal, Val) 555 end. 556 557%% Test some more data types as keys. Use the module name as a key 558%% to minimize the risk of collision with any key used 559%% by the OTP libraries. 560 561keys(_Config) -> 562 Chk = chk(), 563 do_key(?MODULE), 564 do_key([?MODULE]), 565 do_key(?MODULE_STRING), 566 do_key(list_to_binary(?MODULE_STRING)), 567 chk(Chk). 568 569do_key(Key) -> 570 Val = term_to_binary(Key), 571 ok = persistent_term:put(Key, Val), 572 StoredVal = persistent_term:get(Key), 573 Val = StoredVal, 574 true = persistent_term:erase(Key). 575 576%% Create persistent terms with keys that are known to collide. 577%% Delete them in random order, making sure that all others 578%% terms can still be found. 579 580collisions(_Config) -> 581 Chk = chk(), 582 583 %% Create persistent terms with random keys. 584 Keys = lists:flatten(colliding_keys()), 585 Kvs = [{K,rand:uniform(1000)} || K <- Keys], 586 _ = [ok = persistent_term:put(K, V) || {K,V} <- Kvs], 587 _ = [V = persistent_term:get(K) || {K,V} <- Kvs], 588 589 %% Now delete the persistent terms in random order. 590 collisions_delete(lists:keysort(2, Kvs), Chk), 591 592 chk(Chk). 593 594collisions_delete([{Key,Val}|Kvs], Chk) -> 595 Val = persistent_term:get(Key), 596 true = persistent_term:erase(Key), 597 true = lists:sort(pget(Chk)) =:= lists:sort(Kvs), 598 _ = [V = persistent_term:get(K) || {K,V} <- Kvs], 599 collisions_delete(Kvs, Chk); 600collisions_delete([], _) -> 601 ok. 602 603colliding_keys() -> 604 %% Collisions found by find_colliding_keys() below 605 L = [[77674392,148027], 606 [103370644,950908], 607 [106444046,870178], 608 [22217246,735880], 609 [18088843,694607], 610 [63426007,612179], 611 [117354942,906431], 612 [121434305,94282311,816072], 613 [118441466,93873772,783366], 614 [124338174,1414801,123089], 615 [20240282,17113486,923647], 616 [126495528,61463488,164994], 617 [125341723,5729072,445539], 618 [127450932,80442669,348245], 619 [123354692,85724182,14241288,180793], 620 [99159367,65959274,61680971,289939], 621 [107637580,104512101,62639807,181644], 622 [139547511,51654420,2062545,151944], 623 [88078274,73031465,53388204,428872], 624 [141314238,75761379,55699508,861797], 625 [88045216,59272943,21030492,180903]], 626 627 %% Verify that the keys still collide (this will fail if the 628 %% internal hash function has been changed). 629 case erlang:system_info(wordsize) of 630 8 -> 631 verify_colliding_keys(L); 632 4 -> 633 %% Not guaranteed to collide on a 32-bit system. 634 ok 635 end, 636 637 L. 638 639verify_colliding_keys([[K|Ks]|Gs]) -> 640 Hash = internal_hash(K), 641 [Hash] = lists:usort([internal_hash(Key) || Key <- Ks]), 642 verify_colliding_keys(Gs); 643verify_colliding_keys([]) -> 644 ok. 645 646internal_hash(Term) -> 647 erts_debug:get_internal_state({internal_hash,Term}). 648 649%% Use this function to (re)generate the list in colliding_keys/0 650find_colliding_keys() -> 651 MaxCollSz = 4, 652 OfEachSz = 7, 653 erts_debug:set_internal_state(available_internal_state, true), 654 MaxInserts = 1 bsl 20, 655 T = ets:new(x, [set, private]), 656 ok = fck_loop_1(T, 1, MaxInserts, MaxCollSz, OfEachSz), 657 fck_collect(T, MaxCollSz, OfEachSz, []). 658 659fck_collect(_T, 1, _OfEachSz, Acc) -> 660 Acc; 661fck_collect(T, CollSz, OfEachSz, Acc) -> 662 {List, _} = ets:select(T, 663 [{{'$1','$2'}, [{'==',{length,'$2'},CollSz}], ['$2']}], 664 OfEachSz), 665 fck_collect(T, CollSz-1, OfEachSz, List ++ Acc). 666 667 668fck_loop_1(T, Key, 0, MaxCollSz, MaxSzLeft) -> 669 fck_loop_2(T, Key, MaxCollSz, MaxSzLeft); 670fck_loop_1(T, Key, Inserts, MaxCollSz, MaxSzLeft) -> 671 Hash = internal_hash(Key), 672 case ets:insert_new(T, {Hash, [Key]}) of 673 true -> 674 fck_loop_1(T, Key+1, Inserts-1, MaxCollSz, MaxSzLeft); 675 false -> 676 [{Hash, KeyList}] = ets:lookup(T, Hash), 677 true = ets:insert(T, {Hash, [Key | KeyList]}), 678 fck_loop_1(T, Key+1, Inserts, MaxCollSz, MaxSzLeft) 679 end. 680 681fck_loop_2(_T, _Key, _MaxCollSz, 0) -> 682 ok; 683fck_loop_2(T, Key, MaxCollSz, MaxSzLeft0) -> 684 Hash = internal_hash(Key), 685 case ets:lookup(T, Hash) of 686 [] -> 687 fck_loop_2(T, Key+1, MaxCollSz, MaxSzLeft0); 688 [{Hash, KeyList}] -> 689 true = ets:insert(T, {Hash, [Key | KeyList]}), 690 MaxSzLeft1 = case length(KeyList)+1 of 691 MaxCollSz -> 692 MaxSzLeft0 - 1; 693 _ -> 694 MaxSzLeft0 695 end, 696 fck_loop_2(T, Key+1, MaxCollSz, MaxSzLeft1) 697 end. 698 699 700 701%% OTP-17700 Bug skipped refc++ of shared magic reference 702shared_magic_ref(_Config) -> 703 Ref = atomics:new(10, []), 704 persistent_term:put(shared_magic_ref, {Ref, Ref}), 705 shared_magic_ref_cont(). 706 707shared_magic_ref_cont() -> 708 erlang:garbage_collect(), 709 {Ref, Ref} = persistent_term:get(shared_magic_ref), 710 0 = atomics:get(Ref, 1), %% would definitely fail on debug vm 711 ok. 712 713 714%% Test that all persistent terms are erased by init:restart/0. 715 716init_restart(_Config) -> 717 File = "command_file", 718 ok = file:write_file(File, term_to_binary(restart)), 719 {ok,[[Erl]]} = init:get_argument(progname), 720 ModPath = filename:dirname(code:which(?MODULE)), 721 Cmd = Erl ++ " -pa " ++ ModPath ++ " -noshell " 722 "-run " ++ ?MODULE_STRING ++ " test_init_restart_cmd " ++ 723 File, 724 io:format("~s\n", [Cmd]), 725 Expected = "12ok", 726 case os:cmd(Cmd) of 727 Expected -> 728 ok; 729 Actual -> 730 io:format("Expected: ~s", [Expected]), 731 io:format("Actual: ~s\n", [Actual]), 732 ct:fail(unexpected_output) 733 end. 734 735test_init_restart_cmd([File]) -> 736 try 737 do_test_init_restart_cmd(File) 738 catch 739 C:R -> 740 io:format("\n~p ~p\n", [C,R]), 741 halt() 742 end, 743 receive 744 _ -> ok 745 end. 746 747do_test_init_restart_cmd(File) -> 748 {ok,Bin} = file:read_file(File), 749 Seq = lists:seq(1, 50), 750 case binary_to_term(Bin) of 751 restart -> 752 _ = [persistent_term:put({?MODULE,I}, {value,I}) || 753 I <- Seq], 754 ok = file:write_file(File, term_to_binary(was_restarted)), 755 io:put_chars("1"), 756 init:restart(), 757 receive 758 _ -> ok 759 end; 760 was_restarted -> 761 io:put_chars("2"), 762 ok = file:delete(File), 763 _ = [begin 764 Key = {?MODULE,I}, 765 {'EXIT',{badarg,_}} = (catch persistent_term:get(Key)) 766 end || I <- Seq], 767 io:put_chars("ok"), 768 init:stop() 769 end. 770 771%% Test that the literal is copied when removed also when 772%% the whole message is a literal... 773 774whole_message(Config) when is_list(Config) -> 775 whole_message_test(on_heap), 776 whole_message_test(off_heap), 777 ok. 778 779whole_message_test(MQD) -> 780 io:format("Testing on ~p~n", [MQD]), 781 Go = make_ref(), 782 Done = make_ref(), 783 TestRef = make_ref(), 784 Tester = self(), 785 persistent_term:put(test_ref, TestRef), 786 Pid = spawn_opt(fun () -> 787 receive Go -> ok end, 788 receive TestRef -> ok end, 789 receive TestRef -> ok end, 790 receive TestRef -> ok end, 791 receive [TestRef] -> ok end, 792 receive [TestRef] -> ok end, 793 receive [TestRef] -> ok end, 794 Tester ! Done 795 end, [link, {message_queue_data, MQD}]), 796 Pid ! persistent_term:get(test_ref), 797 Pid ! persistent_term:get(test_ref), 798 Pid ! persistent_term:get(test_ref), 799 %% Throw in some messages with a reference from the heap 800 %% while we're at it... 801 Pid ! [persistent_term:get(test_ref)], 802 Pid ! [persistent_term:get(test_ref)], 803 Pid ! [persistent_term:get(test_ref)], 804 persistent_term:erase(test_ref), 805 receive after 1000 -> ok end, 806 Pid ! Go, 807 receive Done -> ok end, 808 unlink(Pid), 809 exit(Pid, kill), 810 false = is_process_alive(Pid), 811 ok. 812 813%% Check that there is the same number of persistents terms before 814%% and after each test case. 815 816chk() -> 817 {xtra_info(), persistent_term:get()}. 818 819chk({Info1, _Initial} = Chk) -> 820 #{count := Count, memory := Memory1, table := Table1} = Info1, 821 case xtra_info() of 822 Info1 -> 823 ok; 824 #{count := Count, memory := Memory2, table := Table2}=Info2 825 when Memory2 > Memory1, 826 Table2 > Table1 -> 827 %% Check increased memory is only table growth hysteresis 828 MemDiff = Memory2 - Memory1, 829 TabDiff = (Table2 - Table1) * erlang:system_info(wordsize), 830 {MemDiff,MemDiff} = {MemDiff, TabDiff}, 831 832 case (Count / Table2) of 833 Load when Load >= 0.25 -> 834 ok; 835 _ -> 836 chk_fail("Hash table too large", Info1, Info2) 837 end; 838 Info2 -> 839 chk_fail("Memory diff", Info1, Info2) 840 end, 841 Key = {?MODULE,?FUNCTION_NAME}, 842 ok = persistent_term:put(Key, {term,Info1}), 843 Term = persistent_term:get(Key), 844 true = persistent_term:erase(Key), 845 chk_not_stuck(Term, 1), 846 [persistent_term:erase(K) || {K, _} <- pget(Chk)], 847 ok. 848 849xtra_info() -> 850 maps:merge(persistent_term:info(), 851 erts_debug:get_internal_state(persistent_term)). 852 853chk_fail(Error, Info1, Info2) -> 854 io:format("Info1 = ~p\n", [Info1]), 855 io:format("Info2 = ~p\n", [Info2]), 856 ct:fail(Error). 857 858chk_not_stuck(Term, Timeout) -> 859 %% Hash tables to be deleted are put onto a queue. 860 %% Make sure that the queue isn't stuck by a table with 861 %% a non-zero ref count. 862 863 case erts_debug:size_shared(Term) of 864 0 -> 865 receive after Timeout -> ok end, 866 case Timeout of 867 1000 -> 868 flush_later_ops(), 869 chk_not_stuck(Term, 1); 870 _ -> 871 chk_not_stuck(Term, Timeout*10) 872 end; 873 _ -> 874 ok 875 end. 876 877pget({_, Initial}) -> 878 persistent_term:get() -- Initial. 879 880 881killed_while_trapping_put(_Config) -> 882 repeat( 883 fun() -> 884 NrOfPutsInChild = 10000, 885 do_puts(2500, my_value), 886 Pid = 887 spawn(fun() -> 888 do_puts(NrOfPutsInChild, my_value2) 889 end), 890 timer:sleep(1), 891 erlang:exit(Pid, kill), 892 do_erases(NrOfPutsInChild) 893 end, 894 10), 895 ok. 896 897killed_while_trapping_erase(_Config) -> 898 repeat( 899 fun() -> 900 NrOfErases = 2500, 901 do_puts(NrOfErases, my_value), 902 Pid = 903 spawn(fun() -> 904 do_erases(NrOfErases) 905 end), 906 timer:sleep(1), 907 erlang:exit(Pid, kill), 908 do_erases(NrOfErases) 909 end, 910 10), 911 ok. 912 913put_erase_trapping(_Config) -> 914 NrOfItems = 5000, 915 do_puts(NrOfItems, first), 916 do_puts(NrOfItems, second), 917 do_erases(NrOfItems), 918 ok. 919 920do_puts(0, _) -> ok; 921do_puts(NrOfPuts, ValuePrefix) -> 922 Key = {?MODULE, NrOfPuts}, 923 Value = {ValuePrefix, NrOfPuts}, 924 erts_debug:set_internal_state(reds_left, rand:uniform(250)), 925 persistent_term:put(Key, Value), 926 Value = persistent_term:get(Key), 927 do_puts(NrOfPuts - 1, ValuePrefix). 928 929do_erases(0) -> ok; 930do_erases(NrOfErases) -> 931 Key = {?MODULE,NrOfErases}, 932 erts_debug:set_internal_state(reds_left, rand:uniform(500)), 933 persistent_term:erase(Key), 934 not_found = persistent_term:get(Key, not_found), 935 do_erases(NrOfErases - 1). 936 937repeat(_Fun, 0) -> 938 ok; 939repeat(Fun, N) -> 940 Fun(), 941 repeat(Fun, N-1). 942 943error_info(_Config) -> 944 L = [{erase, [{?MODULE,my_key}], [no_fail]}, 945 {get, [{?MODULE,certainly_not_existing}]}, 946 {get, [{?MODULE,certainly_not_existing}, default], [no_fail]}, 947 {put, 2} %Can't fail. 948 ], 949 do_error_info(L). 950 951do_error_info(L0) -> 952 L1 = lists:foldl(fun({_,A}, Acc) when is_integer(A) -> Acc; 953 ({F,A}, Acc) -> [{F,A,[]}|Acc]; 954 ({F,A,Opts}, Acc) -> [{F,A,Opts}|Acc] 955 end, [], L0), 956 Tests = ordsets:from_list([{F,length(A)} || {F,A,_} <- L1] ++ 957 [{F,A} || {F,A} <- L0, is_integer(A)]), 958 Bifs0 = [{F,A} || {F,A} <- persistent_term:module_info(exports), 959 A =/= 0, 960 F =/= module_info], 961 Bifs = ordsets:from_list(Bifs0), 962 NYI = [{F,lists:duplicate(A, '*'),nyi} || {F,A} <- Bifs -- Tests], 963 L = lists:sort(NYI ++ L1), 964 do_error_info(L, []). 965 966do_error_info([{_,Args,nyi}=H|T], Errors) -> 967 case lists:all(fun(A) -> A =:= '*' end, Args) of 968 true -> 969 do_error_info(T, [{nyi,H}|Errors]); 970 false -> 971 do_error_info(T, [{bad_nyi,H}|Errors]) 972 end; 973do_error_info([{F,Args,Opts}|T], Errors) -> 974 eval_bif_error(F, Args, Opts, T, Errors); 975do_error_info([], Errors0) -> 976 case lists:sort(Errors0) of 977 [] -> 978 ok; 979 [_|_]=Errors -> 980 io:format("\n~p\n", [Errors]), 981 ct:fail({length(Errors),errors}) 982 end. 983 984eval_bif_error(F, Args, Opts, T, Errors0) -> 985 try apply(persistent_term, F, Args) of 986 Result -> 987 case lists:member(no_fail, Opts) of 988 true -> 989 do_error_info(T, Errors0); 990 false -> 991 do_error_info(T, [{should_fail,{F,Args},Result}|Errors0]) 992 end 993 catch 994 error:Reason:Stk -> 995 SF = fun(Mod, _, _) -> Mod =:= test_server end, 996 Str = erl_error:format_exception(error, Reason, Stk, #{stack_trim_fun => SF}), 997 BinStr = iolist_to_binary(Str), 998 ArgStr = lists:join(", ", [io_lib:format("~p", [A]) || A <- Args]), 999 io:format("\nerlang:~p(~s)\n~ts", [F,ArgStr,BinStr]), 1000 1001 case Stk of 1002 [{persistent_term,ActualF,ActualArgs,Info}|_] -> 1003 RE = <<"[*][*][*] argument \\d+:">>, 1004 Errors1 = case re:run(BinStr, RE, [{capture, none}]) of 1005 match -> 1006 Errors0; 1007 nomatch when Reason =:= system_limit -> 1008 Errors0; 1009 nomatch -> 1010 [{no_explanation,{F,Args},Info}|Errors0] 1011 end, 1012 1013 Errors = case {ActualF,ActualArgs} of 1014 {F,Args} -> 1015 Errors1; 1016 _ -> 1017 [{renamed,{F,length(Args)},{ActualF,ActualArgs}}|Errors1] 1018 end, 1019 1020 do_error_info(T, Errors); 1021 _ -> 1022 Errors = [{renamed,{F,length(Args)},hd(Stk)}|Errors0], 1023 do_error_info(T, Errors) 1024 end 1025 end. 1026 1027 1028%% OTP-17298 1029get_all_race(_Config) -> 1030 N = 20 * erlang:system_info(schedulers_online), 1031 persistent_term:put(get_all_race, N), 1032 SPs = [spawn_link(fun() -> gar_setter(Seq) end) || Seq <- lists:seq(1, N)], 1033 GPs = [spawn_link(fun gar_getter/0) || _ <- lists:seq(1, N)], 1034 receive after 2000 -> ok end, 1035 [begin unlink(Pid), exit(Pid,kill) end || Pid <- (SPs ++ GPs)], 1036 ok. 1037 1038get_all_race_cleanup() -> 1039 N = persistent_term:get(get_all_race, 0), 1040 _ = persistent_term:erase(get_all_race), 1041 [_ = persistent_term:erase(Seq) || Seq <- lists:seq(1, N)], 1042 ok. 1043 1044gar_getter() -> 1045 erts_debug:set_internal_state(reds_left, 1), 1046 _ = persistent_term:get(), 1047 gar_getter(). 1048 1049gar_setter(Key) -> 1050 erts_debug:set_internal_state(reds_left, 1), 1051 persistent_term:erase(Key), 1052 persistent_term:put(Key, {complex, term}), 1053 gar_setter(Key). 1054 1055%% Test that literals in non-message signals are copied 1056%% when removed... 1057%% 1058%% Currently the only non-message signal that may carry 1059%% literals are alias-message signals. Exit signals have 1060%% been added to the test since they should be added 1061%% next... 1062 1063non_message_signal(Config) when is_list(Config) -> 1064 non_message_signal_test(on_heap), 1065 non_message_signal_test(off_heap), 1066 ok. 1067 1068non_message_signal_test(MQD) -> 1069 io:format("Testing on ~p~n", [MQD]), 1070 process_flag(scheduler, 1), 1071 process_flag(priority, max), 1072 MultiSched = erlang:system_info(schedulers) > 1, 1073 {TokOpts, RecvOpts} 1074 = case MultiSched of 1075 true -> 1076 {[link, {scheduler, 2}, {priority, high}], 1077 [link, {scheduler, 2}, {priority, low}, 1078 {message_queue_data, MQD}]}; 1079 false -> 1080 {[link], [link, {message_queue_data, MQD}]} 1081 end, 1082 RecvPrepared = make_ref(), 1083 TokPrepared = make_ref(), 1084 Go = make_ref(), 1085 Done = make_ref(), 1086 TestRef = make_ref(), 1087 Tester = self(), 1088 persistent_term:put(test_ref, TestRef), 1089 Pid = spawn_opt(fun () -> 1090 process_flag(trap_exit, true), 1091 Tester ! {RecvPrepared, alias()}, 1092 receive Go -> ok end, 1093 recv_msg(TestRef, 50), 1094 recv_msg([TestRef], 50), 1095 recv_msg({'EXIT', Tester, TestRef}, 50), 1096 recv_msg({'EXIT', Tester, [TestRef]}, 50), 1097 Tester ! Done 1098 end, RecvOpts), 1099 Alias = receive {RecvPrepared, A} -> A end, 1100 Tok = spawn_opt(fun () -> 1101 Tester ! TokPrepared, 1102 tok_loop() 1103 end, TokOpts), 1104 receive TokPrepared -> ok end, 1105 lists:foreach(fun (_) -> Alias ! persistent_term:get(test_ref) end, 1106 lists:seq(1, 50)), 1107 lists:foreach(fun (_) -> Alias ! [persistent_term:get(test_ref)] end, 1108 lists:seq(1, 50)), 1109 lists:foreach(fun (_) -> exit(Pid, persistent_term:get(test_ref)) end, 1110 lists:seq(1, 50)), 1111 lists:foreach(fun (_) -> exit(Pid, [persistent_term:get(test_ref)]) end, 1112 lists:seq(1, 50)), 1113 persistent_term:erase(test_ref), 1114 receive after 1000 -> ok end, 1115 unlink(Tok), 1116 exit(Tok, kill), 1117 receive after 1000 -> ok end, 1118 Pid ! Go, 1119 receive Done -> ok end, 1120 unlink(Pid), 1121 exit(Pid, kill), 1122 false = is_process_alive(Pid), 1123 false = is_process_alive(Tok), 1124 ok. 1125 1126recv_msg(_Msg, 0) -> 1127 ok; 1128recv_msg(Msg, N) -> 1129 receive 1130 Msg -> 1131 recv_msg(Msg, N-1) 1132 end. 1133 1134tok_loop() -> 1135 tok_loop(). 1136