1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1996-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-module(ets_SUITE). 21 22-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 23 init_per_group/2,end_per_group/2]). 24-export([default/1,setbag/1,badnew/1,verybadnew/1,named/1,keypos2/1, 25 privacy/1]). 26-export([empty/1,badinsert/1]). 27-export([badlookup/1,lookup_order/1]). 28-export([delete_elem/1,delete_tab/1,delete_large_tab/1, 29 delete_large_named_table/1, 30 evil_delete/1,baddelete/1,match_delete/1,table_leak/1]). 31-export([match_delete3/1]). 32-export([firstnext/1,firstnext_concurrent/1]). 33-export([slot/1]). 34-export([hash_clash/1]). 35-export([match1/1, match2/1, match_object/1, match_object2/1]). 36-export([dups/1, misc1/1, safe_fixtable/1, info/1, tab2list/1]). 37-export([info_binary_stress/1]). 38-export([tab2file/1, tab2file2/1, tabfile_ext1/1, 39 tabfile_ext2/1, tabfile_ext3/1, tabfile_ext4/1, badfile/1]). 40-export([heavy_lookup/1, heavy_lookup_element/1, heavy_concurrent/1]). 41-export([lookup_element_mult/1]). 42-export([foldl_ordered/1, foldr_ordered/1, foldl/1, foldr/1, fold_empty/1]). 43-export([t_delete_object/1, t_init_table/1, t_whitebox/1, 44 select_bound_chunk/1, t_delete_all_objects/1, t_test_ms/1, 45 t_select_delete/1,t_select_replace/1,t_select_replace_next_bug/1, 46 t_select_pam_stack_overflow_bug/1, 47 t_ets_dets/1]). 48-export([t_insert_list/1, t_insert_list_bag/1, t_insert_list_duplicate_bag/1, 49 t_insert_list_set/1, t_insert_list_delete_set/1, 50 t_insert_list_parallel/1, t_insert_list_delete_parallel/1, 51 t_insert_list_kill_process/1]). 52-export([test_table_size_concurrency/1,test_table_memory_concurrency/1, 53 test_delete_table_while_size_snapshot/1, test_delete_table_while_size_snapshot_helper/1, 54 test_decentralized_counters_setting/1]). 55 56-export([ordered/1, ordered_match/1, interface_equality/1, 57 fixtable_next/1, fixtable_iter_bag/1, 58 fixtable_insert/1, rename/1, rename_unnamed/1, evil_rename/1, 59 update_element/1, update_counter/1, evil_update_counter/1, partly_bound/1, match_heavy/1]). 60-export([update_counter_with_default/1]). 61-export([update_counter_with_default_bad_pos/1]). 62-export([update_counter_table_growth/1]). 63-export([member/1]). 64-export([memory/1]). 65-export([select_fail/1]). 66-export([t_insert_new/1]). 67-export([t_repair_continuation/1]). 68-export([t_match_spec_run/1]). 69-export([t_bucket_disappears/1]). 70-export([t_named_select/1]). 71-export([select_fixtab_owner_change/1]). 72-export([otp_5340/1]). 73-export([otp_6338/1]). 74-export([otp_6842_select_1000/1]). 75-export([select_mbuf_trapping/1]). 76-export([otp_7665/1]). 77-export([meta_wb/1]). 78-export([grow_shrink/1, grow_pseudo_deleted/1, shrink_pseudo_deleted/1]). 79-export([meta_lookup_unnamed_read/1, meta_lookup_unnamed_write/1, 80 meta_lookup_named_read/1, meta_lookup_named_write/1, 81 meta_newdel_unnamed/1, meta_newdel_named/1]). 82-export([smp_insert/1, smp_fixed_delete/1, smp_unfix_fix/1, smp_select_delete/1, 83 smp_ordered_iteration/1, 84 smp_select_replace/1, otp_8166/1, otp_8732/1, delete_unfix_race/1]). 85-export([throughput_benchmark/0, 86 throughput_benchmark/1, 87 test_throughput_benchmark/1, 88 long_throughput_benchmark/1, 89 lookup_catree_par_vs_seq_init_benchmark/0]). 90-export([exit_large_table_owner/1, 91 exit_many_large_table_owner/1, 92 exit_many_tables_owner/1, 93 exit_many_many_tables_owner/1]). 94-export([write_concurrency/1, heir/1, give_away/1, setopts/1]). 95-export([bad_table/1, types/1]). 96-export([otp_9932/1]). 97-export([otp_9423/1]). 98-export([otp_10182/1]). 99-export([ets_all/1]). 100-export([massive_ets_all/1]). 101-export([take/1]). 102-export([whereis_table/1]). 103-export([ms_excessive_nesting/1]). 104-export([error_info/1]). 105 106-export([init_per_testcase/2, end_per_testcase/2]). 107%% Convenience for manual testing 108-export([random_test/0]). 109 110-export([t_select_reverse/1]). 111 112-include_lib("stdlib/include/ms_transform.hrl"). % ets:fun2ms 113-include_lib("common_test/include/ct.hrl"). 114-include_lib("common_test/include/ct_event.hrl"). 115 116-define(m(A,B), assert_eq(A,B)). 117-define(heap_binary_size, 64). 118 119init_per_testcase(Case, Config) -> 120 rand:seed(default), 121 io:format("*** SEED: ~p ***\n", [rand:export_seed()]), 122 start_spawn_logger(), 123 wait_for_test_procs(), %% Ensure previous case cleaned up 124 [{test_case, Case} | Config]. 125 126end_per_testcase(_Func, _Config) -> 127 wait_for_test_procs(true). 128 129 130%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 131 132suite() -> 133 [{ct_hooks,[ts_install_cth]}, 134 {timetrap,{minutes,5}}]. 135 136all() -> 137 [{group, new}, {group, insert}, {group, lookup}, 138 {group, delete}, firstnext, firstnext_concurrent, slot, hash_clash, 139 {group, match}, t_match_spec_run, 140 {group, lookup_element}, {group, misc}, {group, files}, 141 {group, heavy}, {group, insert_list}, ordered, ordered_match, 142 interface_equality, fixtable_next, fixtable_iter_bag, fixtable_insert, 143 rename, rename_unnamed, evil_rename, update_element, 144 update_counter, evil_update_counter, 145 update_counter_with_default, 146 update_counter_with_default_bad_pos, 147 partly_bound, 148 update_counter_table_growth, 149 match_heavy, {group, fold}, member, t_delete_object, 150 select_bound_chunk, 151 t_init_table, t_whitebox, t_delete_all_objects, 152 t_test_ms, t_select_delete, t_select_replace, 153 t_select_replace_next_bug, 154 t_select_pam_stack_overflow_bug, 155 t_ets_dets, memory, t_select_reverse, t_bucket_disappears, 156 t_named_select, select_fixtab_owner_change, 157 select_fail, t_insert_new, t_repair_continuation, 158 otp_5340, otp_6338, otp_6842_select_1000, otp_7665, 159 select_mbuf_trapping, 160 otp_8732, meta_wb, grow_shrink, grow_pseudo_deleted, 161 shrink_pseudo_deleted, {group, meta_smp}, smp_insert, 162 smp_fixed_delete, smp_unfix_fix, smp_select_replace, 163 smp_ordered_iteration, 164 smp_select_delete, otp_8166, exit_large_table_owner, 165 exit_many_large_table_owner, exit_many_tables_owner, 166 exit_many_many_tables_owner, write_concurrency, heir, 167 give_away, setopts, bad_table, types, 168 otp_10182, 169 otp_9932, 170 otp_9423, 171 ets_all, 172 massive_ets_all, 173 take, 174 whereis_table, 175 delete_unfix_race, 176 %test_throughput_benchmark, 177 %{group, benchmark}, 178 test_table_size_concurrency, 179 test_table_memory_concurrency, 180 test_delete_table_while_size_snapshot, 181 test_decentralized_counters_setting, 182 ms_excessive_nesting, 183 error_info]. 184 185 186groups() -> 187 [{new, [], 188 [default, setbag, badnew, verybadnew, named, keypos2, 189 privacy]}, 190 {insert, [], [empty, badinsert]}, 191 {lookup, [], [badlookup, lookup_order]}, 192 {lookup_element, [], [lookup_element_mult]}, 193 {delete, [], 194 [delete_elem, delete_tab, delete_large_tab, 195 delete_large_named_table, evil_delete, table_leak, 196 baddelete, match_delete, match_delete3]}, 197 {match, [], 198 [match1, match2, match_object, match_object2]}, 199 {misc, [], 200 [misc1, safe_fixtable, info, info_binary_stress, dups, tab2list]}, 201 {files, [], 202 [tab2file, tab2file2, tabfile_ext1, 203 tabfile_ext2, tabfile_ext3, tabfile_ext4, badfile]}, 204 {heavy, [], 205 [heavy_lookup, heavy_lookup_element, heavy_concurrent]}, 206 {fold, [], 207 [foldl_ordered, foldr_ordered, foldl, foldr, 208 fold_empty]}, 209 {meta_smp, [], 210 [meta_lookup_unnamed_read, meta_lookup_unnamed_write, 211 meta_lookup_named_read, meta_lookup_named_write, 212 meta_newdel_unnamed, meta_newdel_named]}, 213 {benchmark, [], 214 [long_throughput_benchmark]}, 215 {insert_list, [], 216 [t_insert_list, t_insert_list_set, t_insert_list_bag, 217 t_insert_list_duplicate_bag, t_insert_list_delete_set, 218 t_insert_list_parallel, t_insert_list_delete_parallel, 219 t_insert_list_kill_process]}]. 220 221init_per_suite(Config) -> 222 erts_debug:set_internal_state(available_internal_state, true), 223 case erts_debug:set_internal_state(ets_force_trap, true) of 224 ok -> 225 [{ets_force_trap, true} | Config]; 226 notsup -> 227 Config 228 end. 229 230end_per_suite(_Config) -> 231 stop_spawn_logger(), 232 erts_debug:set_internal_state(ets_force_trap, false), 233 catch erts_debug:set_internal_state(available_internal_state, false), 234 ok. 235 236init_per_group(benchmark, Config) -> 237 P = self(), 238 %% Spawn owner of ETS table that is alive until end_per_group is run 239 EtsProcess = 240 spawn( 241 fun()-> 242 Tab = ets:new(ets_benchmark_result_summary_tab, [public]), 243 P ! {the_table, Tab}, 244 receive 245 kill -> ok 246 end 247 end), 248 Tab = receive {the_table, T} -> T end, 249 CounterNames = [nr_of_benchmarks, 250 total_throughput, 251 nr_of_set_benchmarks, 252 total_throughput_set, 253 nr_of_ordered_set_benchmarks, 254 total_throughput_ordered_set], 255 lists:foreach(fun(CtrName) -> 256 ets:insert(Tab, {CtrName, 0.0}) 257 end, 258 CounterNames), 259 [{ets_benchmark_result_summary_tab, Tab}, 260 {ets_benchmark_result_summary_tab_process, EtsProcess} | Config]; 261init_per_group(_GroupName, Config) -> 262 Config. 263 264end_per_group(benchmark, Config) -> 265 T = proplists:get_value(ets_benchmark_result_summary_tab, Config), 266 EtsProcess = proplists:get_value(ets_benchmark_result_summary_tab_process, Config), 267 Report = 268 fun(NOfBenchmarksCtr, TotThroughoutCtr, Name) -> 269 Average = 270 ets:lookup_element(T, TotThroughoutCtr, 2) / 271 ets:lookup_element(T, NOfBenchmarksCtr, 2), 272 io:format("~p ~p~n", [Name, Average]), 273 ct_event:notify( 274 #event{name = benchmark_data, 275 data = [{suite,"ets_bench"}, 276 {name, Name}, 277 {value, Average}]}) 278 end, 279 Report(nr_of_benchmarks, 280 total_throughput, 281 "Average Throughput"), 282 Report(nr_of_set_benchmarks, 283 total_throughput_set, 284 "Average Throughput Set"), 285 Report(nr_of_ordered_set_benchmarks, 286 total_throughput_ordered_set, 287 "Average Throughput Ordered Set"), 288 ets:delete(T), 289 EtsProcess ! kill, 290 Config; 291end_per_group(_GroupName, Config) -> 292 Config. 293 294 295%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 296 297%% Test that a disappearing bucket during select of a non-fixed table works. 298t_bucket_disappears(Config) when is_list(Config) -> 299 repeat_for_opts(fun t_bucket_disappears_do/1). 300 301t_bucket_disappears_do(Opts) -> 302 EtsMem = etsmem(), 303 ets_new(abcd, [named_table, public, {keypos, 2} | Opts]), 304 ets:insert(abcd, {abcd,1,2}), 305 ets:insert(abcd, {abcd,2,2}), 306 ets:insert(abcd, {abcd,3,2}), 307 {_, Cont} = ets:select(abcd, [{{'_', '$1', '_'}, 308 [{'<', '$1', {const, 10}}], 309 ['$1']}], 1), 310 ets:delete(abcd, 2), 311 ets:select(Cont), 312 true = ets:delete(abcd), 313 verify_etsmem(EtsMem). 314 315%% OTP-21: Test that select/1 fails if named table was deleted and recreated 316%% and succeeds if table was renamed. 317t_named_select(_Config) -> 318 repeat_for_opts(fun t_named_select_do/1). 319 320t_named_select_do(Opts) -> 321 EtsMem = etsmem(), 322 T = t_name_tid_select, 323 ets_new(T, [named_table | Opts]), 324 ets:insert(T, {1,11}), 325 ets:insert(T, {2,22}), 326 ets:insert(T, {3,33}), 327 MS = [{{'$1', 22}, [], ['$1']}], 328 {[2], Cont1} = ets:select(T, MS, 1), 329 ets:delete(T), 330 {'EXIT',{badarg,_}} = (catch ets:select(Cont1)), 331 ets_new(T, [named_table | Opts]), 332 {'EXIT',{badarg,_}} = (catch ets:select(Cont1)), 333 334 true = ets:insert_new(T, {1,22}), 335 true = ets:insert_new(T, {2,22}), 336 true = ets:insert_new(T, {4,22}), 337 {[A,B], Cont2} = ets:select(T, MS, 2), 338 ets:rename(T, abcd), 339 {[C], '$end_of_table'} = ets:select(Cont2), 340 7 = A + B + C, 341 342 true = ets:delete(abcd), 343 verify_etsmem(EtsMem). 344 345 346%% Verify select and friends release fixtab as they should 347%% even when owneship is changed between traps. 348select_fixtab_owner_change(_Config) -> 349 T = ets:new(xxx, [protected]), 350 NKeys = 2000, 351 [ets:insert(T,{K,K band 7}) || K <- lists:seq(1,NKeys)], 352 353 %% Buddy and Papa will ping-pong table ownership between them 354 %% and the aim is to give Buddy the table when he is 355 %% in the middle of a yielding select* call. 356 {Buddy,_} = spawn_opt(fun() -> sfoc_buddy_loop(T, 1, undefined) end, 357 [link,monitor]), 358 359 sfoc_papa_loop(T, Buddy), 360 361 receive {'DOWN', _, process, Buddy, _} -> ok end, 362 ets:delete(T), 363 ok. 364 365sfoc_buddy_loop(T, I, State0) -> 366 receive 367 {'ETS-TRANSFER', T, Papa, _} -> 368 ets:give_away(T, Papa, State0), 369 case State0 of 370 done -> 371 ok; 372 _ -> 373 State1 = sfoc_traverse(T, I, State0), 374 %% Verify no fixation left 375 {I, false} = {I, ets:info(T, safe_fixed_monotonic_time)}, 376 sfoc_buddy_loop(T, I+1, State1) 377 end 378 end. 379 380sfoc_papa_loop(T, Buddy) -> 381 ets:give_away(T, Buddy, "Catch!"), 382 receive 383 {'ETS-TRANSFER', T, Buddy, State} -> 384 case State of 385 done -> 386 ok; 387 _ -> 388 sfoc_papa_loop(T, Buddy) 389 end 390 end. 391 392sfoc_traverse(T, 1, S) -> 393 ets:select(T, [{{'$1',7}, [], ['$1']}]), S; 394sfoc_traverse(T, 2, S) -> 395 0 = ets:select_count(T, [{{'$1',7}, [], [false]}]), S; 396sfoc_traverse(T, 3, _) -> 397 Limit = ets:info(T, size) div 2, 398 {_, Continuation} = ets:select(T, [{{'$1',7}, [], ['$1']}], 399 Limit), 400 Continuation; 401sfoc_traverse(_T, 4, Continuation) -> 402 _ = ets:select(Continuation), 403 done. 404 405%% Check ets:match_spec_run/2. 406t_match_spec_run(Config) when is_list(Config) -> 407 ct:timetrap({minutes,30}), %% valgrind needs a lot 408 init_externals(), 409 EtsMem = etsmem(), 410 411 t_match_spec_run_test([{1},{2},{3}], 412 [{{'$1'},[{'>','$1',1}],['$1']}], 413 [2,3]), 414 415 Huge = [{X} || X <- lists:seq(1,2500)], 416 L = lists:seq(2476,2500), 417 t_match_spec_run_test(Huge, [{{'$1'},[{'>','$1',2475}],['$1']}], L), 418 419 L2 = [{X*16#FFFFFFF} || X <- L], 420 t_match_spec_run_test(Huge, 421 [{{'$1'}, [{'>','$1',2475}], [{{{'*','$1',16#FFFFFFF}}}]}], 422 L2), 423 424 t_match_spec_run_test(Huge, [{{'$1'}, [{'=:=',{'rem','$1',500},0}], ['$1']}], 425 [500,1000,1500,2000,2500]), 426 427 %% More matching fun with several match clauses and guards, 428 %% applied to a variety of terms. 429 Fun = fun(Term) -> 430 CTerm = {const, Term}, 431 432 N_List = [{Term, "0", "v-element"}, 433 {"=hidden_node", "0", Term}, 434 {"0", Term, Term}, 435 {"something", Term, "something else"}, 436 {"guard and res", Term, 872346}, 437 {Term, {'and',Term,'again'}, 3.14}, 438 {Term, {'and',Term,'again'}, "m&g"}, 439 {Term, {'and',Term,'again'}, "m&g&r"}, 440 {[{second,Term}, 'and', "tail"], Term, ['and',"tail"]}], 441 442 N_MS = [{{'$1','$2','$3'}, 443 [{'=:=','$1',CTerm}, {'=:=','$2',{const,"0"}}], 444 [{{"Guard only for $1",'$3'}}]}, 445 446 {{'$3','$1','$4'}, 447 [{'=:=','$3',"=hidden_node"}, {'=:=','$1',{const,"0"}}], 448 [{{"Result only for $4",'$4'}}]}, 449 450 {{'$2','$1','$1'}, 451 [{'=:=','$2',{const,"0"}}], 452 [{{"Match only for $1",'$2'}}]}, 453 454 {{'$2',Term,['$3'|'_']}, 455 [{is_list,'$2'},{'=:=','$3',$s}], 456 [{{"Matching term",'$2'}}]}, 457 458 {{'$1','$2',872346}, 459 [{'=:=','$2',CTerm}, {is_list,'$1'}], 460 [{{"Guard and result",'$2'}}]}, 461 462 {{'$1', {'and','$1','again'}, '$2'}, 463 [{is_float,'$2'}], 464 [{{"Match and result",'$1'}}]}, 465 466 {{'$1', {'and','$1','again'}, '$2'}, 467 [{'=:=','$1',CTerm}, {'=:=', '$2', "m&g"}], 468 [{{"Match and guard",'$2'}}]}, 469 470 {{'$1', {'and','$1','again'}, "m&g&r"}, 471 [{'=:=','$1',CTerm}], 472 [{{"Match, guard and result",'$1'}}]}, 473 474 {{'$1', '$2', '$3'}, 475 [{'=:=','$1',[{{second,'$2'}} | '$3']}], 476 [{{"Building guard"}}]} 477 ], 478 479 N_Result = [{"Guard only for $1", "v-element"}, 480 {"Result only for $4", Term}, 481 {"Match only for $1", "0"}, 482 {"Matching term","something"}, 483 {"Guard and result",Term}, 484 {"Match and result",Term}, 485 {"Match and guard","m&g"}, 486 {"Match, guard and result",Term}, 487 {"Building guard"}], 488 489 F = fun(N_MS_Perm) -> 490 t_match_spec_run_test(N_List, N_MS_Perm, N_Result) 491 end, 492 repeat_for_permutations(F, N_MS) 493 end, 494 test_terms(Fun, skip_refc_check), 495 496 verify_etsmem(EtsMem). 497 498t_match_spec_run_test(List, MS, Result) -> 499 500 %%io:format("ms = ~p\n",[MS]), 501 502 ?m(Result, ets:match_spec_run(List, ets:match_spec_compile(MS))), 503 504 %% Check that ets:select agree 505 Tab = ets:new(xxx, [bag]), 506 ets:insert(Tab, List), 507 SRes = lists:sort(Result), 508 ?m(SRes, lists:sort(ets:select(Tab, MS))), 509 ets:delete(Tab), 510 511 %% Check that tracing agree 512 Self = self(), 513 {Tracee, MonRef} = my_spawn_monitor(fun() -> ms_tracee(Self, List) end), 514 receive {Tracee, ready} -> ok end, 515 516 MST = lists:map(fun(Clause) -> ms_clause_ets_to_trace(Clause) end, MS), 517 518 %%io:format("MS = ~p\nMST= ~p\n",[MS,MST]), 519 520 erlang:trace_pattern({?MODULE,ms_tracee_dummy,'_'}, MST , [local]), 521 erlang:trace(Tracee, true, [call]), 522 Tracee ! start, 523 TRes = ms_tracer_collect(Tracee, MonRef, []), 524 case TRes of 525 SRes -> ok; 526 _ -> 527 io:format("TRACE MATCH FAILED\n"), 528 io:format("Input = ~p\nMST = ~p\nExpected = ~p\nGot = ~p\n", [List, MST, SRes, TRes]), 529 ct:fail("TRACE MATCH FAILED") 530 end, 531 ok. 532 533 534 535ms_tracer_collect(Tracee, Ref, Acc) -> 536 receive 537 {trace, Tracee, call, _Args, [Msg]} -> 538 ms_tracer_collect(Tracee, Ref, [Msg | Acc]); 539 540 {'DOWN', Ref, process, Tracee, _} -> 541 TDRef = erlang:trace_delivered(Tracee), 542 ms_tracer_collect(Tracee, TDRef, Acc); 543 544 {trace_delivered, Tracee, Ref} -> 545 lists:sort(Acc); 546 547 Other -> 548 io:format("Unexpected message = ~p\n", [Other]), 549 ct:fail("Unexpected tracer msg") 550 end. 551 552 553ms_tracee(Parent, CallArgList) -> 554 Parent ! {self(), ready}, 555 receive start -> ok end, 556 F = fun({A1}) -> 557 ms_tracee_dummy(A1); 558 ({A1,A2}) -> 559 ms_tracee_dummy(A1, A2); 560 ({A1,A2,A3}) -> 561 ms_tracee_dummy(A1, A2, A3); 562 ({A1,A2,A3,A4}) -> 563 ms_tracee_dummy(A1, A2, A3, A4) 564 end, 565 lists:foreach(F, CallArgList). 566 567ms_tracee_dummy(_) -> ok. 568ms_tracee_dummy(_,_) -> ok. 569ms_tracee_dummy(_,_,_) -> ok. 570ms_tracee_dummy(_,_,_,_) -> ok. 571 572ms_clause_ets_to_trace({Head, Guard, Body}) -> 573 {tuple_to_list(Head), Guard, [{message, Body}]}. 574 575assert_eq(A,A) -> ok; 576assert_eq(A,B) -> 577 io:format("FAILED MATCH:\n~p\n =/=\n~p\n",[A,B]), 578 ct:fail("assert_eq failed"). 579 580 581%% Test ets:repair_continuation/2. 582t_repair_continuation(Config) when is_list(Config) -> 583 repeat_for_opts(fun t_repair_continuation_do/1). 584 585 586t_repair_continuation_do(Opts) -> 587 EtsMem = etsmem(), 588 MS = [{'_',[],[true]}], 589 MS2 = [{{{'$1','_'},'_'},[],['$1']}], 590 (fun() -> 591 T = ets_new(x,[ordered_set|Opts]), 592 F = fun(0,_)->ok;(N,F) -> ets:insert(T,{N,N}), F(N-1,F) end, 593 F(1000,F), 594 {_,C} = ets:select(T,MS,5), 595 C2 = erlang:setelement(5,C,<<>>), 596 {'EXIT',{badarg,_}} = (catch ets:select(C2)), 597 C3 = ets:repair_continuation(C2,MS), 598 {[true,true,true,true,true],_} = ets:select(C3), 599 {[true,true,true,true,true],_} = ets:select(C), 600 true = ets:delete(T) 601 end)(), 602 (fun() -> 603 T = ets_new(x,[ordered_set|Opts]), 604 F = fun(0,_)->ok;(N,F) -> ets:insert(T,{N,N}), F(N-1,F) end, 605 F(1000,F), 606 {_,C} = ets:select(T,MS,1001), 607 C = '$end_of_table', 608 C3 = ets:repair_continuation(C,MS), 609 '$end_of_table' = ets:select(C3), 610 '$end_of_table' = ets:select(C), 611 true = ets:delete(T) 612 end)(), 613 614 (fun() -> 615 T = ets_new(x,[ordered_set|Opts]), 616 F = fun(0,_)->ok;(N,F) -> 617 ets:insert(T,{integer_to_list(N),N}), 618 F(N-1,F) 619 end, 620 F(1000,F), 621 {_,C} = ets:select(T,MS,5), 622 C2 = erlang:setelement(5,C,<<>>), 623 {'EXIT',{badarg,_}} = (catch ets:select(C2)), 624 C3 = ets:repair_continuation(C2,MS), 625 {[true,true,true,true,true],_} = ets:select(C3), 626 {[true,true,true,true,true],_} = ets:select(C), 627 true = ets:delete(T) 628 end)(), 629 (fun() -> 630 T = ets_new(x,[ordered_set|Opts]), 631 F = fun(0,_)->ok;(N,F) -> 632 ets:insert(T,{{integer_to_list(N),N},N}), 633 F(N-1,F) 634 end, 635 F(1000,F), 636 {_,C} = ets:select(T,MS2,5), 637 C2 = erlang:setelement(5,C,<<>>), 638 {'EXIT',{badarg,_}} = (catch ets:select(C2)), 639 C3 = ets:repair_continuation(C2,MS2), 640 {[_,_,_,_,_],_} = ets:select(C3), 641 {[_,_,_,_,_],_} = ets:select(C), 642 true = ets:delete(T) 643 end)(), 644 645 (fun() -> 646 T = ets_new(x,[set|Opts]), 647 F = fun(0,_)->ok;(N,F) -> 648 ets:insert(T,{N,N}), 649 F(N-1,F) 650 end, 651 F(1000,F), 652 {_,C} = ets:select(T,MS,5), 653 C2 = erlang:setelement(4,C,<<>>), 654 {'EXIT',{badarg,_}} = (catch ets:select(C2)), 655 C3 = ets:repair_continuation(C2,MS), 656 {[true,true,true,true,true],_} = ets:select(C3), 657 {[true,true,true,true,true],_} = ets:select(C), 658 true = ets:delete(T) 659 end)(), 660 (fun() -> 661 T = ets_new(x,[set|Opts]), 662 F = fun(0,_)->ok;(N,F) -> 663 ets:insert(T,{integer_to_list(N),N}), 664 F(N-1,F) 665 end, 666 F(1000,F), 667 {_,C} = ets:select(T,MS,5), 668 C2 = erlang:setelement(4,C,<<>>), 669 {'EXIT',{badarg,_}} = (catch ets:select(C2)), 670 C3 = ets:repair_continuation(C2,MS), 671 {[true,true,true,true,true],_} = ets:select(C3), 672 {[true,true,true,true,true],_} = ets:select(C), 673 true = ets:delete(T) 674 end)(), 675 (fun() -> 676 T = ets_new(x,[bag|Opts]), 677 F = fun(0,_)->ok;(N,F) -> 678 ets:insert(T,{integer_to_list(N),N}), 679 F(N-1,F) 680 end, 681 F(1000,F), 682 {_,C} = ets:select(T,MS,5), 683 C2 = erlang:setelement(4,C,<<>>), 684 {'EXIT',{badarg,_}} = (catch ets:select(C2)), 685 C3 = ets:repair_continuation(C2,MS), 686 {[true,true,true,true,true],_} = ets:select(C3), 687 {[true,true,true,true,true],_} = ets:select(C), 688 true = ets:delete(T) 689 end)(), 690 (fun() -> 691 T = ets_new(x,[duplicate_bag|Opts]), 692 F = fun(0,_)->ok;(N,F) -> 693 ets:insert(T,{integer_to_list(N),N}), 694 F(N-1,F) 695 end, 696 F(1000,F), 697 {_,C} = ets:select(T,MS,5), 698 C2 = erlang:setelement(4,C,<<>>), 699 {'EXIT',{badarg,_}} = (catch ets:select(C2)), 700 C3 = ets:repair_continuation(C2,MS), 701 {[true,true,true,true,true],_} = ets:select(C3), 702 {[true,true,true,true,true],_} = ets:select(C), 703 true = ets:delete(T) 704 end)(), 705 false = ets:is_compiled_ms(<<>>), 706 true = ets:is_compiled_ms(ets:match_spec_compile(MS)), 707 verify_etsmem(EtsMem). 708 709 710%% Test correct default vaules of a new ets table. 711default(Config) when is_list(Config) -> 712 %% Default should be set,protected 713 EtsMem = etsmem(), 714 Def = ets_new(def,[]), 715 set = ets:info(Def,type), 716 protected = ets:info(Def,protection), 717 Compressed = erlang:system_info(ets_always_compress), 718 Compressed = ets:info(Def,compressed), 719 Self = self(), 720 Self = ets:info(Def,owner), 721 none = ets:info(Def, heir), 722 false = ets:info(Def,named_table), 723 ets:delete(Def), 724 verify_etsmem(EtsMem). 725 726%% Test that select fails even if nothing can match. 727select_fail(Config) when is_list(Config) -> 728 EtsMem = etsmem(), 729 repeat_for_opts(fun select_fail_do/1, 730 [all_types,write_concurrency]), 731 verify_etsmem(EtsMem). 732 733select_fail_do(Opts) -> 734 T = ets_new(x,Opts), 735 ets:insert(T,{a,a}), 736 case (catch 737 ets:select(T,[{{a,'_'},[],[{snuffla}]}])) of 738 {'EXIT',{badarg,_}} -> 739 ok; 740 Else0 -> 741 exit({type,ets:info(T,type), 742 expected,'EXIT',got,Else0}) 743 end, 744 case (catch 745 ets:select(T,[{{b,'_'},[],[{snuffla}]}])) of 746 {'EXIT',{badarg,_}} -> 747 ok; 748 Else1 -> 749 exit({type,ets:info(T,type), 750 expected,'EXIT',got,Else1}) 751 end, 752 ets:delete(T). 753 754 755-define(S(T),ets:info(T,memory)). 756 757%% Whitebox test of ets:info(X, memory). 758memory(Config) when is_list(Config) -> 759 ok = chk_normal_tab_struct_size(), 760 repeat_for_opts(fun memory_do/1, [compressed]), 761 catch erts_debug:set_internal_state(available_internal_state, false). 762 763memory_do(Opts) -> 764 L = [T1,T2,T3,T4] = fill_sets_int(1000,Opts), 765 XR1 = case mem_mode(T1) of 766 {normal,_} -> {13836, 15346, 15346, 15346+6}; 767 {compressed,4} -> {11041, 12551, 12551, 12551+1}; 768 {compressed,8} -> {10050, 11560, 11560, 11560} 769 end, 770 XRes1 = adjust_xmem(L, XR1, 1), 771 Res1 = {?S(T1),?S(T2),?S(T3),?S(T4)}, 772 lists:foreach(fun(T) -> 773 Before = ets:info(T,size), 774 Key = 2, %894, %%ets:first(T), 775 Objs = ets:lookup(T,Key), 776 ets:delete(T,Key), 777 io:format("deleted key ~p from ~p changed size ~p to ~p: ~p\n", 778 [Key, ets:info(T,type), Before, ets:info(T,size), Objs]) 779 end, 780 L), 781 XR2 = case mem_mode(T1) of 782 {normal,_} -> {13826, 15337, 15337-9, 15337-3}; 783 {compressed,4} -> {11031, 12542, 12542-9, 12542-8}; 784 {compressed,8} -> {10040, 11551, 11551-9, 11551-9} 785 end, 786 XRes2 = adjust_xmem(L, XR2, 1), 787 Res2 = {?S(T1),?S(T2),?S(T3),?S(T4)}, 788 lists:foreach(fun(T) -> 789 Before = ets:info(T,size), 790 Key = 4, %802, %ets:first(T), 791 Objs = ets:lookup(T,Key), 792 ets:match_delete(T,{Key,'_'}), 793 io:format("match_deleted key ~p from ~p changed size ~p to ~p: ~p\n", 794 [Key, ets:info(T,type), Before, ets:info(T,size), Objs]) 795 end, 796 L), 797 XR3 = case mem_mode(T1) of 798 {normal,_} -> {13816, 15328, 15328-18, 15328-12}; 799 {compressed,4} -> {11021, 12533, 12533-18, 12533-17}; 800 {compressed,8} -> {10030, 11542, 11542-18, 11542-18} 801 end, 802 XRes3 = adjust_xmem(L, XR3, 1), 803 Res3 = {?S(T1),?S(T2),?S(T3),?S(T4)}, 804 lists:foreach(fun(T) -> 805 ets:delete_all_objects(T) 806 end, 807 L), 808 XRes4 = adjust_xmem(L, {50, 256, 256, 256}, 0), 809 Res4 = {?S(T1),?S(T2),?S(T3),?S(T4)}, 810 lists:foreach(fun(T) -> 811 ets:delete(T) 812 end, 813 L), 814 L2 = [T11,T12,T13,T14] = fill_sets_int(1000), 815 lists:foreach(fun(T) -> 816 ets:select_delete(T,[{'_',[],[true]}]) 817 end, 818 L2), 819 XRes5 = adjust_xmem(L2, {50, 256, 256, 256}, 0), 820 Res5 = {?S(T11),?S(T12),?S(T13),?S(T14)}, 821 io:format("XRes1 = ~p~n" 822 " Res1 = ~p~n~n" 823 "XRes2 = ~p~n" 824 " Res2 = ~p~n~n" 825 "XRes3 = ~p~n" 826 " Res3 = ~p~n~n" 827 "XRes4 = ~p~n" 828 " Res4 = ~p~n~n" 829 "XRes5 = ~p~n" 830 " Res5 = ~p~n~n", 831 [XRes1, Res1, 832 XRes2, Res2, 833 XRes3, Res3, 834 XRes4, Res4, 835 XRes5, Res5]), 836 XRes1 = Res1, 837 XRes2 = Res2, 838 XRes3 = Res3, 839 XRes4 = Res4, 840 XRes5 = Res5, 841 ok. 842 843mem_mode(T) -> 844 {case ets:info(T,compressed) of 845 true -> compressed; 846 false -> normal 847 end, 848 erlang:system_info(wordsize)}. 849 850chk_normal_tab_struct_size() -> 851 System = {os:type(), 852 os:version(), 853 erlang:system_info(wordsize), 854 erlang:system_info(smp_support), 855 erlang:system_info(heap_type)}, 856 io:format("System = ~p~n", [System]), 857 ok. 858 859adjust_xmem([_T1,_T2,_T3,_T4], {A0,B0,C0,D0} = _Mem0, EstCnt) -> 860 %% Adjust for 64-bit, smp, and os: 861 %% Table struct size may differ. 862 863 {TabSz, EstSz} = erts_debug:get_internal_state('DbTable_words'), 864 HTabSz = TabSz + EstCnt*EstSz, 865 OrdSetExtra = case erlang:system_info(wordsize) of 866 8 -> 40; % larger stack on 64 bit architectures 867 _ -> 0 868 end, 869 {A0+TabSz+OrdSetExtra, B0+HTabSz, C0+HTabSz, D0+HTabSz}. 870 871%% Misc. whitebox tests 872t_whitebox(Config) when is_list(Config) -> 873 EtsMem = etsmem(), 874 repeat_for_opts(fun whitebox_1/1), 875 repeat_for_opts(fun whitebox_1/1), 876 repeat_for_opts(fun whitebox_1/1), 877 repeat_for_opts(fun whitebox_2/1), 878 repeat_for_opts(fun whitebox_2/1), 879 repeat_for_opts(fun whitebox_2/1), 880 verify_etsmem(EtsMem). 881 882whitebox_1(Opts) -> 883 T=ets_new(x,[bag | Opts]), 884 ets:insert(T,[{du,glade},{ta,en}]), 885 ets:insert(T,[{hej,hopp2},{du,glade2},{ta,en2}]), 886 {_,C}=ets:match(T,{ta,'$1'},1), 887 ets:select(C), 888 ets:match(C), 889 ets:delete(T), 890 ok. 891 892whitebox_2(Opts) -> 893 T=ets_new(x,[ordered_set, {keypos,2} | Opts]), 894 T2=ets_new(x,[set, {keypos,2}| Opts]), 895 0 = ets:select_delete(T,[{{hej},[],[true]}]), 896 0 = ets:select_delete(T,[{{hej,hopp},[],[true]}]), 897 0 = ets:select_delete(T2,[{{hej},[],[true]}]), 898 0 = ets:select_delete(T2,[{{hej,hopp},[],[true]}]), 899 ets:delete(T), 900 ets:delete(T2), 901 ok. 902 903select_bound_chunk(_Config) -> 904 repeat_for_opts(fun select_bound_chunk_do/1, [all_types]). 905 906select_bound_chunk_do(Opts) -> 907 T = ets_new(x, Opts), 908 ets:insert(T, [{key, 1}]), 909 {[{key, 1}], '$end_of_table'} = ets:select(T, [{{key,1},[],['$_']}], 100000), 910 ok. 911 912 913%% Test ets:to/from_dets. 914t_ets_dets(Config) when is_list(Config) -> 915 repeat_for_opts(fun(Opts) -> t_ets_dets(Config,Opts) end). 916 917t_ets_dets(Config, Opts) -> 918 Fname = gen_dets_filename(Config,1), 919 (catch file:delete(Fname)), 920 {ok,DTab} = dets:open_file(testdets_1, 921 [{file, Fname}]), 922 ETab = ets_new(x,Opts), 923 filltabint(ETab,3000), 924 DTab = ets:to_dets(ETab,DTab), 925 ets:delete_all_objects(ETab), 926 0 = ets:info(ETab,size), 927 true = ets:from_dets(ETab,DTab), 928 3000 = ets:info(ETab,size), 929 ets:delete(ETab), 930 check_badarg(catch ets:to_dets(ETab,DTab), 931 ets, to_dets, [ETab,DTab]), 932 check_badarg(catch ets:from_dets(ETab,DTab), 933 ets, from_dets, [ETab,DTab]), 934 ETab2 = ets_new(x,Opts), 935 filltabint(ETab2,3000), 936 dets:close(DTab), 937 check_badarg(catch ets:to_dets(ETab2,DTab), 938 ets, to_dets, [ETab2,DTab]), 939 check_badarg(catch ets:from_dets(ETab2,DTab), 940 ets, from_dets, [ETab2,DTab]), 941 ets:delete(ETab2), 942 (catch file:delete(Fname)), 943 ok. 944 945check_badarg({'EXIT', {badarg, [{M,F,Args,_} | _]}}, M, F, Args) -> 946 true. 947 948%% Test ets:delete_all_objects/1. 949t_delete_all_objects(Config) when is_list(Config) -> 950 EtsMem = etsmem(), 951 repeat_for_opts_all_set_table_types(fun t_delete_all_objects_do/1), 952 verify_etsmem(EtsMem). 953 954get_kept_objects(T) -> 955 case ets:info(T,stats) of 956 {_,_,_,_,_,_,KO} -> 957 KO; 958 _ -> 959 0 960 end. 961 962t_delete_all_objects_do(Opts) -> 963 KeyRange = 4000, 964 T=ets_new(x, Opts, KeyRange), 965 filltabint(T,KeyRange), 966 O=ets:first(T), 967 ets:next(T,O), 968 ets:safe_fixtable(T,true), 969 true = ets:delete_all_objects(T), 970 '$end_of_table' = ets:next(T,O), 971 0 = ets:info(T,size), 972 case ets:info(T,type) of 973 ordered_set -> ok; 974 _ -> KeyRange = get_kept_objects(T) 975 end, 976 ets:safe_fixtable(T,false), 977 0 = ets:info(T,size), 978 0 = get_kept_objects(T), 979 filltabint(T, KeyRange), 980 KeyRange = ets:info(T,size), 981 true = ets:delete_all_objects(T), 982 0 = ets:info(T,size), 983 ets:delete(T), 984 985 %% Test delete_all_objects is atomic 986 T2 = ets_new(t_delete_all_objects, [public | Opts]), 987 Self = self(), 988 Inserters = [spawn_link(fun() -> inserter(T2, 1, Self) end) || _ <- [1,2,3,4]], 989 [receive {Ipid, running} -> ok end || Ipid <- Inserters], 990 991 ets:delete_all_objects(T2), 992 erlang:yield(), 993 [Ipid ! stop || Ipid <- Inserters], 994 Result = [receive {Ipid, stopped, Highest} -> {Ipid,Highest} end || Ipid <- Inserters], 995 996 %% Verify unbroken sequences of objects inserted _after_ ets:delete_all_objects. 997 Sum = lists:foldl(fun({Ipid, Highest}, AccSum) -> 998 %% ets:fun2ms(fun({{K,Ipid}}) when K =< Highest -> true end), 999 AliveMS = [{{{'$1',Ipid}},[{'=<','$1',{const,Highest}}],[true]}], 1000 Alive = ets:select_count(T2, AliveMS), 1001 Lowest = Highest - (Alive-1), 1002 1003 %% ets:fun2ms(fun({{K,Ipid}}) when K < Lowest -> true end) 1004 DeletedMS = [{{{'$1',Ipid}},[{'<','$1',{const,Lowest}}],[true]}], 1005 0 = ets:select_count(T2, DeletedMS), 1006 AccSum + Alive 1007 end, 1008 0, 1009 Result), 1010 ok = case ets:info(T2, size) of 1011 Sum -> ok; 1012 Size -> 1013 io:format("Sum = ~p\nSize = ~p\n", [Sum, Size]), 1014 {Sum,Size} 1015 end, 1016 1017 ets:delete(T2). 1018 1019inserter(T, Next, Papa) -> 1020 Wait = case Next of 1021 10*1000 -> 1022 Papa ! {self(), running}, 1023 0; 1024 100*1000 -> %% We most often don't reach this far 1025 io:format("Inserter ~p reached ~p objects\n", 1026 [self(), Next]), 1027 infinity; 1028 _ -> 1029 0 1030 end, 1031 1032 ets:insert(T, {{Next, self()}}), 1033 receive 1034 stop -> 1035 Papa ! {self(), stopped, Next}, 1036 ok 1037 after Wait -> 1038 inserter(T, Next+1, Papa) 1039 end. 1040 1041 1042%% Test ets:delete_object/2. 1043t_delete_object(Config) when is_list(Config) -> 1044 EtsMem = etsmem(), 1045 repeat_for_opts(fun t_delete_object_do/1), 1046 verify_etsmem(EtsMem). 1047 1048t_delete_object_do(Opts) -> 1049 T = ets_new(x,Opts), 1050 filltabint(T,4000), 1051 del_one_by_one_set(T,1,4001), 1052 filltabint(T,4000), 1053 del_one_by_one_set(T,4000,0), 1054 filltabint(T,4000), 1055 First = ets:first(T), 1056 Next = ets:next(T,First), 1057 ets:safe_fixtable(T,true), 1058 ets:delete_object(T,{First, integer_to_list(First)}), 1059 Next = ets:next(T,First), 1060 3999 = ets:info(T,size), 1061 1 = get_kept_objects(T), 1062 ets:safe_fixtable(T,false), 1063 3999 = ets:info(T,size), 1064 0 = get_kept_objects(T), 1065 ets:delete(T), 1066 T1 = ets_new(x,[ordered_set | Opts]), 1067 filltabint(T1,4000), 1068 del_one_by_one_set(T1,1,4001), 1069 filltabint(T1,4000), 1070 del_one_by_one_set(T1,4000,0), 1071 ets:delete(T1), 1072 T2 = ets_new(x,[bag | Opts]), 1073 filltabint2(T2,4000), 1074 del_one_by_one_bag(T2,1,4001), 1075 filltabint2(T2,4000), 1076 del_one_by_one_bag(T2,4000,0), 1077 ets:delete(T2), 1078 T3 = ets_new(x,[duplicate_bag | Opts]), 1079 filltabint3(T3,4000), 1080 del_one_by_one_dbag_1(T3,1,4001), 1081 filltabint3(T3,4000), 1082 del_one_by_one_dbag_1(T3,4000,0), 1083 filltabint(T3,4000), 1084 filltabint3(T3,4000), 1085 del_one_by_one_dbag_2(T3,1,4001), 1086 filltabint(T3,4000), 1087 filltabint3(T3,4000), 1088 del_one_by_one_dbag_2(T3,4000,0), 1089 1090 filltabint2(T3,4000), 1091 filltabint(T3,4000), 1092 del_one_by_one_dbag_3(T3,4000,0), 1093 ets:delete(T3), 1094 ok. 1095 1096make_init_fun(N) when N > 4000-> 1097 fun(read) -> 1098 end_of_input; 1099 (close) -> 1100 exit(close_not_expected) 1101 end; 1102make_init_fun(N) -> 1103 fun(read) -> 1104 case N rem 2 of 1105 0 -> 1106 {[{N, integer_to_list(N)}, {N, integer_to_list(N)}], 1107 make_init_fun(N + 1)}; 1108 1 -> 1109 {[], make_init_fun(N + 1)} 1110 end; 1111 (close) -> 1112 exit(close_not_expected) 1113 end. 1114 1115%% Test ets:init_table/2. 1116t_init_table(Config) when is_list(Config)-> 1117 EtsMem = etsmem(), 1118 repeat_for_opts(fun t_init_table_do/1), 1119 verify_etsmem(EtsMem). 1120 1121t_init_table_do(Opts) -> 1122 T = ets_new(x,[duplicate_bag | Opts]), 1123 filltabint(T,4000), 1124 ets:init_table(T, make_init_fun(1)), 1125 del_one_by_one_dbag_1(T,4000,0), 1126 ets:delete(T), 1127 ok. 1128 1129do_fill_dbag_using_lists(T,0) -> 1130 T; 1131do_fill_dbag_using_lists(T,N) -> 1132 ets:insert(T,[{N,integer_to_list(N)}, 1133 {N + N rem 2,integer_to_list(N + N rem 2)}]), 1134 do_fill_dbag_using_lists(T,N - 1). 1135 1136 1137%% Test the insert_new function. 1138t_insert_new(Config) when is_list(Config) -> 1139 EtsMem = etsmem(), 1140 L = fill_sets_int(1000) ++ fill_sets_int(1000,[{write_concurrency,true}]), 1141 lists:foreach(fun(Tab) -> 1142 false = ets:insert_new(Tab,{2,"2"}), 1143 true = ets:insert_new(Tab,{2002,"2002"}), 1144 false = ets:insert_new(Tab,{2002,"2002"}), 1145 true = ets:insert(Tab,{2002,"2002"}), 1146 false = ets:insert_new(Tab,[{2002,"2002"}]), 1147 false = ets:insert_new(Tab,[{2002,"2002"}, 1148 {2003,"2003"}]), 1149 false = ets:insert_new(Tab,[{2001,"2001"}, 1150 {2002,"2002"}, 1151 {2003,"2003"}]), 1152 false = ets:insert_new(Tab,[{2001,"2001"}, 1153 {2002,"2002"}]), 1154 true = ets:insert_new(Tab,[{2001,"2001"}, 1155 {2003,"2003"}]), 1156 false = ets:insert_new(Tab,{2001,"2001"}), 1157 false = ets:insert_new(Tab,{2002,"2002"}), 1158 false = ets:insert_new(Tab,{2003,"2003"}), 1159 true = ets:insert_new(Tab,{2004,"2004"}), 1160 true = ets:insert_new(Tab,{2000,"2000"}), 1161 true = ets:insert_new(Tab,[{2005,"2005"}, 1162 {2006,"2006"}, 1163 {2007,"2007"}]), 1164 Num = 1165 case ets:info(Tab,type) of 1166 bag -> 1167 true = 1168 ets:insert(Tab,{2004,"2004-2"}), 1169 false = 1170 ets:insert_new(Tab,{2004,"2004-3"}), 1171 1009; 1172 duplicate_bag -> 1173 true = 1174 ets:insert(Tab,{2004,"2004"}), 1175 false = 1176 ets:insert_new(Tab,{2004,"2004"}), 1177 1010; 1178 _ -> 1179 1008 1180 end, 1181 Num = ets:info(Tab,size), 1182 List = ets:tab2list(Tab), 1183 ets:delete_all_objects(Tab), 1184 true = ets:insert_new(Tab,List), 1185 false = ets:insert_new(Tab,List), 1186 ets:delete(Tab) 1187 end, 1188 L), 1189 verify_etsmem(EtsMem). 1190 1191%% Test ets:insert/2 with list of objects into duplicate bag table. 1192t_insert_list(Config) when is_list(Config) -> 1193 EtsMem = etsmem(), 1194 repeat_for_opts(fun t_insert_list_do/1), 1195 verify_etsmem(EtsMem). 1196 1197t_insert_list_do(Opts) -> 1198 T = ets_new(x,[duplicate_bag | Opts]), 1199 do_fill_dbag_using_lists(T,4000), 1200 del_one_by_one_dbag_2(T,4000,0), 1201 ets:delete(T). 1202 1203% Insert a long list twice in a bag 1204t_insert_list_bag(Config) when is_list(Config) -> 1205 EtsMem = etsmem(), 1206 repeat_for_opts(fun t_insert_list_bag_do/1, 1207 [write_concurrency, compressed]), 1208 verify_etsmem(EtsMem). 1209 1210t_insert_list_bag_do(Opts) -> 1211 T = ets:new(t, [bag | Opts]), 1212 ListSize = 25000, 1213 List = [ {N} || N <- lists:seq(1, ListSize)], 1214 ets:insert(T, List), 1215 ets:insert(T, List), 1216 ListSize = ets:info(T, size), 1217 1218 %% Insert different sized objects to better test (compressed) object comparison 1219 List2 = [begin Bits=(N rem 71), {N div 7, <<N:Bits>>} end || {N} <- List], 1220 ets:insert(T, List2), 1221 List2Sz = ListSize * 2, 1222 List2Sz = ets:info(T, size), 1223 ets:delete(T), 1224 ok. 1225 1226% Insert a long list twice in a duplicate_bag 1227t_insert_list_duplicate_bag(Config) when is_list(Config) -> 1228 EtsMem = etsmem(), 1229 T = ets:new(t, [duplicate_bag]), 1230 ListSize = 25000, 1231 List = [ {N} || N <- lists:seq(1, ListSize)], 1232 ets:insert(T, List), 1233 ets:insert(T, List), 1234 DoubleListSize = ListSize * 2, 1235 DoubleListSize = ets:info(T, size), 1236 ets:delete(T), 1237 verify_etsmem(EtsMem). 1238 1239%% Test ets:insert/2 with list of objects into set tables. 1240t_insert_list_set(Config) when is_list(Config) -> 1241 EtsMem = etsmem(), 1242 repeat_for_opts(fun t_insert_list_set_do/1, [set_types]), 1243 verify_etsmem(EtsMem). 1244 1245t_insert_list_set_do(Opts) -> 1246 Nr = 2, 1247 t_insert_list_set_do(Opts, fun ets_insert_with_check/2, Nr, 1, Nr+1), 1248 t_insert_list_set_do(Opts, fun ets_insert_with_check/2, Nr*2, 2, Nr*2), 1249 InsertNewWithCheck = 1250 fun(T,E) -> 1251 Res = ets:insert_new(T,E), 1252 Seq = element(1, lists:nth(1, E)), 1253 case Seq rem 2 =:= 0 of 1254 true -> Res = false; 1255 false -> Res = true 1256 end 1257 end, 1258 t_insert_list_set_do(Opts, InsertNewWithCheck, Nr, 1, Nr), 1259 t_insert_list_set_do(Opts, fun ets:insert_new/2, Nr*2, 2, Nr*2), 1260 ok. 1261 1262t_insert_list_set_do(Opts, InsertFun, Nr, Step, ExpectedSize) -> 1263 T = ets_new(x,Opts), 1264 [InsertFun(T,[{X,X}, {X+1,X}]) || X <- lists:seq(1,Nr,Step)], 1265 ExpectedSize = ets:info(T,size), 1266 ets:delete(T). 1267 1268%% Test ets:insert/2 with list of objects into set tables in parallel. 1269t_insert_list_parallel(Config) when is_list(Config) -> 1270 EtsMem = etsmem(), 1271 repeat_for_opts(fun t_insert_list_parallel_do/1, [[public], set_types]), 1272 verify_etsmem(EtsMem). 1273 1274ets_insert_with_check(Table, ToInsert) -> 1275 true = ets:insert(Table, ToInsert), 1276 true. 1277 1278ets_insert_new_with_check(Table, ToInsert) -> 1279 ExpectedRes = 1280 case put(is_first_insert_for_list, true) of 1281 undefined -> true; 1282 true -> false 1283 end, 1284 ExpectedRes = ets:insert_new(Table, ToInsert), 1285 ExpectedRes. 1286 1287t_insert_list_parallel_do(Opts) -> 1288 [(fun(I) -> 1289 t_insert_list_parallel_do(Opts, I, 2, 100, 500), 1290 t_insert_list_parallel_do(Opts, I, 10, 100, 100), 1291 t_insert_list_parallel_do(Opts, I, 1000, 100, 10), 1292 t_insert_list_parallel_do(Opts, I, 50000, 3, 1) 1293 end)(InsertFun) || InsertFun <- [fun ets_insert_with_check/2, 1294 fun ets_insert_new_with_check/2]]. 1295 1296t_insert_list_parallel_do(Opts, InsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess) -> 1297 T = ets_new(x,Opts), 1298 t_insert_list_parallel_do_helper(self(), T, 0, InsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess), 1299 receive done -> ok end, 1300 ExpectedSize = ListLength * NrOfProcesses, 1301 ExpectedSize = length(ets:match_object(T, {'$0', '$1'})), 1302 ExpectedSize = ets:info(T, size), 1303 ets:delete(T), 1304 ok. 1305 1306t_insert_list_delete_parallel(Config) when is_list(Config) -> 1307 EtsMem = etsmem(), 1308 repeat_for_opts(fun t_insert_list_delete_parallel_do/1, [[public], set_types]), 1309 verify_etsmem(EtsMem). 1310 1311t_insert_list_delete_parallel_do(Opts) -> 1312 [(fun(I) -> 1313 t_insert_list_delete_parallel_do(Opts, I, 30, 32, 1000000), 1314 t_insert_list_delete_parallel_do(Opts, I, 300, 8, 1000000), 1315 t_insert_list_delete_parallel_do(Opts, I, 3000, 4, 1000000), 1316 t_insert_list_delete_parallel_do(Opts, I, 9000, 4, 1000000) 1317 end)(InsertFun) || InsertFun <- [fun ets_insert_with_check/2, 1318 fun ets_insert_new_with_check/2]], 1319 ok. 1320 1321t_insert_list_delete_parallel_do(Opts, InsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess) -> 1322 T = ets_new(x,Opts), 1323 CompletedInsertsCtr = counters:new(1,[]), 1324 NewInsertFun = 1325 fun(Table, ToInsert) -> 1326 try 1327 InsertFun(Table, ToInsert), 1328 counters:add(CompletedInsertsCtr, 1, 1) 1329 catch 1330 error:badarg -> put(stop,yes) 1331 end 1332 end, 1333 Self = self(), 1334 spawn(fun()-> 1335 t_insert_list_parallel_do_helper(self(), T, 0, NewInsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess), 1336 receive done -> Self ! done_parallel_insert end 1337 end), 1338 receive after 3 -> ok end, 1339 spawn(fun()-> 1340 spawn(fun()-> 1341 receive after 7 -> ok end, 1342 ets:delete(T), 1343 Self ! done_delete 1344 end) 1345 end), 1346 receive done_delete -> ok end, 1347 receive done_parallel_insert -> ok end, 1348 io:format("~p/~p completed", 1349 [counters:get(CompletedInsertsCtr, 1), 1350 NrOfProcesses * NrOfInsertsPerProcess]). 1351 1352 1353t_insert_list_parallel_do_helper(Parent, T, StartKey, InsertFun, ListLength, 1, NrOfInsertsPerProcess) -> 1354 try 1355 repeat(fun()-> 1356 case get(stop) of 1357 yes -> throw(end_repeat); 1358 _ -> ok 1359 end, 1360 InsertFun(T,[{X,X} || X <- lists:seq(StartKey,StartKey+ListLength-1,1)]) 1361 end, NrOfInsertsPerProcess) 1362 catch 1363 throw:end_repeat -> ok 1364 end, 1365 Parent ! done; 1366t_insert_list_parallel_do_helper(Parent, T, StartKey, InsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess) -> 1367 Self = self(), 1368 spawn(fun() -> 1369 t_insert_list_parallel_do_helper(Self, 1370 T, 1371 StartKey, 1372 InsertFun, 1373 ListLength, 1374 NrOfProcesses div 2, 1375 NrOfInsertsPerProcess) end), 1376 spawn(fun() -> 1377 t_insert_list_parallel_do_helper(Self, 1378 T, 1379 StartKey + ListLength*(NrOfProcesses div 2), 1380 InsertFun, 1381 ListLength, 1382 (NrOfProcesses div 2) + (NrOfProcesses rem 2), 1383 NrOfInsertsPerProcess) 1384 end), 1385 receive done -> ok end, 1386 receive done -> ok end, 1387 Parent ! done. 1388 1389t_insert_list_delete_set(Config) when is_list(Config) -> 1390 EtsMem = etsmem(), 1391 repeat_for_opts(fun t_insert_list_delete_set_do/1, [[public],set_types]), 1392 verify_etsmem(EtsMem). 1393 1394t_insert_list_delete_set_do(Opts) -> 1395 [(fun(I) -> 1396 t_insert_list_delete_set_do(Opts, I, 1000000, 1, 1), 1397 t_insert_list_delete_set_do(Opts, I, 100000, 10, 5), 1398 t_insert_list_delete_set_do(Opts, I, 10000, 100, 50), 1399 t_insert_list_delete_set_do(Opts, I, 1000, 1000, 500) 1400 end)(InsertFun) || InsertFun <- [fun ets_insert_with_check/2, 1401 fun ets_insert_new_with_check/2]], 1402 ok. 1403 1404 1405t_insert_list_delete_set_do(Opts, InsertFun, ListLength, NrOfTables, NrOfInserts) -> 1406 CompletedInsertsCtr = counters:new(1,[]), 1407 Parent = self(), 1408 [(fun() -> 1409 T = ets_new(x,Opts), 1410 spawn( 1411 fun() -> 1412 try 1413 repeat( 1414 fun() -> 1415 InsertFun(T,[{Z,Z} || 1416 Z <- lists:seq(1,ListLength)]), 1417 counters:add(CompletedInsertsCtr, 1, 1)%, 1418 end, NrOfInserts) 1419 catch 1420 error:badarg -> ok 1421 end, 1422 Parent ! done 1423 end), 1424 receive after 1 -> ok end, 1425 ets:delete(T) 1426 end)() || _ <- lists:seq(1,NrOfTables)], 1427 [receive done -> ok end || _ <- lists:seq(1,NrOfTables)], 1428 io:format("~p/~p completed", 1429 [counters:get(CompletedInsertsCtr, 1), 1430 NrOfTables * NrOfInserts]). 1431 1432 1433t_insert_list_kill_process(Config) when is_list(Config) -> 1434 EtsMem = etsmem(), 1435 repeat_for_opts(fun t_insert_list_kill_process_do/1, [[public], set_types]), 1436 verify_etsmem(EtsMem). 1437 1438 1439t_insert_list_kill_process_do(Opts) -> 1440 [(fun(I) -> 1441 [(fun(Time) -> 1442 T = ets_new(x,Opts), 1443 List = lists:seq(1,600000), 1444 TupleList = [{E,E} || E <- List], 1445 Pid = spawn(fun() -> I(T, TupleList) end), 1446 receive after Time -> ok end, 1447 exit(Pid, kill), 1448 ets:delete(T) 1449 end)(TheTime) || TheTime <- [1,3,5] ++ lists:seq(7,29,7)] 1450 end)(InsertFun) || InsertFun <- [fun ets:insert/2, 1451 fun ets:insert_new/2]], 1452 ok. 1453 1454%% Test interface of ets:test_ms/2. 1455t_test_ms(Config) when is_list(Config) -> 1456 EtsMem = etsmem(), 1457 {ok,[a,b]} = ets:test_ms({a,b}, 1458 [{{'$1','$2'},[{'<','$1','$2'}],['$$']}]), 1459 {ok,false} = ets:test_ms({a,b}, 1460 [{{'$1','$2'},[{'>','$1','$2'}],['$$']}]), 1461 Tpl = {a,gb_sets:new()}, 1462 {ok,Tpl} = ets:test_ms(Tpl, [{{'_','_'}, [], ['$_']}]), % OTP-10190 1463 {error,[{error,String}]} = ets:test_ms({a,b}, 1464 [{{'$1','$2'}, 1465 [{'flurp','$1','$2'}], 1466 ['$$']}]), 1467 true = (if is_list(String) -> true; true -> false end), 1468 verify_etsmem(EtsMem). 1469 1470%% Test the select reverse BIFs. 1471t_select_reverse(Config) when is_list(Config) -> 1472 Table = ets_new(xxx, [ordered_set]), 1473 filltabint(Table,1000), 1474 A = lists:reverse(ets:select(Table,[{{'$1', '_'}, 1475 [{'>', 1476 {'rem', 1477 '$1', 5}, 1478 2}], 1479 ['$_']}])), 1480 A = ets:select_reverse(Table,[{{'$1', '_'}, 1481 [{'>', 1482 {'rem', 1483 '$1', 5}, 1484 2}], 1485 ['$_']}]), 1486 A = reverse_chunked(Table,[{{'$1', '_'}, 1487 [{'>', 1488 {'rem', 1489 '$1', 5}, 1490 2}], 1491 ['$_']}],3), 1492 %% A set/bag/duplicate_bag should get the same result regardless 1493 %% of select or select_reverse 1494 Table2 = ets_new(xxx, [set]), 1495 filltabint(Table2,1000), 1496 Table3 = ets_new(xxx, [bag]), 1497 filltabint(Table3,1000), 1498 Table4 = ets_new(xxx, [duplicate_bag]), 1499 filltabint(Table4,1000), 1500 lists:map(fun(Tab) -> 1501 B = ets:select(Tab,[{{'$1', '_'}, 1502 [{'>', 1503 {'rem', 1504 '$1', 5}, 1505 2}], 1506 ['$_']}]), 1507 B = ets:select_reverse(Tab,[{{'$1', '_'}, 1508 [{'>', 1509 {'rem', 1510 '$1', 5}, 1511 2}], 1512 ['$_']}]) 1513 end,[Table2, Table3, Table4]), 1514 ok. 1515 1516 1517 1518reverse_chunked(T,MS,N) -> 1519 do_reverse_chunked(ets:select_reverse(T,MS,N),[]). 1520 1521do_reverse_chunked('$end_of_table',Acc) -> 1522 lists:reverse(Acc); 1523do_reverse_chunked({L,C},Acc) -> 1524 NewAcc = lists:reverse(L)++Acc, 1525 do_reverse_chunked(ets:select_reverse(C), NewAcc). 1526 1527 1528%% Test the ets:select_delete/2 and ets:select_count/2 BIFs. 1529t_select_delete(Config) when is_list(Config) -> 1530 ct:timetrap({minutes,30}), %% valgrind needs a lot 1531 EtsMem = etsmem(), 1532 Tables = fill_sets_int(10000) ++ fill_sets_int(10000,[{write_concurrency,true}]), 1533 lists:foreach 1534 (fun(Table) -> 1535 4000 = ets:select_count(Table,[{{'$1', '_'}, 1536 [{'>', 1537 {'rem', 1538 '$1', 5}, 1539 2}], 1540 [true]}]), 1541 4000 = ets:select_delete(Table,[{{'$1', '_'}, 1542 [{'>', 1543 {'rem', 1544 '$1', 5}, 1545 2}], 1546 [true]}]), 1547 check(Table, 1548 fun({N,_}) when (N rem 5) =< 2 -> 1549 true; 1550 (_) -> 1551 false 1552 end, 1553 6000) 1554 1555 end, 1556 Tables), 1557 lists:foreach 1558 (fun(Table) -> 1559 ets:select_delete(Table,[{'_',[],[true]}]), 1560 xfilltabint(Table,4000), 1561 successive_delete(Table,1,4001,bound), 1562 0 = ets:info(Table,size), 1563 xfilltabint(Table,4000), 1564 successive_delete(Table,4000,0, bound), 1565 0 = ets:info(Table,size), 1566 xfilltabint(Table,4000), 1567 successive_delete(Table,1,4001,unbound), 1568 0 = ets:info(Table,size), 1569 xfilltabint(Table,4000), 1570 successive_delete(Table,4000,0, unbound), 1571 0 = ets:info(Table,size) 1572 1573 end, 1574 Tables), 1575 lists:foreach 1576 (fun(Table) -> 1577 F = case ets:info(Table,type) of 1578 X when X == bag; X == duplicate_bag -> 1579 2; 1580 _ -> 1581 1 1582 end, 1583 xfilltabstr(Table, 4000), 1584 1000 = ets:select_count(Table, 1585 [{{[$3 | '$1'], '_'}, 1586 [{'==', 1587 {'length', '$1'}, 1588 3}],[true]}]) div F, 1589 1000 = ets:select_delete(Table, 1590 [{{[$3 | '$1'], '_'}, 1591 [{'==', 1592 {'length', '$1'}, 1593 3}],[true]}]) div F, 1594 check(Table, fun({[3,_,_,_],_}) -> false; 1595 (_) -> true 1596 end, 3000*F), 1597 8 = ets:select_count(Table, 1598 [{{"7",'_'},[],[false]}, 1599 {{['_'], '_'}, 1600 [],[true]}]) div F, 1601 8 = ets:select_delete(Table, 1602 [{{"7",'_'},[],[false]}, 1603 {{['_'], '_'}, 1604 [],[true]}]) div F, 1605 check(Table, fun({"7",_}) -> true; 1606 ({[_],_}) -> false; 1607 (_) -> true 1608 end, 2992*F), 1609 xfilltabstr(Table, 4000), 1610 %% This happens to be interesting for other select types too 1611 200 = length(ets:select(Table, 1612 [{{[$3,'_','_'],'_'}, 1613 [],[true]}, 1614 {{[$1,'_','_'],'_'}, 1615 [],[true]}])) div F, 1616 200 = ets:select_count(Table, 1617 [{{[$3,'_','_'],'_'}, 1618 [],[true]}, 1619 {{[$1,'_','_'],'_'}, 1620 [],[true]}]) div F, 1621 200 = length(element(1,ets:select(Table, 1622 [{{[$3,'_','_'],'_'}, 1623 [],[true]}, 1624 {{[$1,'_','_'],'_'}, 1625 [],[true]}], 1626 1000))) div F, 1627 200 = length( 1628 ets:select_reverse(Table, 1629 [{{[$3,'_','_'],'_'}, 1630 [],[true]}, 1631 {{[$1,'_','_'],'_'}, 1632 [],[true]}])) div F, 1633 200 = length( 1634 element(1, 1635 ets:select_reverse 1636 (Table, 1637 [{{[$3,'_','_'],'_'}, 1638 [],[true]}, 1639 {{[$1,'_','_'],'_'}, 1640 [],[true]}], 1641 1000))) div F, 1642 200 = ets:select_delete(Table, 1643 [{{[$3,'_','_'],'_'}, 1644 [],[true]}, 1645 {{[$1,'_','_'],'_'}, 1646 [],[true]}]) div F, 1647 0 = ets:select_count(Table, 1648 [{{[$3,'_','_'],'_'}, 1649 [],[true]}, 1650 {{[$1,'_','_'],'_'}, 1651 [],[true]}]) div F, 1652 check(Table, fun({[$3,_,_],_}) -> false; 1653 ({[$1,_,_],_}) -> false; 1654 (_) -> true 1655 end, 3800*F) 1656 end, 1657 Tables), 1658 lists:foreach(fun(Tab) -> ets:delete(Tab) end,Tables), 1659 verify_etsmem(EtsMem). 1660 1661%% Tests the ets:select_replace/2 BIF 1662t_select_replace(Config) when is_list(Config) -> 1663 EtsMem = etsmem(), 1664 repeat_for_opts(fun do_select_replace/1), 1665 verify_etsmem(EtsMem). 1666 1667do_select_replace(Opts) -> 1668 Tables = fill_sets_intup(10000, Opts), 1669 1670 TestFun = fun (Table, TableType) when TableType =:= bag -> 1671 % Operation not supported; bag implementation 1672 % presented both semantic consistency and performance issues. 1673 10000 = ets:select_delete(Table, [{'_',[],[true]}]); 1674 1675 (Table, TableType) -> 1676 % Invalid replacement doesn't keep the key 1677 MatchSpec1 = [{{{'$1','$3'}, '$2'}, 1678 [{'=:=', {'band', '$1', 2#11}, 2#11}, 1679 {'=/=', {'hd', '$2'}, $x}], 1680 [{{{{'$2','$3'}}, '$1'}}]}], 1681 {'EXIT',{badarg,_}} = (catch ets:select_replace(Table, MatchSpec1)), 1682 1683 % Invalid replacement doesn't keep the key (even though it would be the same value) 1684 MatchSpec2 = [{{{'$1','$3'}, '$2'}, 1685 [{'=:=', {'band', '$1', 2#11}, 2#11}], 1686 [{{{{{'+', '$1', 0},'$3'}}, '$2'}}]}, 1687 {{{'$1','$3'}, '$2'}, 1688 [{'=/=', {'band', '$1', 2#11}, 2#11}], 1689 [{{{{{'-', '$1', 0},'$3'}}, '$2'}}]}], 1690 {'EXIT',{badarg,_}} = (catch ets:select_replace(Table, MatchSpec2)), 1691 1692 % Invalid replacement changes key to float equivalent 1693 MatchSpec3 = [{{{'$1','$3'}, '$2'}, 1694 [{'=:=', {'band', '$1', 2#11}, 2#11}, 1695 {'=/=', {'hd', '$2'}, $x}], 1696 [{{{{{'*', '$1', 1.0},'$3'}}, '$2'}}]}], 1697 {'EXIT',{badarg,_}} = (catch ets:select_replace(Table, MatchSpec3)), 1698 1699 % Replacements are differently-sized tuples 1700 MatchSpec4_A = [{{{'$1','$3'},'$2'}, 1701 [{'<', {'rem', '$1', 5}, 2}], 1702 [{{{{'$1','$3'}}, [$x | '$2'], stuff}}]}], 1703 MatchSpec4_B = [{{{'$1','$3'},'$2','_'}, 1704 [], 1705 [{{{{'$1','$3'}},'$2'}}]}], 1706 4000 = ets:select_replace(Table, MatchSpec4_A), 1707 4000 = ets:select_replace(Table, MatchSpec4_B), 1708 1709 % Replacement is the same tuple 1710 MatchSpec5 = [{{{'$1','$3'}, '$2'}, 1711 [{'>', {'rem', '$1', 5}, 3}], 1712 ['$_']}], 1713 2000 = ets:select_replace(Table, MatchSpec5), 1714 1715 % Replacement reconstructs an equal tuple 1716 MatchSpec6 = [{{{'$1','$3'}, '$2'}, 1717 [{'>', {'rem', '$1', 5}, 3}], 1718 [{{{{'$1','$3'}}, '$2'}}]}], 1719 2000 = ets:select_replace(Table, MatchSpec6), 1720 1721 % Replacement uses {element,KeyPos,T} for key 1722 2000 = ets:select_replace(Table, 1723 [{{{'$1','$3'}, '$2'}, 1724 [{'>', {'rem', '$1', 5}, 3}], 1725 [{{{element, 1, '$_'}, '$2'}}]}]), 1726 1727 % Replacement uses wrong {element,KeyPos,T} for key 1728 {'EXIT',{badarg,_}} = (catch ets:select_replace(Table, 1729 [{{{'$1','$3'}, '$2'}, 1730 [], 1731 [{{{element, 2, '$_'}, '$2'}}]}])), 1732 1733 check(Table, 1734 fun ({{N,_}, [$x, C | _]}) when ((N rem 5) < 2) -> (C >= $0) andalso (C =< $9); 1735 ({{N,_}, [C | _]}) when is_float(N) -> (C >= $0) andalso (C =< $9); 1736 ({{N,_}, [C | _]}) when ((N rem 5) > 3) -> (C >= $0) andalso (C =< $9); 1737 ({_, [C | _]}) -> (C >= $0) andalso (C =< $9) 1738 end, 1739 10000), 1740 1741 % Replace unbound range (>) 1742 MatchSpec7 = [{{{'$1','$3'}, '$2'}, 1743 [{'>', '$1', 7000}], 1744 [{{{{'$1','$3'}}, {{gt_range, '$2'}}}}]}], 1745 3000 = ets:select_replace(Table, MatchSpec7), 1746 1747 % Replace unbound range (<) 1748 MatchSpec8 = [{{{'$1','$3'}, '$2'}, 1749 [{'<', '$1', 3000}], 1750 [{{{{'$1','$3'}}, {{le_range, '$2'}}}}]}], 1751 case TableType of 1752 ordered_set -> 2999 = ets:select_replace(Table, MatchSpec8); 1753 set -> 2999 = ets:select_replace(Table, MatchSpec8); 1754 duplicate_bag -> 2998 = ets:select_replace(Table, MatchSpec8) 1755 end, 1756 1757 % Replace bound range 1758 MatchSpec9 = [{{{'$1','$3'}, '$2'}, 1759 [{'>=', '$1', 3001}, 1760 {'<', '$1', 7000}], 1761 [{{{{'$1','$3'}}, {{range, '$2'}}}}]}], 1762 case TableType of 1763 ordered_set -> 3999 = ets:select_replace(Table, MatchSpec9); 1764 set -> 3999 = ets:select_replace(Table, MatchSpec9); 1765 duplicate_bag -> 3998 = ets:select_replace(Table, MatchSpec9) 1766 end, 1767 1768 % Replace particular keys 1769 MatchSpec10 = [{{{'$1','$3'}, '$2'}, 1770 [{'==', '$1', 3000}], 1771 [{{{{'$1','$3'}}, {{specific1, '$2'}}}}]}, 1772 {{{'$1','$3'}, '$2'}, 1773 [{'==', '$1', 7000}], 1774 [{{{{'$1','$3'}}, {{specific2, '$2'}}}}]}], 1775 case TableType of 1776 ordered_set -> 2 = ets:select_replace(Table, MatchSpec10); 1777 set -> 2 = ets:select_replace(Table, MatchSpec10); 1778 duplicate_bag -> 4 = ets:select_replace(Table, MatchSpec10) 1779 end, 1780 1781 check(Table, 1782 fun ({{N,_}, {gt_range, _}}) -> N > 7000; 1783 ({{N,_}, {le_range, _}}) -> N < 3000; 1784 ({{N,_}, {range, _}}) -> (N >= 3001) andalso (N < 7000); 1785 ({{N,_}, {specific1, _}}) -> N == 3000; 1786 ({{N,_}, {specific2, _}}) -> N == 7000 1787 end, 1788 10000), 1789 1790 10000 = ets:select_delete(Table, [{'_',[],[true]}]), 1791 check(Table, fun (_) -> false end, 0) 1792 end, 1793 1794 lists:foreach( 1795 fun(Table) -> 1796 TestFun(Table, ets:info(Table, type)), 1797 ets:delete(Table) 1798 end, 1799 Tables), 1800 1801 %% Test key-safe match-specs are accepted 1802 BigNum = (123 bsl 123), 1803 RefcBin = list_to_binary(lists:seq(1,?heap_binary_size+1)), 1804 Terms = [a, "hej", 123, 1.23, BigNum , <<"123">>, RefcBin, TestFun, self()], 1805 EqPairs = fun(X,Y) -> 1806 [{ '$1', '$1'}, 1807 { {X, Y}, {{X, Y}}}, 1808 { {'$1', Y}, {{'$1', Y}}}, 1809 { {{X, Y}}, {{{{X, Y}}}}}, 1810 { {X}, {{X}}}, 1811 { X, {const, X}}, 1812 { {X,Y}, {const, {X,Y}}}, 1813 { {X}, {const, {X}}}, 1814 { {X, Y}, {{X, {const, Y}}}}, 1815 { {X, {Y,'$1'}}, {{{const, X}, {{Y,'$1'}}}}}, 1816 { [X, Y | '$1'], [X, Y | '$1']}, 1817 { [{X, '$1'}, Y], [{{X, '$1'}}, Y]}, 1818 { [{X, Y} | '$1'], [{const, {X, Y}} | '$1']}, 1819 { [$p,$r,$e,$f,$i,$x | '$1'], [$p,$r,$e,$f,$i,$x | '$1']}, 1820 { {[{X,Y}]}, {{[{{X,Y}}]}}}, 1821 { {[{X,Y}]}, {{{const, [{X,Y}]}}}}, 1822 { {[{X,Y}]}, {{[{const,{X,Y}}]}}} 1823 ] 1824 end, 1825 1826 T2 = ets:new(x, Opts), 1827 [lists:foreach(fun({A, B}) -> 1828 %% just check that matchspec is accepted 1829 0 = ets:select_replace(T2, [{{A, '$2', '$3'}, [], [{{B, '$3', '$2'}}]}]) 1830 end, 1831 EqPairs(X,Y)) || X <- Terms, Y <- Terms], 1832 1833 %% Test key-unsafe matchspecs are rejected 1834 NeqPairs = fun(X, Y) -> 1835 [{'$1', '$2'}, 1836 {{X, Y}, {X, Y}}, 1837 {{{X, Y}}, {{{X, Y}}}}, 1838 {{X}, {{{X}}}}, 1839 {{const, X}, {const, X}}, 1840 {{const, {X,Y}}, {const, {X,Y}}}, 1841 {'$1', {const, '$1'}}, 1842 {{X}, {const, {{X}}}}, 1843 {{X, {Y,'$1'}}, {{{const, X}, {Y,'$1'}}}}, 1844 {[X, Y | '$1'], [X, Y]}, 1845 {[X, Y], [X, Y | '$1']}, 1846 {[{X, '$1'}, Y], [{X, '$1'}, Y]}, 1847 {[$p,$r,$e,$f,$i,$x | '$1'], [$p,$r,$e,$f,$I,$x | '$1']}, 1848 { {[{X,Y}]}, {{[{X,Y}]}}}, 1849 { {[{X,Y}]}, {{{const, [{{X,Y}}]}}}}, 1850 { {[{X,Y}]}, {{[{const,{{X,Y}}}]}}}, 1851 {'_', '_'}, 1852 {'$_', '$_'}, 1853 {'$$', '$$'}, 1854 {#{}, #{}}, 1855 {#{X => '$1'}, #{X => '$1'}} 1856 ] 1857 end, 1858 1859 [lists:foreach(fun({A, B}) -> 1860 %% just check that matchspec is rejected 1861 {'EXIT',{badarg,_}} = (catch ets:select_replace(T2, [{{A, '$2', '$3'}, [], [{{B, '$3', '$2'}}]}])) 1862 end, 1863 NeqPairs(X,Y)) || X <- Terms, Y <- Terms], 1864 1865 1866 %% Wrap entire tuple with 'const' 1867 [[begin 1868 Old = {Key, 1, 2}, 1869 ets:insert(T2, Old), 1870 1 = ets:select_replace(T2, [{Old, [], [{const, New}]}]), 1871 [New] = ets:lookup(T2, Key), 1872 ets:delete(T2, Key) 1873 end || New <- [{Key, 1, 2}, {Key, 3, 4}, {Key, 1}, {Key, 1, 2, 3}, {Key}] 1874 ] 1875 || Key <- [{1, tuple}, {nested, {tuple, {a,b}}} | Terms]], 1876 1877 %% 'const' wrap does not work with maps or variables in keys 1878 [[begin 1879 Old = {Key, 1, 2}, 1880 {'EXIT',{badarg,_}} = (catch ets:select_replace(T2, [{Old, [], [{const, New}]}])) 1881 end || New <- [{Key, 1, 2}, {Key, 3, 4}, {Key, 1}, {Key, 1, 2, 3}, {Key}] 1882 ] 1883 || Key <- [#{a => 1}, {nested, #{a => 1}}, '$1']], 1884 1885 1886 ets:delete(T2), 1887 ok. 1888 1889%% OTP-15346: Bug caused select_replace of bound key to corrupt static stack 1890%% used by ets:next and ets:prev. 1891t_select_replace_next_bug(Config) when is_list(Config) -> 1892 T = ets:new(k, [ordered_set]), 1893 [ets:insert(T, {I, value}) || I <- lists:seq(1,10)], 1894 1 = ets:first(T), 1895 1896 %% Make sure select_replace does not leave pointer 1897 %% to deallocated {2,value} in static stack. 1898 MS = [{{2,value}, [], [{{2,"new_value"}}]}], 1899 1 = ets:select_replace(T, MS), 1900 1901 %% This would crash or give wrong result at least on DEBUG emulator 1902 %% where deallocated memory is overwritten. 1903 2 = ets:next(T, 1), 1904 1905 ets:delete(T). 1906 1907 1908%% OTP-17379 1909t_select_pam_stack_overflow_bug(Config) -> 1910 T = ets:new(k, []), 1911 ets:insert(T,[{x,17}]), 1912 [{x,18}] = ets:select(T,[{{x,17}, [], [{{{element,1,'$_'},{const,18}}}]}]), 1913 ets:delete(T), 1914 ok. 1915 1916 1917%% Test that partly bound keys gives faster matches. 1918partly_bound(Config) when is_list(Config) -> 1919 case os:type() of 1920 {win32,_} -> 1921 {skip,"Inaccurate measurements on Windows"}; 1922 _ -> 1923 EtsMem = etsmem(), 1924 dont_make_worse(), 1925 make_better(), 1926 verify_etsmem(EtsMem) 1927 end. 1928 1929dont_make_worse() -> 1930 seventyfive_percent_success(fun dont_make_worse_sub/0, 0, 0, 10). 1931 1932dont_make_worse_sub() -> 1933 T = build_table([a,b],[a,b],15000), 1934 T1 = time_match_object(T,{'_',a,a,1500}, [{{a,a,1500},a,a,1500}]), 1935 T2 = time_match_object(T,{{a,a,'_'},a,a,1500}, 1936 [{{a,a,1500},a,a,1500}]), 1937 ets:delete(T), 1938 true = (T1 > T2), 1939 ok. 1940 1941make_better() -> 1942 fifty_percent_success(fun make_better_sub2/0, 0, 0, 10), 1943 fifty_percent_success(fun make_better_sub1/0, 0, 0, 10). 1944 1945make_better_sub1() -> 1946 T = build_table2([a,b],[a,b],15000), 1947 T1 = time_match_object(T,{'_',1500,a,a}, [{{1500,a,a},1500,a,a}]), 1948 T2 = time_match_object(T,{{1500,a,'_'},1500,a,a}, 1949 [{{1500,a,a},1500,a,a}]), 1950 ets:delete(T), 1951 io:format("~p>~p~n",[(T1 / 100),T2]), 1952 true = ((T1 / 100) > T2), % More marginal than needed. 1953 ok. 1954 1955make_better_sub2() -> 1956 T = build_table2([a,b],[a,b],15000), 1957 T1 = time_match(T,{'$1',1500,a,a}), 1958 T2 = time_match(T,{{1500,a,'$1'},1500,a,a}), 1959 ets:delete(T), 1960 io:format("~p>~p~n",[(T1 / 100),T2]), 1961 true = ((T1 / 100) > T2), % More marginal than needed. 1962 ok. 1963 1964 1965%% Heavy random matching, comparing set with ordered_set. 1966match_heavy(Config) when is_list(Config) -> 1967 PrivDir = proplists:get_value(priv_dir,Config), 1968 DataDir = proplists:get_value(data_dir, Config), 1969 %% Easier to have in process dictionary when manually 1970 %% running the test function. 1971 put(where_to_read,DataDir), 1972 put(where_to_write,PrivDir), 1973 random_test(), 1974 drop_match(), 1975 ok. 1976 1977%%% Extra safety for the very low probability that this is not 1978%%% caught by the random test (Statistically impossible???) 1979drop_match() -> 1980 EtsMem = etsmem(), 1981 T = build_table([a,b],[a],1500), 1982 [{{a,a,1},a,a,1},{{b,a,1},b,a,1}] = 1983 ets:match_object(T, {'_','_','_',1}), 1984 true = ets:delete(T), 1985 verify_etsmem(EtsMem). 1986 1987 1988 1989ets_match(Tab,Expr) -> 1990 case rand:uniform(2) of 1991 1 -> 1992 ets:match(Tab,Expr); 1993 _ -> 1994 match_chunked(Tab,Expr) 1995 end. 1996 1997match_chunked(Tab,Expr) -> 1998 match_chunked_collect(ets:match(Tab,Expr, 1999 rand:uniform(1999) + 1)). 2000match_chunked_collect('$end_of_table') -> 2001 []; 2002match_chunked_collect({Results, Continuation}) -> 2003 Results ++ match_chunked_collect(ets:match(Continuation)). 2004 2005ets_match_object(Tab,Expr) -> 2006 case rand:uniform(2) of 2007 1 -> 2008 ets:match_object(Tab,Expr); 2009 _ -> 2010 match_object_chunked(Tab,Expr) 2011 end. 2012 2013match_object_chunked(Tab,Expr) -> 2014 match_object_chunked_collect(ets:match_object(Tab,Expr, 2015 rand:uniform(1999) + 1)). 2016match_object_chunked_collect('$end_of_table') -> 2017 []; 2018match_object_chunked_collect({Results, Continuation}) -> 2019 Results ++ match_object_chunked_collect(ets:match_object(Continuation)). 2020 2021 2022 2023random_test() -> 2024 ReadDir = get(where_to_read), 2025 WriteDir = get(where_to_write), 2026 (catch file:make_dir(WriteDir)), 2027 case file:consult(filename:join([ReadDir,"preset_random_seed.txt"])) of 2028 {ok,[X]} -> 2029 rand:seed(X); 2030 _ -> 2031 rand:seed(default) 2032 end, 2033 Seed = rand:export_seed(), 2034 {ok,F} = file:open(filename:join([WriteDir,"last_random_seed.txt"]), 2035 [write]), 2036 io:format(F,"~p. ~n",[Seed]), 2037 file:close(F), 2038 io:format("Random seed ~p written to ~s, copy to ~s to rerun with " 2039 "same seed.",[Seed, 2040 filename:join([WriteDir, "last_random_seed.txt"]), 2041 filename:join([ReadDir, 2042 "preset_random_seed.txt"])]), 2043 do_random_test(). 2044 2045do_random_test() -> 2046 EtsMem = etsmem(), 2047 OrdSet = ets_new(xxx,[ordered_set]), 2048 Set = ets_new(xxx,[]), 2049 do_n_times(fun() -> 2050 Key = create_random_string(25), 2051 Value = create_random_tuple(25), 2052 ets:insert(OrdSet,{Key,Value}), 2053 ets:insert(Set,{Key,Value}) 2054 end, 5000), 2055 io:format("~nData inserted~n"), 2056 do_n_times(fun() -> 2057 I = rand:uniform(25), 2058 Key = create_random_string(I) ++ '_', 2059 L1 = ets_match_object(OrdSet,{Key,'_'}), 2060 L2 = lists:sort(ets_match_object(Set,{Key,'_'})), 2061 case L1 == L2 of 2062 false -> 2063 io:format("~p != ~p~n", 2064 [L1,L2]), 2065 exit({not_eq, L1, L2}); 2066 true -> 2067 ok 2068 end 2069 end, 2070 2000), 2071 io:format("~nData matched~n"), 2072 ets:match_delete(OrdSet,'_'), 2073 ets:match_delete(Set,'_'), 2074 do_n_times(fun() -> 2075 Value = create_random_string(25), 2076 Key = create_random_tuple(25), 2077 ets:insert(OrdSet,{Key,Value}), 2078 ets:insert(Set,{Key,Value}) 2079 end, 2000), 2080 io:format("~nData inserted~n"), 2081 (fun() -> 2082 Key = list_to_tuple(lists:duplicate(25,'_')), 2083 L1 = ets_match_object(OrdSet,{Key,'_'}), 2084 L2 = lists:sort(ets_match_object(Set,{Key,'_'})), 2085 2000 = length(L1), 2086 case L1 == L2 of 2087 false -> 2088 io:format("~p != ~p~n", 2089 [L1,L2]), 2090 exit({not_eq, L1, L2}); 2091 true -> 2092 ok 2093 end 2094 end)(), 2095 (fun() -> 2096 Key = {'$1','$2','$3','$4', 2097 '$5','$6','$7','$8', 2098 '$9','$10','$11','$12', 2099 '$13','$14','$15','$16', 2100 '$17','$18','$19','$20', 2101 '$21','$22','$23','$24', 2102 '$25'}, 2103 L1 = ets_match_object(OrdSet,{Key,'_'}), 2104 L2 = lists:sort(ets_match_object(Set,{Key,'_'})), 2105 2000 = length(L1), 2106 case L1 == L2 of 2107 false -> 2108 io:format("~p != ~p~n", 2109 [L1,L2]), 2110 exit({not_eq, L1, L2}); 2111 true -> 2112 ok 2113 end 2114 end)(), 2115 (fun() -> 2116 Key = {'$1','$2','$3','$4', 2117 '$5','$6','$7','$8', 2118 '$9','$10','$11','$12', 2119 '$13','$14','$15','$16', 2120 '$17','$18','$19','$20', 2121 '$21','$22','$23','$24', 2122 '$25'}, 2123 L1 = ets_match(OrdSet,{Key,'_'}), 2124 L2 = lists:sort(ets_match(Set,{Key,'_'})), 2125 2000 = length(L1), 2126 case L1 == L2 of 2127 false -> 2128 io:format("~p != ~p~n", 2129 [L1,L2]), 2130 exit({not_eq, L1, L2}); 2131 true -> 2132 ok 2133 end 2134 end)(), 2135 ets:match_delete(OrdSet,'_'), 2136 ets:match_delete(Set,'_'), 2137 do_n_times(fun() -> 2138 Value = create_random_string(25), 2139 Key = create_random_tuple(25), 2140 ets:insert(OrdSet,{Key,Value}), 2141 ets:insert(Set,{Key,Value}) 2142 end, 2000), 2143 io:format("~nData inserted~n"), 2144 do_n_times(fun() -> 2145 Key = create_partly_bound_tuple(25), 2146 L1 = ets_match_object(OrdSet,{Key,'_'}), 2147 L2 = lists:sort(ets_match_object(Set,{Key,'_'})), 2148 case L1 == L2 of 2149 false -> 2150 io:format("~p != ~p~n", 2151 [L1,L2]), 2152 exit({not_eq, L1, L2}); 2153 true -> 2154 ok 2155 end 2156 end, 2157 2000), 2158 do_n_times(fun() -> 2159 Key = create_partly_bound_tuple2(25), 2160 L1 = ets_match_object(OrdSet,{Key,'_'}), 2161 L2 = lists:sort(ets_match_object(Set,{Key,'_'})), 2162 case L1 == L2 of 2163 false -> 2164 io:format("~p != ~p~n", 2165 [L1,L2]), 2166 exit({not_eq, L1, L2}); 2167 true -> 2168 ok 2169 end 2170 end, 2171 2000), 2172 do_n_times(fun() -> 2173 Key = create_partly_bound_tuple2(25), 2174 L1 = ets_match(OrdSet,{Key,'_'}), 2175 L2 = lists:sort(ets_match(Set,{Key,'_'})), 2176 case L1 == L2 of 2177 false -> 2178 io:format("~p != ~p~n", 2179 [L1,L2]), 2180 exit({not_eq, L1, L2}); 2181 true -> 2182 ok 2183 end 2184 end, 2185 2000), 2186 io:format("~nData matched~n"), 2187 ets:match_delete(OrdSet,'_'), 2188 ets:match_delete(Set,'_'), 2189 do_n_times(fun() -> 2190 do_n_times(fun() -> 2191 Value = 2192 create_random_string(25), 2193 Key = create_random_tuple(25), 2194 ets:insert(OrdSet,{Key,Value}), 2195 ets:insert(Set,{Key,Value}) 2196 end, 500), 2197 io:format("~nData inserted~n"), 2198 do_n_times(fun() -> 2199 Key = 2200 create_partly_bound_tuple(25), 2201 ets:match_delete(OrdSet,{Key,'_'}), 2202 ets:match_delete(Set,{Key,'_'}), 2203 L1 = ets:info(OrdSet,size), 2204 L2 = ets:info(Set,size), 2205 [] = ets_match_object(OrdSet, 2206 {Key,'_'}), 2207 case L1 == L2 of 2208 false -> 2209 io:format("~p != ~p " 2210 "(deleted ~p)~n", 2211 [L1,L2,Key]), 2212 exit({not_eq, L1, L2, 2213 {deleted,Key}}); 2214 true -> 2215 ok 2216 end 2217 end, 2218 50), 2219 io:format("~nData deleted~n") 2220 end, 2221 10), 2222 ets:delete(OrdSet), 2223 ets:delete(Set), 2224 verify_etsmem(EtsMem). 2225 2226%% Test various variants of update_element. 2227update_element(Config) when is_list(Config) -> 2228 EtsMem = etsmem(), 2229 repeat_for_opts(fun update_element_opts/1), 2230 verify_etsmem(EtsMem). 2231 2232update_element_opts(Opts) -> 2233 TupleCases = [{{key,val}, 1 ,2}, 2234 {{val,key}, 2, 1}, 2235 {{key,val}, 1 ,[2]}, 2236 {{key,val,val}, 1, [2,3]}, 2237 {{val,key,val,val}, 2, [3,4,1]}, 2238 {{val,val,key,val}, 3, [1,4,1,2]}, % update pos1 twice 2239 {{val,val,val,key}, 4, [2,1,2,3]}],% update pos2 twice 2240 2241 lists:foreach(fun({Tuple,KeyPos,UpdPos}) -> update_element_opts(Tuple,KeyPos,UpdPos,Opts) end, 2242 TupleCases), 2243 2244 update_element_neg(Opts). 2245 2246 2247 2248update_element_opts(Tuple,KeyPos,UpdPos,Opts) -> 2249 Set = ets_new(set,[{keypos,KeyPos} | Opts]), 2250 OrdSet = ets_new(ordered_set,[ordered_set,{keypos,KeyPos} | Opts]), 2251 update_element(Set,Tuple,KeyPos,UpdPos), 2252 update_element(OrdSet,Tuple,KeyPos,UpdPos), 2253 true = ets:delete(Set), 2254 true = ets:delete(OrdSet), 2255 ok. 2256 2257update_element(T,Tuple,KeyPos,UpdPos) -> 2258 KeyList = [17,"seventeen",<<"seventeen">>,{17},list_to_binary(lists:seq(1,100)),make_ref(), self()], 2259 lists:foreach(fun(Key) -> 2260 TupleWithKey = setelement(KeyPos,Tuple,Key), 2261 update_element_do(T,TupleWithKey,Key,UpdPos) 2262 end, 2263 KeyList). 2264 2265update_element_do(Tab,Tuple,Key,UpdPos) -> 2266 2267 %% Strategy: Step around in Values array and call ets:update_element for the values. 2268 %% Take Length number of steps of size 1, then of size 2, ..., Length-1. 2269 %% This will try all combinations of {fromValue,toValue} 2270 %% 2271 %% IMPORTANT: size(Values) must be a prime number for this to work!!! 2272 2273 Big32 = 16#12345678, 2274 Big64 = 16#123456789abcdef0, 2275 Values = { 623, -27, 0, Big32, -Big32, Big64, -Big64, Big32*Big32, 2276 -Big32*Big32, Big32*Big64, -Big32*Big64, Big64*Big64, -Big64*Big64, 2277 "A", "Sverker", [], {12,-132}, {}, 2278 <<45,232,0,12,133>>, <<234,12,23>>, list_to_binary(lists:seq(1,100)), 2279 (fun(X) -> X*Big32 end), 2280 make_ref(), make_ref(), self(), ok, update_element, 28, 29 }, 2281 Length = size(Values), 2282 2283 PosValArgF = fun(ToIx, ResList, [Pos | PosTail], Rand, MeF) -> 2284 NextIx = (ToIx+Rand) rem Length, 2285 MeF(NextIx, [{Pos,element(ToIx+1,Values)} | ResList], PosTail, Rand, MeF); 2286 2287 (_ToIx, ResList, [], _Rand, _MeF) -> 2288 ResList; 2289 2290 (ToIx, [], Pos, _Rand, _MeF) -> 2291 {Pos, element(ToIx+1,Values)} % single {pos,value} arg 2292 end, 2293 2294 UpdateF = fun(ToIx,Rand) -> 2295 PosValArg = PosValArgF(ToIx,[],UpdPos,Rand,PosValArgF), 2296 %%io:format("update_element(~p)~n",[PosValArg]), 2297 ArgHash = erlang:phash2({Tab,Key,PosValArg}), 2298 true = ets:update_element(Tab, Key, PosValArg), 2299 ArgHash = erlang:phash2({Tab,Key,PosValArg}), 2300 NewTuple = update_tuple(PosValArg,Tuple), 2301 [NewTuple] = ets:lookup(Tab,Key) 2302 end, 2303 2304 LoopF = fun(_FromIx, Incr, _Times, Checksum, _MeF) when Incr >= Length -> 2305 Checksum; % done 2306 2307 (FromIx, Incr, 0, Checksum, MeF) -> 2308 MeF(FromIx, Incr+1, Length, Checksum, MeF); 2309 2310 (FromIx, Incr, Times, Checksum, MeF) -> 2311 ToIx = (FromIx + Incr) rem Length, 2312 UpdateF(ToIx,Checksum), 2313 if 2314 Incr =:= 0 -> UpdateF(ToIx,Checksum); % extra update to same value 2315 true -> true 2316 end, 2317 MeF(ToIx, Incr, Times-1, Checksum+ToIx+1, MeF) 2318 end, 2319 2320 FirstTuple = Tuple, 2321 true = ets:insert(Tab,FirstTuple), 2322 [FirstTuple] = ets:lookup(Tab,Key), 2323 2324 Checksum = LoopF(0, 1, Length, 0, LoopF), 2325 Checksum = (Length-1)*Length*(Length+1) div 2, % if Length is a prime 2326 ok. 2327 2328update_tuple({Pos,Val}, Tpl) -> 2329 setelement(Pos, Tpl, Val); 2330update_tuple([{Pos,Val} | Tail], Tpl) -> 2331 update_tuple(Tail,setelement(Pos, Tpl, Val)); 2332update_tuple([], Tpl) -> 2333 Tpl. 2334 2335 2336 2337update_element_neg(Opts) -> 2338 Set = ets_new(set,Opts), 2339 OrdSet = ets_new(ordered_set,[ordered_set | Opts]), 2340 update_element_neg_do(Set), 2341 update_element_neg_do(OrdSet), 2342 ets:delete(Set), 2343 {'EXIT',{badarg,_}} = (catch ets:update_element(Set,key,{2,1})), 2344 ets:delete(OrdSet), 2345 {'EXIT',{badarg,_}} = (catch ets:update_element(OrdSet,key,{2,1})), 2346 2347 Bag = ets_new(bag,[bag | Opts]), 2348 DBag = ets_new(duplicate_bag,[duplicate_bag | Opts]), 2349 {'EXIT',{badarg,_}} = (catch ets:update_element(Bag,key,{2,1})), 2350 {'EXIT',{badarg,_}} = (catch ets:update_element(DBag,key,{2,1})), 2351 true = ets:delete(Bag), 2352 true = ets:delete(DBag), 2353 ok. 2354 2355 2356update_element_neg_do(T) -> 2357 Object = {key, 0, "Hej"}, 2358 true = ets:insert(T,Object), 2359 2360 UpdateF = fun(Arg3) -> 2361 ArgHash = erlang:phash2({T,key,Arg3}), 2362 {'EXIT',{badarg,_}} = (catch ets:update_element(T,key,Arg3)), 2363 ArgHash = erlang:phash2({T,key,Arg3}), 2364 [Object] = ets:lookup(T,key) 2365 end, 2366 2367 %% List of invalid {Pos,Value} tuples 2368 InvList = [false, {2}, {2,1,false}, {false,1}, {0,1}, {1,1}, {-1,1}, {4,1}], 2369 2370 lists:foreach(UpdateF, InvList), 2371 lists:foreach(fun(InvTpl) -> UpdateF([{2,1},InvTpl]) end, InvList), 2372 lists:foreach(fun(InvTpl) -> UpdateF([InvTpl,{2,1}]) end, InvList), 2373 lists:foreach(fun(InvTpl) -> UpdateF([{2,1},{3,"Hello"},InvTpl]) end, InvList), 2374 lists:foreach(fun(InvTpl) -> UpdateF([{3,"Hello"},{2,1},InvTpl]) end, InvList), 2375 lists:foreach(fun(InvTpl) -> UpdateF([{2,1},InvTpl,{3,"Hello"}]) end, InvList), 2376 lists:foreach(fun(InvTpl) -> UpdateF([InvTpl,{3,"Hello"},{2,1}]) end, InvList), 2377 UpdateF([{2,1} | {3,1}]), 2378 lists:foreach(fun(InvTpl) -> UpdateF([{2,1} | InvTpl]) end, InvList), 2379 2380 true = ets:update_element(T,key,[]), 2381 false = ets:update_element(T,false,[]), 2382 false = ets:update_element(T,false,{2,1}), 2383 ets:delete(T,key), 2384 false = ets:update_element(T,key,{2,1}), 2385 ok. 2386 2387 2388%% test various variants of update_counter. 2389update_counter(Config) when is_list(Config) -> 2390 EtsMem = etsmem(), 2391 repeat_for_opts(fun update_counter_do/1), 2392 verify_etsmem(EtsMem). 2393 2394update_counter_do(Opts) -> 2395 Set = ets_new(set,Opts), 2396 OrdSet = ets_new(ordered_set,[ordered_set | Opts]), 2397 update_counter_for(Set), 2398 update_counter_for(OrdSet), 2399 ets:delete_all_objects(Set), 2400 ets:delete_all_objects(OrdSet), 2401 ets:safe_fixtable(Set, true), 2402 ets:safe_fixtable(OrdSet, true), 2403 update_counter_for(Set), 2404 update_counter_for(OrdSet), 2405 ets:safe_fixtable(Set, false), 2406 ets:safe_fixtable(OrdSet, false), 2407 ets:delete(Set), 2408 ets:delete(OrdSet), 2409 update_counter_neg(Opts). 2410 2411update_counter_for(T) -> 2412 ets:insert(T,{a,1,1}), 2413 101 = ets:update_counter(T,a,100), 2414 [{a,101,1}] = ets:lookup(T,a), 2415 101 = ets:update_counter(T,a,{3,100}), 2416 [{a,101,101}] = ets:lookup(T,a), 2417 2418 2419 LooperF = fun(Obj, 0, _, _) -> 2420 Obj; 2421 2422 (Obj, Times, Arg3, Myself) -> 2423 {NewObj, Ret} = uc_mimic(Obj,Arg3), 2424 ArgHash = erlang:phash2({T,a,Arg3}), 2425 %%io:format("update_counter(~p, ~p, ~p) expecting ~p\n",[T,a,Arg3,Ret]), 2426 [DefaultObj] = ets:lookup(T, a), 2427 Ret = ets:update_counter(T,a,Arg3), 2428 Ret = ets:update_counter(T, b, Arg3, DefaultObj), % Use other key 2429 ArgHash = erlang:phash2({T,a,Arg3}), 2430 %%io:format("NewObj=~p~n ",[NewObj]), 2431 [NewObj] = ets:lookup(T,a), 2432 true = ets:lookup(T, b) =:= [setelement(1, NewObj, b)], 2433 ets:delete(T, b), 2434 Myself(NewObj,Times-1,Arg3,Myself) 2435 end, 2436 2437 LoopF = fun(Obj, Times, Arg3) -> 2438 %%io:format("Loop start:\nObj = ~p\nArg3=~p\n",[Obj,Arg3]), 2439 LooperF(Obj,Times,Arg3,LooperF) 2440 end, 2441 2442 SmallMax32 = (1 bsl 27) - 1, 2443 SmallMax64 = (1 bsl (27+32)) - 1, 2444 Big1Max32 = (1 bsl 32) - 1, 2445 Big1Max64 = (1 bsl 64) - 1, 2446 2447 Steps = 100, 2448 Obj0 = {a,0,0,0,0}, 2449 ets:insert(T,Obj0), 2450 Obj1 = LoopF(Obj0, Steps, {2,(SmallMax32 div Steps)*2}), 2451 Obj2 = LoopF(Obj1, Steps, {3,(SmallMax64 div Steps)*2}), 2452 Obj3 = LoopF(Obj2, Steps, {4,(Big1Max32 div Steps)*2}), 2453 Obj4 = LoopF(Obj3, Steps, {5,(Big1Max64 div Steps)*2}), 2454 2455 Obj5 = LoopF(Obj4, Steps, {2,-(SmallMax32 div Steps)*4}), 2456 Obj6 = LoopF(Obj5, Steps, {3,-(SmallMax64 div Steps)*4}), 2457 Obj7 = LoopF(Obj6, Steps, {4,-(Big1Max32 div Steps)*4}), 2458 Obj8 = LoopF(Obj7, Steps, {5,-(Big1Max64 div Steps)*4}), 2459 2460 Obj9 = LoopF(Obj8, Steps, {2,(SmallMax32 div Steps)*2}), 2461 ObjA = LoopF(Obj9, Steps, {3,(SmallMax64 div Steps)*2}), 2462 ObjB = LoopF(ObjA, Steps, {4,(Big1Max32 div Steps)*2}), 2463 Obj0 = LoopF(ObjB, Steps, {5,(Big1Max64 div Steps)*2}), 2464 2465 %% back at zero, same trip again with lists 2466 2467 Obj4 = LoopF(Obj0,Steps,[{2, (SmallMax32 div Steps)*2}, 2468 {3, (SmallMax64 div Steps)*2}, 2469 {4, (Big1Max32 div Steps)*2}, 2470 {5, (Big1Max64 div Steps)*2}]), 2471 2472 Obj8 = LoopF(Obj4,Steps,[{4, -(Big1Max32 div Steps)*4}, 2473 {2, -(SmallMax32 div Steps)*4}, 2474 {5, -(Big1Max64 div Steps)*4}, 2475 {3, -(SmallMax64 div Steps)*4}]), 2476 2477 Obj0 = LoopF(Obj8,Steps,[{5, (Big1Max64 div Steps)*2}, 2478 {2, (SmallMax32 div Steps)*2}, 2479 {4, (Big1Max32 div Steps)*2}, 2480 {3, (SmallMax64 div Steps)*2}]), 2481 2482 %% make them shift size at the same time 2483 ObjC = LoopF(Obj0,Steps,[{5, (Big1Max64 div Steps)*2}, 2484 {3, (Big1Max64 div Steps)*2 + 1}, 2485 {2, -(Big1Max64 div Steps)*2}, 2486 {4, -(Big1Max64 div Steps)*2 + 1}]), 2487 2488 %% update twice in same list 2489 ObjD = LoopF(ObjC,Steps,[{5, -(Big1Max64 div Steps) + 1}, 2490 {3, -(Big1Max64 div Steps)*2 - 1}, 2491 {5, -(Big1Max64 div Steps) - 1}, 2492 {4, (Big1Max64 div Steps)*2 - 1}]), 2493 2494 Obj0 = LoopF(ObjD,Steps,[{2, (Big1Max64 div Steps) - 1}, 2495 {4, Big1Max64*2}, 2496 {2, (Big1Max64 div Steps) + 1}, 2497 {4, -Big1Max64*2}]), 2498 2499 %% warping with list 2500 ObjE = LoopF(Obj0,1000, 2501 [{3,SmallMax32*4 div 5,SmallMax32*2,-SmallMax32*2}, 2502 {5,-SmallMax64*4 div 7,-SmallMax64*2,SmallMax64*2}, 2503 {4,-Big1Max32*4 div 11,-Big1Max32*2,Big1Max32*2}, 2504 {2,Big1Max64*4 div 13,Big1Max64*2,-Big1Max64*2}]), 2505 2506 %% warping without list 2507 ObjF = LoopF(ObjE,1000,{3,SmallMax32*4 div 5,SmallMax32*2,-SmallMax32*2}), 2508 ObjG = LoopF(ObjF,1000,{5,-SmallMax64*4 div 7,-SmallMax64*2,SmallMax64*2}), 2509 ObjH = LoopF(ObjG,1000,{4,-Big1Max32*4 div 11,-Big1Max32*2,Big1Max32*2}), 2510 ObjI = LoopF(ObjH,1000,{2,Big1Max64*4 div 13,Big1Max64*2,-Big1Max64*2}), 2511 2512 %% mixing it up 2513 LoopF(ObjI,1000, 2514 [{3,SmallMax32*4 div 5,SmallMax32*2,-SmallMax32*2}, 2515 {5,-SmallMax64*4 div 3}, 2516 {3,-SmallMax32*4 div 11}, 2517 {5,0}, 2518 {4,1}, 2519 {5,-SmallMax64*4 div 7,-SmallMax64*2,SmallMax64*2}, 2520 {2,Big1Max64*4 div 13,Big1Max64*2,-Big1Max64*2}]), 2521 ok. 2522 2523%% uc_mimic works kind of like the real ets:update_counter 2524%% Obj = Tuple in ets 2525%% Pits = {Pos,Incr} | {Pos,Incr,Thres,Warp} 2526%% Returns {Updated tuple in ets, Return value from update_counter} 2527uc_mimic(Obj, Pits) when is_tuple(Pits) -> 2528 Pos = element(1,Pits), 2529 NewObj = setelement(Pos, Obj, uc_adder(element(Pos,Obj),Pits)), 2530 {NewObj, element(Pos,NewObj)}; 2531 2532uc_mimic(Obj, PitsList) when is_list(PitsList) -> 2533 {NewObj,ValList} = uc_mimic(Obj,PitsList,[]), 2534 {NewObj,lists:reverse(ValList)}. 2535 2536uc_mimic(Obj, [], Acc) -> 2537 {Obj,Acc}; 2538uc_mimic(Obj, [Pits|Tail], Acc) -> 2539 {NewObj,NewVal} = uc_mimic(Obj,Pits), 2540 uc_mimic(NewObj,Tail,[NewVal|Acc]). 2541 2542uc_adder(Init, {_Pos, Add}) -> 2543 Init + Add; 2544uc_adder(Init, {_Pos, Add, Thres, Warp}) -> 2545 case Init + Add of 2546 X when X > Thres, Add > 0 -> 2547 Warp; 2548 Y when Y < Thres, Add < 0 -> 2549 Warp; 2550 Z -> 2551 Z 2552 end. 2553 2554update_counter_neg(Opts) -> 2555 Set = ets_new(set,Opts), 2556 OrdSet = ets_new(ordered_set,[ordered_set | Opts]), 2557 update_counter_neg_for(Set), 2558 update_counter_neg_for(OrdSet), 2559 ets:delete(Set), 2560 {'EXIT',{badarg,_}} = (catch ets:update_counter(Set,key,1)), 2561 ets:delete(OrdSet), 2562 {'EXIT',{badarg,_}} = (catch ets:update_counter(OrdSet,key,1)), 2563 2564 Bag = ets_new(bag,[bag | Opts]), 2565 DBag = ets_new(duplicate_bag,[duplicate_bag | Opts]), 2566 {'EXIT',{badarg,_}} = (catch ets:update_counter(Bag,key,1)), 2567 {'EXIT',{badarg,_}} = (catch ets:update_counter(DBag,key,1)), 2568 true = ets:delete(Bag), 2569 true = ets:delete(DBag), 2570 ok. 2571 2572update_counter_neg_for(T) -> 2573 Object = {key,0,false,1}, 2574 true = ets:insert(T,Object), 2575 2576 UpdateF = fun(Arg3) -> 2577 ArgHash = erlang:phash2({T,key,Arg3}), 2578 {'EXIT',{badarg,_}} = (catch ets:update_counter(T,key,Arg3)), 2579 ArgHash = erlang:phash2({T,key,Arg3}), 2580 [Object] = ets:lookup(T,key) 2581 end, 2582 2583 %% List of invalid arg3-tuples 2584 InvList = [false, {2}, {2,false}, {false,1}, 2585 {0,1}, {-1,1}, % BUG < R12B-2 2586 {1,1}, {3,1}, {5,1}, {2,1,100}, {2,1,100,0,false}, {2,1,false,0}, {2,1,0,false}], 2587 2588 lists:foreach(UpdateF, InvList), 2589 lists:foreach(fun(Inv) -> UpdateF([{2,1},Inv]) end, InvList), 2590 lists:foreach(fun(Inv) -> UpdateF([Inv,{2,1}]) end, InvList), 2591 lists:foreach(fun(Inv) -> UpdateF([{2,1},{4,-100},Inv]) end, InvList), 2592 lists:foreach(fun(Inv) -> UpdateF([{4,100,50,0},{2,1},Inv]) end, InvList), 2593 lists:foreach(fun(Inv) -> UpdateF([{2,1},Inv,{4,100,50,0}]) end, InvList), 2594 lists:foreach(fun(Inv) -> UpdateF([Inv,{4,100,50,0},{2,1}]) end, InvList), 2595 UpdateF([{2,1} | {4,1}]), 2596 lists:foreach(fun(Inv) -> UpdateF([{2,1} | Inv]) end, InvList), 2597 2598 {'EXIT',{badarg,_}} = (catch ets:update_counter(T,false,1)), 2599 ets:delete(T,key), 2600 {'EXIT',{badarg,_}} = (catch ets:update_counter(T,key,1)), 2601 ok. 2602 2603 2604evil_update_counter(Config) when is_list(Config) -> 2605 %% The code server uses ets table. Pre-load modules that might not be 2606 %% already loaded. 2607 gb_sets:module_info(), 2608 math:module_info(), 2609 ordsets:module_info(), 2610 rand:module_info(), 2611 2612 repeat_for_opts(fun evil_update_counter_do/1). 2613 2614evil_update_counter_do(Opts) -> 2615 EtsMem = etsmem(), 2616 process_flag(trap_exit, true), 2617 Pids = [my_spawn_link(fun() -> evil_counter(I,Opts) end) || I <- lists:seq(1, 40)], 2618 wait_for_all(gb_sets:from_list(Pids)), 2619 verify_etsmem(EtsMem), 2620 ok. 2621 2622wait_for_all(Pids0) -> 2623 case gb_sets:is_empty(Pids0) of 2624 true -> 2625 ok; 2626 false -> 2627 receive 2628 {'EXIT',Pid,normal} -> 2629 Pids = gb_sets:delete(Pid, Pids0), 2630 wait_for_all(Pids); 2631 Other -> 2632 io:format("unexpected: ~p\n", [Other]), 2633 ct:fail(failed) 2634 end 2635 end. 2636 2637evil_counter(I,Opts) -> 2638 T = ets_new(a, Opts), 2639 Start0 = case I rem 3 of 2640 0 -> 16#12345678; 2641 1 -> 16#12345678FFFFFFFF; 2642 2 -> 16#7777777777FFFFFFFF863648726743 2643 end, 2644 Start = Start0 + rand:uniform(100000), 2645 ets:insert(T, {dracula,Start}), 2646 Iter = 40000 div syrup_factor(), 2647 End = Start + Iter, 2648 End = evil_counter_1(Iter, T), 2649 ets:delete(T). 2650 2651evil_counter_1(0, T) -> 2652 [{dracula,Count}] = ets:lookup(T, dracula), 2653 Count; 2654evil_counter_1(Iter, T) -> 2655 ets:update_counter(T, dracula, 1), 2656 evil_counter_1(Iter-1, T). 2657 2658update_counter_with_default(Config) when is_list(Config) -> 2659 repeat_for_opts(fun update_counter_with_default_do/1). 2660 2661update_counter_with_default_do(Opts) -> 2662 T1 = ets_new(a, [set | Opts]), 2663 %% Insert default object. 2664 3 = ets:update_counter(T1, foo, 2, {beaufort,1}), 2665 1 = ets:info(T1, size), 2666 %% Increment. 2667 5 = ets:update_counter(T1, foo, 2, {cabecou,1}), 2668 1 = ets:info(T1, size), 2669 %% Increment with list. 2670 [9] = ets:update_counter(T1, foo, [{2,4}], {camembert,1}), 2671 1 = ets:info(T1, size), 2672 %% Same with non-immediate key. 2673 3 = ets:update_counter(T1, {foo,bar}, 2, {{chaource,chevrotin},1}), 2674 2 = ets:info(T1, size), 2675 5 = ets:update_counter(T1, {foo,bar}, 2, {{cantal,comté},1}), 2676 2 = ets:info(T1, size), 2677 [9] = ets:update_counter(T1, {foo,bar}, [{2,4}], {{emmental,de,savoie},1}), 2678 2 = ets:info(T1, size), 2679 %% default counter is not an integer. 2680 {'EXIT',{badarg,_}} = (catch ets:update_counter(T1, qux, 3, {saint,félicien})), 2681 2 = ets:info(T1, size), 2682 %% No third element in default value. 2683 {'EXIT',{badarg,_}} = (catch ets:update_counter(T1, qux, [{3,1}], {roquefort,1})), 2684 2 = ets:info(T1, size), 2685 2686 %% Same with ordered set. 2687 T2 = ets_new(b, [ordered_set | Opts]), 2688 3 = ets:update_counter(T2, foo, 2, {maroilles,1}), 2689 1 = ets:info(T2, size), 2690 5 = ets:update_counter(T2, foo, 2, {mimolette,1}), 2691 1 = ets:info(T2, size), 2692 [9] = ets:update_counter(T2, foo, [{2,4}], {morbier,1}), 2693 1 = ets:info(T2, size), 2694 3 = ets:update_counter(T2, {foo,bar}, 2, {{laguiole},1}), 2695 2 = ets:info(T2, size), 2696 5 = ets:update_counter(T2, {foo,bar}, 2, {{saint,nectaire},1}), 2697 2 = ets:info(T2, size), 2698 [9] = ets:update_counter(T2, {foo,bar}, [{2,4}], {{rocamadour},1}), 2699 2 = ets:info(T2, size), 2700 %% Arithmetically-equal keys. 2701 3 = ets:update_counter(T2, 1.0, 2, {1,1}), 2702 3 = ets:info(T2, size), 2703 5 = ets:update_counter(T2, 1, 2, {1,1}), 2704 3 = ets:info(T2, size), 2705 7 = ets:update_counter(T2, 1, 2, {1.0,1}), 2706 3 = ets:info(T2, size), 2707 %% Same with reversed type difference. 2708 3 = ets:update_counter(T2, 2, 2, {2.0,1}), 2709 4 = ets:info(T2, size), 2710 5 = ets:update_counter(T2, 2.0, 2, {2.0,1}), 2711 4 = ets:info(T2, size), 2712 7 = ets:update_counter(T2, 2.0, 2, {2,1}), 2713 4 = ets:info(T2, size), 2714 %% default counter is not an integer. 2715 {'EXIT',{badarg,_}} = (catch ets:update_counter(T1, qux, 3, {saint,félicien})), 2716 4 = ets:info(T2, size), 2717 %% No third element in default value. 2718 {'EXIT',{badarg,_}} = (catch ets:update_counter(T1, qux, [{3,1}], {roquefort,1})), 2719 4 = ets:info(T2, size), 2720 ok. 2721 2722%% ERL-1125 2723update_counter_with_default_bad_pos(Config) when is_list(Config) -> 2724 repeat_for_all_ord_set_table_types(fun update_counter_with_default_bad_pos_do/1). 2725 2726update_counter_with_default_bad_pos_do(Opts) -> 2727 T = ets_new(a, Opts), 2728 0 = ets:info(T, size), 2729 ok = try ets:update_counter(T, 101065, {1, 1}, {101065, 0}) 2730 catch 2731 error:badarg -> ok; 2732 Class:Reason -> {Class, Reason} 2733 end, 2734 0 = ets:info(T, size), 2735 ok. 2736 2737update_counter_table_growth(_Config) -> 2738 repeat_for_opts(fun update_counter_table_growth_do/1). 2739 2740update_counter_table_growth_do(Opts) -> 2741 Set = ets_new(b, [set | Opts]), 2742 [ets:update_counter(Set, N, {2, 1}, {N, 1}) || N <- lists:seq(1,10000)], 2743 OrderedSet = ets_new(b, [ordered_set | Opts]), 2744 [ets:update_counter(OrderedSet, N, {2, 1}, {N, 1}) || N <- lists:seq(1,10000)], 2745 ok. 2746 2747%% Check that a first-next sequence always works on a fixed table. 2748fixtable_next(Config) when is_list(Config) -> 2749 repeat_for_opts(fun fixtable_next_do/1, 2750 [write_concurrency,all_types]). 2751 2752fixtable_next_do(Opts) -> 2753 EtsMem = etsmem(), 2754 do_fixtable_next(ets_new(set,[public | Opts])), 2755 verify_etsmem(EtsMem). 2756 2757do_fixtable_next(Tab) -> 2758 F = fun(X,T,FF) -> 2759 case X of 2760 0 -> true; 2761 _ -> 2762 ets:insert(T, {X, 2763 integer_to_list(X), 2764 X rem 10}), 2765 FF(X-1,T,FF) 2766 end 2767 end, 2768 F(100,Tab,F), 2769 ets:safe_fixtable(Tab,true), 2770 First = ets:first(Tab), 2771 ets:delete(Tab, First), 2772 ets:next(Tab, First), 2773 ets:match_delete(Tab,{'_','_','_'}), 2774 '$end_of_table' = ets:next(Tab, First), 2775 true = ets:info(Tab, fixed), 2776 ets:safe_fixtable(Tab, false), 2777 false = ets:info(Tab, fixed), 2778 ets:delete(Tab). 2779 2780%% Check that iteration of bags find all live objects and nothing else. 2781fixtable_iter_bag(Config) when is_list(Config) -> 2782 repeat_for_opts(fun fixtable_iter_do/1, 2783 [write_concurrency,[bag,duplicate_bag]]). 2784 2785fixtable_iter_do(Opts) -> 2786 EtsMem = etsmem(), 2787 do_fixtable_iter_bag(ets_new(fixtable_iter_bag,Opts)), 2788 verify_etsmem(EtsMem). 2789 2790do_fixtable_iter_bag(T) -> 2791 MaxValues = 4, 2792 %% Create 1 to MaxValues objects for each key 2793 %% and then delete every possible combination of those objects 2794 %% in every possible order. 2795 %% Then test iteration returns all live objects and nothing else. 2796 2797 CrDelOps = [begin 2798 Values = lists:seq(1,N), 2799 %% All ways of deleting any number of the Values in any order 2800 Combos = combs(Values), 2801 DeleteOps = concat_lists([perms(C) || C <- Combos]), 2802 {N, DeleteOps} 2803 end 2804 || N <- lists:seq(1,MaxValues)], 2805 2806 %%io:format("~p\n", [CrDelOps]), 2807 2808 NKeys = lists:foldl(fun({_, DeleteOps}, Cnt) -> 2809 Cnt + length(DeleteOps) 2810 end, 2811 0, 2812 CrDelOps), 2813 2814 io:format("Create ~p keys\n", [NKeys]), 2815 2816 %% Fixate even before inserts just to maintain small table size 2817 %% and increase likelyhood of different keys in same bucket. 2818 ets:safe_fixtable(T,true), 2819 InsRes = [begin 2820 [begin 2821 Key = {NValues,ValueList}, 2822 [begin 2823 Tpl = {Key, V}, 2824 %%io:format("Insert object ~p", [Tpl]), 2825 ets:insert(T, Tpl), 2826 Tpl 2827 end 2828 || V <- lists:seq(1,NValues)] 2829 end 2830 || ValueList <- DeleteOps] 2831 end 2832 || {NValues, DeleteOps} <- CrDelOps], 2833 2834 Inserted = lists:flatten(InsRes), 2835 InSorted = lists:sort(Inserted), 2836 InSorted = lists:usort(Inserted), %% No duplicates 2837 NObjs = length(Inserted), 2838 2839 DelRes = [begin 2840 [begin 2841 Key = {NValues,ValueList}, 2842 [begin 2843 Tpl = {Key, V}, 2844 %%io:format("Delete object ~p", [Tpl]), 2845 ets:delete_object(T, Tpl), 2846 Tpl 2847 end 2848 || V <- ValueList] 2849 end 2850 || ValueList <- DeleteOps] 2851 end 2852 || {NValues, DeleteOps} <- CrDelOps], 2853 2854 Deleted = lists:flatten(DelRes), 2855 DelSorted = lists:sort(Deleted), 2856 DelSorted = lists:usort(Deleted), %% No duplicates 2857 NDels = length(Deleted), 2858 2859 %% Nr of keys where all values were deleted. 2860 NDeletedKeys = lists:sum([factorial(N) || N <- lists:seq(1,MaxValues)]), 2861 2862 CountKeysFun = fun Me(K1, Cnt) -> 2863 case ets:next(T, K1) of 2864 '$end_of_table' -> 2865 Cnt; 2866 K2 -> 2867 Objs = ets:lookup(T, K2), 2868 [{{NValues, ValueList}, _V} | _] = Objs, 2869 ExpectedLive = NValues - length(ValueList), 2870 ExpectedLive = length(Objs), 2871 Me(K2, Cnt+1) 2872 end 2873 end, 2874 2875 ExpectedKeys = NKeys - NDeletedKeys, 2876 io:format("Expected keys: ~p\n", [ExpectedKeys]), 2877 FoundKeys = CountKeysFun(ets:first(T), 1), 2878 io:format("Found keys: ~p\n", [FoundKeys]), 2879 ExpectedKeys = FoundKeys, 2880 2881 ExpectedObjs = NObjs - NDels, 2882 io:format("Expected objects: ~p\n", [ExpectedObjs]), 2883 FoundObjs = ets:select_count(T, [{{'_','_'}, [], [true]}]), 2884 io:format("Found objects: ~p\n", [FoundObjs]), 2885 ExpectedObjs = FoundObjs, 2886 2887 ets:delete(T). 2888 2889%% All permutations of list 2890perms([]) -> [[]]; 2891perms(L) -> [[H|T] || H <- L, T <- perms(L--[H])]. 2892 2893%% All combinations of picking the element (or not) from list 2894combs([]) -> [[]]; 2895combs([H|T]) -> 2896 Tcombs = combs(T), 2897 Tcombs ++ [[H | C] || C <- Tcombs]. 2898 2899factorial(0) -> 1; 2900factorial(N) when N > 0 -> 2901 N * factorial(N - 1). 2902 2903concat_lists([]) -> 2904 []; 2905concat_lists([H|T]) -> 2906 H ++ concat_lists(T). 2907 2908 2909%% Check inserts of deleted keys in fixed bags. 2910fixtable_insert(Config) when is_list(Config) -> 2911 Combos = [[Type,{write_concurrency,WC}] || Type<- [bag,duplicate_bag], 2912 WC <- [false,true]], 2913 lists:foreach(fun(Opts) -> fixtable_insert_do(Opts) end, 2914 Combos), 2915 ok. 2916 2917fixtable_insert_do(Opts) -> 2918 io:format("Opts = ~p\n",[Opts]), 2919 Ets = make_table(ets, Opts, [{a,1}, {a,2}, {b,1}, {b,2}]), 2920 ets:safe_fixtable(Ets,true), 2921 ets:match_delete(Ets,{b,1}), 2922 First = ets:first(Ets), 2923 Next = case First of 2924 a -> b; 2925 b -> a 2926 end, 2927 Next = ets:next(Ets,First), 2928 ets:delete(Ets,Next), 2929 '$end_of_table' = ets:next(Ets,First), 2930 ets:insert(Ets, {Next,1}), 2931 false = ets:insert_new(Ets, {Next,1}), 2932 Next = ets:next(Ets,First), 2933 '$end_of_table' = ets:next(Ets,Next), 2934 ets:delete(Ets,Next), 2935 '$end_of_table' = ets:next(Ets,First), 2936 ets:insert(Ets, {Next,2}), 2937 false = ets:insert_new(Ets, {Next,1}), 2938 Next = ets:next(Ets,First), 2939 '$end_of_table' = ets:next(Ets,Next), 2940 ets:delete(Ets,First), 2941 Next = ets:first(Ets), 2942 '$end_of_table' = ets:next(Ets,Next), 2943 ets:delete(Ets,Next), 2944 '$end_of_table' = ets:next(Ets,First), 2945 true = ets:insert_new(Ets,{Next,1}), 2946 false = ets:insert_new(Ets,{Next,2}), 2947 Next = ets:next(Ets,First), 2948 ets:delete_object(Ets,{Next,1}), 2949 '$end_of_table' = ets:next(Ets,First), 2950 true = ets:insert_new(Ets,{Next,2}), 2951 false = ets:insert_new(Ets,{Next,1}), 2952 Next = ets:next(Ets,First), 2953 ets:delete(Ets,First), 2954 ets:safe_fixtable(Ets,false), 2955 {'EXIT',{badarg,_}} = (catch ets:next(Ets,First)), 2956 ok. 2957 2958%% Test the 'write_concurrency' option. 2959write_concurrency(Config) when is_list(Config) -> 2960 EtsMem = etsmem(), 2961 Yes1 = ets_new(foo,[public,{write_concurrency,true}]), 2962 Yes2 = ets_new(foo,[protected,{write_concurrency,true}]), 2963 No1 = ets_new(foo,[private,{write_concurrency,true}]), 2964 2965 Yes3 = ets_new(foo,[bag,public,{write_concurrency,true}]), 2966 Yes4 = ets_new(foo,[bag,protected,{write_concurrency,true}]), 2967 No2 = ets_new(foo,[bag,private,{write_concurrency,true}]), 2968 2969 Yes5 = ets_new(foo,[duplicate_bag,public,{write_concurrency,true}]), 2970 Yes6 = ets_new(foo,[duplicate_bag,protected,{write_concurrency,true}]), 2971 No3 = ets_new(foo,[duplicate_bag,private,{write_concurrency,true}]), 2972 2973 NoCentCtrs = {decentralized_counters,false}, 2974 Yes7 = ets_new(foo,[ordered_set,public,{write_concurrency,true},NoCentCtrs]), 2975 Yes8 = ets_new(foo,[ordered_set,protected,{write_concurrency,true},NoCentCtrs]), 2976 Yes9 = ets_new(foo,[ordered_set,{write_concurrency,true},NoCentCtrs]), 2977 Yes10 = ets_new(foo,[{write_concurrency,true},ordered_set,public,NoCentCtrs]), 2978 Yes11 = ets_new(foo,[{write_concurrency,true},ordered_set,protected,NoCentCtrs]), 2979 Yes12 = ets_new(foo,[set,{write_concurrency,false}, 2980 {write_concurrency,true},ordered_set,public,NoCentCtrs]), 2981 Yes13 = ets_new(foo,[private,public,set,{write_concurrency,false}, 2982 {write_concurrency,true},ordered_set,NoCentCtrs]), 2983 Yes14 = ets_new(foo,[ordered_set,public,{write_concurrency,true}]), 2984 No4 = ets_new(foo,[ordered_set,private,{write_concurrency,true}]), 2985 No5 = ets_new(foo,[ordered_set,public,{write_concurrency,false}]), 2986 No6 = ets_new(foo,[ordered_set,protected,{write_concurrency,false}]), 2987 No7 = ets_new(foo,[ordered_set,private,{write_concurrency,false}]), 2988 2989 No8 = ets_new(foo,[public,{write_concurrency,false}]), 2990 No9 = ets_new(foo,[protected,{write_concurrency,false}]), 2991 2992 YesMem = ets:info(Yes1,memory), 2993 NoHashMem = ets:info(No1,memory), 2994 YesTreeMem = ets:info(Yes7,memory), 2995 YesYesTreeMem = ets:info(Yes14,memory), 2996 NoTreeMem = ets:info(No4,memory), 2997 io:format("YesMem=~p NoHashMem=~p NoTreeMem=~p YesTreeMem=~p\n",[YesMem,NoHashMem, 2998 NoTreeMem,YesTreeMem]), 2999 3000 YesMem = ets:info(Yes2,memory), 3001 YesMem = ets:info(Yes3,memory), 3002 YesMem = ets:info(Yes4,memory), 3003 YesMem = ets:info(Yes5,memory), 3004 YesMem = ets:info(Yes6,memory), 3005 NoHashMem = ets:info(No2,memory), 3006 NoHashMem = ets:info(No3,memory), 3007 YesTreeMem = ets:info(Yes7,memory), 3008 YesTreeMem = ets:info(Yes8,memory), 3009 YesTreeMem = ets:info(Yes9,memory), 3010 YesTreeMem = ets:info(Yes10,memory), 3011 YesTreeMem = ets:info(Yes11,memory), 3012 YesTreeMem = ets:info(Yes12,memory), 3013 YesTreeMem = ets:info(Yes13,memory), 3014 NoTreeMem = ets:info(No4,memory), 3015 NoTreeMem = ets:info(No5,memory), 3016 NoTreeMem = ets:info(No6,memory), 3017 NoTreeMem = ets:info(No7,memory), 3018 NoHashMem = ets:info(No8,memory), 3019 NoHashMem = ets:info(No9,memory), 3020 3021 true = YesMem > YesTreeMem, 3022 3023 case erlang:system_info(schedulers) > 1 of 3024 true -> 3025 true = YesMem > NoHashMem, 3026 true = YesMem > NoTreeMem, 3027 true = YesTreeMem < NoTreeMem, 3028 true = YesYesTreeMem > YesTreeMem; 3029 _ -> 3030 one_scheduler_only 3031 end, 3032 3033 {'EXIT',{badarg,_}} = (catch ets_new(foo,[public,{write_concurrency,foo}])), 3034 {'EXIT',{badarg,_}} = (catch ets_new(foo,[public,{write_concurrency}])), 3035 {'EXIT',{badarg,_}} = (catch ets_new(foo,[public,{write_concurrency,true,foo}])), 3036 {'EXIT',{badarg,_}} = (catch ets_new(foo,[public,write_concurrency])), 3037 3038 lists:foreach(fun(T) -> ets:delete(T) end, 3039 [Yes1,Yes2,Yes3,Yes4,Yes5,Yes6,Yes7,Yes8,Yes9,Yes10,Yes11,Yes12,Yes13,Yes14, 3040 No1,No2,No3,No4,No5,No6,No7,No8,No9]), 3041 verify_etsmem(EtsMem), 3042 ok. 3043 3044 3045%% The 'heir' option. 3046heir(Config) when is_list(Config) -> 3047 repeat_for_opts(fun heir_do/1). 3048 3049heir_do(Opts) -> 3050 EtsMem = etsmem(), 3051 Master = self(), 3052 3053 %% Different types of heir data and link/monitor relations 3054 TestFun = fun(Arg) -> {EtsMem,Arg} end, 3055 Combos = [{Data,Mode} || Data<-[foo_data, <<"binary">>, 3056 lists:seq(1,10), {17,TestFun,self()}, 3057 "The busy heir"], 3058 Mode<-[none,link,monitor]], 3059 lists:foreach(fun({Data,Mode})-> heir_1(Data,Mode,Opts) end, 3060 Combos), 3061 3062 %% No heir 3063 {Founder1,MrefF1} = my_spawn_monitor(fun()->heir_founder(Master,foo_data,Opts)end), 3064 Founder1 ! {go, none}, 3065 {"No heir",Founder1} = receive_any(), 3066 {'DOWN', MrefF1, process, Founder1, normal} = receive_any(), 3067 undefined = ets:info(foo), 3068 3069 %% An already dead heir 3070 {Heir2,MrefH2} = my_spawn_monitor(fun()->die end), 3071 {'DOWN', MrefH2, process, Heir2, normal} = receive_any(), 3072 {Founder2,MrefF2} = my_spawn_monitor(fun()->heir_founder(Master,foo_data,Opts)end), 3073 Founder2 ! {go, Heir2}, 3074 {"No heir",Founder2} = receive_any(), 3075 {'DOWN', MrefF2, process, Founder2, normal} = receive_any(), 3076 undefined = ets:info(foo), 3077 3078 %% When heir dies before founder 3079 {Founder3,MrefF3} = my_spawn_monitor(fun()->heir_founder(Master,"The dying heir",Opts)end), 3080 {Heir3,MrefH3} = my_spawn_monitor(fun()->heir_heir(Founder3)end), 3081 Founder3 ! {go, Heir3}, 3082 {'DOWN', MrefH3, process, Heir3, normal} = receive_any(), 3083 Founder3 ! die_please, 3084 {'DOWN', MrefF3, process, Founder3, normal} = receive_any(), 3085 undefined = ets:info(foo), 3086 3087 %% When heir dies and pid reused before founder dies 3088 repeat_while(fun() -> 3089 NextPidIx = erts_debug:get_internal_state(next_pid), 3090 {Founder4,MrefF4} = my_spawn_monitor(fun()->heir_founder(Master,"The dying heir",Opts)end), 3091 {Heir4,MrefH4} = my_spawn_monitor(fun()->heir_heir(Founder4)end), 3092 Founder4 ! {go, Heir4}, 3093 {'DOWN', MrefH4, process, Heir4, normal} = receive_any(), 3094 erts_debug:set_internal_state(next_pid, NextPidIx), 3095 DoppelGanger = spawn_monitor_with_pid(Heir4, 3096 fun()-> die_please = receive_any() end), 3097 Founder4 ! die_please, 3098 {'DOWN', MrefF4, process, Founder4, normal} = receive_any(), 3099 case DoppelGanger of 3100 {Heir4,MrefH4_B} -> 3101 Heir4 ! die_please, 3102 {'DOWN', MrefH4_B, process, Heir4, normal} = receive_any(), 3103 undefined = ets:info(foo), 3104 false; 3105 failed -> 3106 io:format("Failed to spawn process with pid ~p\n", [Heir4]), 3107 true % try again 3108 end 3109 end), 3110 3111 verify_etsmem(EtsMem). 3112 3113heir_founder(Master, HeirData, Opts) -> 3114 {go,Heir} = receive_any(), 3115 HeirTpl = case Heir of 3116 none -> {heir,none}; 3117 _ -> {heir, Heir, HeirData} 3118 end, 3119 T = ets_new(foo,[named_table, private, HeirTpl | Opts]), 3120 true = ets:insert(T,{key,1}), 3121 [{key,1}] = ets:lookup(T,key), 3122 Self = self(), 3123 Self = ets:info(T,owner), 3124 case ets:info(T,heir) of 3125 none -> 3126 true = (Heir =:= none) orelse (not is_process_alive(Heir)), 3127 Master ! {"No heir",self()}; 3128 3129 Heir -> 3130 true = is_process_alive(Heir), 3131 Heir ! {table,T,HeirData}, 3132 die_please = receive_any() 3133 end. 3134 3135 3136heir_heir(Founder) -> 3137 heir_heir(Founder, none). 3138heir_heir(Founder, Mode) -> 3139 {table,T,HeirData} = receive_any(), 3140 {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)), 3141 case HeirData of 3142 "The dying heir" -> exit(normal); 3143 _ -> ok 3144 end, 3145 3146 Mref = case Mode of 3147 link -> process_flag(trap_exit, true), 3148 link(Founder); 3149 monitor -> erlang:monitor(process,Founder); 3150 none -> ok 3151 end, 3152 Founder ! die_please, 3153 Msg = case HeirData of 3154 "The busy heir" -> receive_any_spinning(); 3155 _ -> receive_any() 3156 end, 3157 {'ETS-TRANSFER', T, Founder, HeirData} = Msg, 3158 foo = T, 3159 Self = self(), 3160 Self = ets:info(T,owner), 3161 Self = ets:info(T,heir), 3162 [{key,1}] = ets:lookup(T,key), 3163 true = ets:insert(T,{key,2}), 3164 [{key,2}] = ets:lookup(T,key), 3165 case Mode of % Verify that EXIT or DOWN comes after ETS-TRANSFER 3166 link -> 3167 {'EXIT',Founder,normal} = receive_any(), 3168 process_flag(trap_exit, false); 3169 monitor -> 3170 {'DOWN', Mref, process, Founder, normal} = receive_any(); 3171 none -> ok 3172 end. 3173 3174 3175heir_1(HeirData,Mode,Opts) -> 3176 io:format("test with heir_data = ~p\n", [HeirData]), 3177 Master = self(), 3178 Founder = my_spawn_link(fun() -> heir_founder(Master,HeirData,Opts) end), 3179 io:format("founder spawned = ~p\n", [Founder]), 3180 {Heir,Mref} = my_spawn_monitor(fun() -> heir_heir(Founder,Mode) end), 3181 io:format("heir spawned = ~p\n", [{Heir,Mref}]), 3182 Founder ! {go, Heir}, 3183 {'DOWN', Mref, process, Heir, normal} = receive_any(). 3184 3185%% Test ets:give_way/3. 3186give_away(Config) when is_list(Config) -> 3187 repeat_for_opts(fun give_away_do/1). 3188 3189give_away_do(Opts) -> 3190 T = ets_new(foo,[named_table, private | Opts]), 3191 true = ets:insert(T,{key,1}), 3192 [{key,1}] = ets:lookup(T,key), 3193 Parent = self(), 3194 3195 %% Give and then give back 3196 {Receiver,Mref} = my_spawn_monitor(fun()-> give_away_receiver(T,Parent) end), 3197 give_me = receive_any(), 3198 true = ets:give_away(T,Receiver,here_you_are), 3199 {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)), 3200 Receiver ! give_back, 3201 {'ETS-TRANSFER',T,Receiver,"Tillbakakaka"} = receive_any(), 3202 [{key,2}] = ets:lookup(T,key), 3203 {'DOWN', Mref, process, Receiver, normal} = receive_any(), 3204 3205 %% Give and then let receiver keep it 3206 true = ets:insert(T,{key,1}), 3207 {Receiver3,Mref3} = my_spawn_monitor(fun()-> give_away_receiver(T,Parent) end), 3208 give_me = receive_any(), 3209 true = ets:give_away(T,Receiver3,here_you_are), 3210 {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)), 3211 Receiver3 ! die_please, 3212 {'DOWN', Mref3, process, Receiver3, normal} = receive_any(), 3213 undefined = ets:info(T), 3214 3215 %% Give and then kill receiver to get back 3216 T2 = ets_new(foo,[private | Opts]), 3217 true = ets:insert(T2,{key,1}), 3218 ets:setopts(T2,{heir,self(),"Som en gummiboll..."}), 3219 {Receiver2,Mref2} = my_spawn_monitor(fun()-> give_away_receiver(T2,Parent) end), 3220 give_me = receive_any(), 3221 true = ets:give_away(T2,Receiver2,here_you_are), 3222 {'EXIT',{badarg,_}} = (catch ets:lookup(T2,key)), 3223 Receiver2 ! die_please, 3224 {'ETS-TRANSFER',T2,Receiver2,"Som en gummiboll..."} = receive_any(), 3225 [{key,2}] = ets:lookup(T2,key), 3226 {'DOWN', Mref2, process, Receiver2, normal} = receive_any(), 3227 3228 %% Some negative testing 3229 {'EXIT',{badarg,_}} = (catch ets:give_away(T2,Receiver,"To a dead one")), 3230 {'EXIT',{badarg,_}} = (catch ets:give_away(T2,self(),"To myself")), 3231 {'EXIT',{badarg,_}} = (catch ets:give_away(T2,"not a pid","To wrong type")), 3232 3233 true = ets:delete(T2), 3234 {ReceiverNeg,MrefNeg} = my_spawn_monitor(fun()-> give_away_receiver(T2,Parent) end), 3235 give_me = receive_any(), 3236 {'EXIT',{badarg,_}} = (catch ets:give_away(T2,ReceiverNeg,"A deleted table")), 3237 3238 T3 = ets_new(foo,[public | Opts]), 3239 my_spawn_link(fun()-> {'EXIT',{badarg,_}} = (catch ets:give_away(T3,ReceiverNeg,"From non owner")), 3240 Parent ! done 3241 end), 3242 done = receive_any(), 3243 ReceiverNeg ! no_soup_for_you, 3244 {'DOWN', MrefNeg, process, ReceiverNeg, normal} = receive_any(), 3245 ok. 3246 3247give_away_receiver(T, Giver) -> 3248 {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)), 3249 Giver ! give_me, 3250 case receive_any() of 3251 {'ETS-TRANSFER',T,Giver,here_you_are} -> 3252 [{key,1}] = ets:lookup(T,key), 3253 true = ets:insert(T,{key,2}), 3254 case receive_any() of 3255 give_back -> 3256 true = ets:give_away(T,Giver,"Tillbakakaka"), 3257 {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)); 3258 die_please -> 3259 ok 3260 end; 3261 no_soup_for_you -> 3262 ok 3263 end. 3264 3265 3266%% Test ets:setopts/2. 3267setopts(Config) when is_list(Config) -> 3268 repeat_for_opts(fun setopts_do/1, [write_concurrency,all_types]). 3269 3270setopts_do(Opts) -> 3271 Self = self(), 3272 T = ets_new(foo,[named_table, private | Opts]), 3273 none = ets:info(T,heir), 3274 Heir = my_spawn_link(fun()->heir_heir(Self) end), 3275 ets:setopts(T,{heir,Heir,"Data"}), 3276 Heir = ets:info(T,heir), 3277 ets:setopts(T,{heir,self(),"Data"}), 3278 Self = ets:info(T,heir), 3279 ets:setopts(T,[{heir,Heir,"Data"}]), 3280 Heir = ets:info(T,heir), 3281 ets:setopts(T,[{heir,none}]), 3282 none = ets:info(T,heir), 3283 3284 {'EXIT',{badarg,_}} = (catch ets:setopts(T,[{heir,self(),"Data"},false])), 3285 {'EXIT',{badarg,_}} = (catch ets:setopts(T,{heir,self()})), 3286 {'EXIT',{badarg,_}} = (catch ets:setopts(T,{heir,false})), 3287 {'EXIT',{badarg,_}} = (catch ets:setopts(T,heir)), 3288 {'EXIT',{badarg,_}} = (catch ets:setopts(T,{heir,false,"Data"})), 3289 {'EXIT',{badarg,_}} = (catch ets:setopts(T,{false,self(),"Data"})), 3290 3291 ets:setopts(T,{protection,protected}), 3292 ets:setopts(T,{protection,public}), 3293 ets:setopts(T,{protection,private}), 3294 ets:setopts(T,[{protection,protected}]), 3295 ets:setopts(T,[{protection,public}]), 3296 ets:setopts(T,[{protection,private}]), 3297 3298 {'EXIT',{badarg,_}} = (catch ets:setopts(T,{protection})), 3299 {'EXIT',{badarg,_}} = (catch ets:setopts(T,{protection,false})), 3300 {'EXIT',{badarg,_}} = (catch ets:setopts(T,{protection,private,false})), 3301 {'EXIT',{badarg,_}} = (catch ets:setopts(T,protection)), 3302 ets:delete(T), 3303 unlink(Heir), 3304 exit(Heir, bang), 3305 ok. 3306 3307%% All kinds of operations with bad table argument. 3308bad_table(Config) when is_list(Config) -> 3309 3310 %% Open and close disk_log to stabilize etsmem. 3311 Name = make_ref(), 3312 File = filename:join([proplists:get_value(priv_dir, Config),"bad_table.dummy"]), 3313 {ok, Name} = disk_log:open([{name, Name}, {file, File}]), 3314 disk_log:close(Name), 3315 file:delete(File), 3316 3317 EtsMem = etsmem(), 3318 3319 repeat_for_opts(fun(Opts) -> bad_table_do(Opts,File) end, 3320 [write_concurrency, all_types]), 3321 verify_etsmem(EtsMem), 3322 ok. 3323 3324bad_table_do(Opts, DummyFile) -> 3325 Parent = self(), 3326 {Pid,Mref} = my_spawn_opt(fun()-> ets_new(priv,[private,named_table | Opts]), 3327 Priv = ets_new(priv,[private | Opts]), 3328 ets_new(prot,[protected,named_table | Opts]), 3329 Prot = ets_new(prot,[protected | Opts]), 3330 Parent ! {self(),Priv,Prot}, 3331 die_please = receive_any() 3332 end, 3333 [link, monitor]), 3334 {Pid,Priv,Prot} = receive_any(), 3335 MatchSpec = {{key,'_'}, [], ['$$']}, 3336 Fun = fun(X,_) -> X end, 3337 OpList = [{delete,[key],update}, 3338 {delete_all_objects,[],update}, 3339 {delete_object,[{key,data}],update}, 3340 {first,[],read}, 3341 {foldl,[Fun, 0], read, tabarg_last}, 3342 {foldr,[Fun, 0], read, tabarg_last}, 3343 %%{from_dets,[DetsTab], update}, 3344 {give_away,[Pid, data], update}, 3345 %%{info, [], read}, 3346 %%{info, [safe_fixed], read}, 3347 %%{init_table,[Name, InitFun],update}, 3348 {insert, [{key,data}], update}, 3349 {insert_new, [{key,data}], update}, 3350 {insert_new, [[{key,data},{other,data}]], update}, 3351 {last, [], read}, 3352 {lookup, [key], read}, 3353 {lookup_element, [key, 2], read}, 3354 {match, [{}], read}, 3355 {match, [{},17], read}, 3356 {match_delete, [{}], update}, 3357 {match_object, [{}], read}, 3358 {match_object, [{},17], read}, 3359 {member,[key], read}, 3360 {next, [key], read}, 3361 {prev, [key], read}, 3362 {rename, [new_name], update}, 3363 {safe_fixtable, [true], read}, 3364 {select,[MatchSpec], read}, 3365 {select,[MatchSpec,17], read}, 3366 {select_count,[MatchSpec], read}, 3367 {select_delete,[MatchSpec], update}, 3368 {setopts, [{heir,none}], update}, 3369 {slot, [0], read}, 3370 {tab2file, [DummyFile], read, {return,{error,badtab}}}, 3371 {tab2file, [DummyFile,[]], read, {return,{error,badtab}}}, 3372 {tab2list, [], read}, 3373 %%{table,[], read}, 3374 %%{to_dets, [DetsTab], read}, 3375 {update_counter,[key,1], update}, 3376 {update_element,[key,{2,new_data}], update} 3377 ], 3378 Info = {Opts, Priv, Prot}, 3379 lists:foreach(fun(Op) -> bad_table_op(Info, Op) end, 3380 OpList), 3381 Pid ! die_please, 3382 {'DOWN', Mref, process, Pid, normal} = receive_any(), 3383 ok. 3384 3385bad_table_op({Opts,Priv,Prot}, Op) -> 3386 %%io:format("Doing Op=~p on ~p's\n",[Op,Type]), 3387 T1 = ets_new(noname,Opts), 3388 bad_table_call(noname,Op), 3389 ets:delete(T1), 3390 bad_table_call(T1,Op), 3391 T2 = ets_new(named,[named_table | Opts]), 3392 ets:delete(T2), 3393 bad_table_call(named,Op), 3394 bad_table_call(T2,Op), 3395 bad_table_call(priv,Op), 3396 bad_table_call(Priv,Op), 3397 case element(3,Op) of 3398 update -> 3399 bad_table_call(prot,Op), 3400 bad_table_call(Prot,Op); 3401 read -> ok 3402 end. 3403 3404bad_table_call(T,{F,Args,_}) -> 3405 {'EXIT',{badarg,_}} = (catch apply(ets, F, [T|Args])); 3406bad_table_call(T,{F,Args,_,tabarg_last}) -> 3407 {'EXIT',{badarg,_}} = (catch apply(ets, F, Args++[T])); 3408bad_table_call(T,{F,Args,_,{return,Return}}) -> 3409 try 3410 Return = apply(ets, F, [T|Args]) 3411 catch 3412 error:badarg -> ok 3413 end. 3414 3415 3416%% Check rename of ets tables. 3417rename(Config) when is_list(Config) -> 3418 repeat_for_opts(fun rename_do/1, [write_concurrency, all_types]). 3419 3420rename_do(Opts) -> 3421 EtsMem = etsmem(), 3422 ets_new(foobazz,[named_table, public | Opts]), 3423 ets:insert(foobazz,{foo,bazz}), 3424 ungermanbazz = ets:rename(foobazz,ungermanbazz), 3425 {'EXIT',{badarg, _}} = (catch ets:lookup(foobazz,foo)), 3426 [{foo,bazz}] = ets:lookup(ungermanbazz,foo), 3427 {'EXIT',{badarg,_}} = (catch ets:rename(ungermanbazz,"no atom")), 3428 ets:delete(ungermanbazz), 3429 verify_etsmem(EtsMem). 3430 3431%% Check rename of unnamed ets table. 3432rename_unnamed(Config) when is_list(Config) -> 3433 repeat_for_opts(fun rename_unnamed_do/1, 3434 [write_concurrency,all_types]). 3435 3436rename_unnamed_do(Opts) -> 3437 EtsMem = etsmem(), 3438 Tab = ets_new(bonkz,[public | Opts]), 3439 {'EXIT',{badarg, _}} = (catch ets:insert(bonkz,{foo,bazz})), 3440 bonkz = ets:info(Tab, name), 3441 Tab = ets:rename(Tab, tjabonkz), 3442 {'EXIT',{badarg, _}} = (catch ets:insert(tjabonkz,{foo,bazz})), 3443 tjabonkz = ets:info(Tab, name), 3444 ets:delete(Tab), 3445 verify_etsmem(EtsMem). 3446 3447%% Rename a table with many fixations, and at the same time delete it. 3448evil_rename(Config) when is_list(Config) -> 3449 EtsMem = etsmem(), 3450 evil_rename_1(old_hash, new_hash, [public,named_table]), 3451 evil_rename_1(old_tree, new_tree, [public,ordered_set,named_table]), 3452 wait_for_test_procs(true), 3453 verify_etsmem(EtsMem). 3454 3455evil_rename_1(Old, New, Flags) -> 3456 process_flag(trap_exit, true), 3457 Old = ets_new(Old, Flags), 3458 Fixer = fun() -> ets:safe_fixtable(Old, true) end, 3459 crazy_fixtable(15000, Fixer), 3460 erlang:yield(), 3461 New = ets:rename(Old, New), 3462 erlang:yield(), 3463 ets:delete(New), 3464 ok. 3465 3466crazy_fixtable(N, Fixer) -> 3467 Dracula = ets_new(count_dracula, [public]), 3468 ets:insert(Dracula, {count,0}), 3469 SpawnFun = fun() -> 3470 Fixer(), 3471 case ets:update_counter(Dracula, count, 1) rem 15 of 3472 0 -> evil_creater_destroyer(); 3473 _ -> erlang:hibernate(erlang, error, [dont_wake_me]) 3474 end 3475 end, 3476 crazy_fixtable_1(N, SpawnFun), 3477 crazy_fixtable_wait(N, Dracula), 3478 Dracula. 3479 3480crazy_fixtable_wait(N, Dracula) -> 3481 case ets:lookup(Dracula, count) of 3482 [{count,N}] -> 3483 ets:delete(Dracula); 3484 Other -> 3485 io:format("~p\n", [Other]), 3486 receive after 10 -> ok end, 3487 crazy_fixtable_wait(N, Dracula) 3488 end. 3489 3490crazy_fixtable_1(0, _) -> 3491 ok; 3492crazy_fixtable_1(N, Fun) -> 3493 %%FIXME my_spawn_link(Fun), 3494 my_spawn_link(Fun), 3495 crazy_fixtable_1(N-1, Fun). 3496 3497evil_creater_destroyer() -> 3498 T1 = evil_create_fixed_tab(), 3499 ets:delete(T1). 3500 3501evil_create_fixed_tab() -> 3502 T = ets_new(arne, [public]), 3503 ets:safe_fixtable(T, true), 3504 T. 3505 3506%% Tests that the return values and errors are equal for set's and 3507%% ordered_set's where applicable. 3508interface_equality(Config) when is_list(Config) -> 3509 repeat_for_opts(fun interface_equality_do/1). 3510 3511interface_equality_do(Opts) -> 3512 EtsMem = etsmem(), 3513 Set = ets_new(set,[set | Opts]), 3514 OrderedSet = ets_new(ordered_set,[ordered_set | Opts]), 3515 F = fun(X,T,FF) -> case X of 3516 0 -> true; 3517 _ -> 3518 ets:insert(T, {X, 3519 integer_to_list(X), 3520 X rem 10}), 3521 FF(X-1,T,FF) 3522 end 3523 end, 3524 F(100,Set,F), 3525 F(100,OrderedSet,F), 3526 equal_results(ets, insert, Set, OrderedSet, [{a,"a"}]), 3527 equal_results(ets, insert, Set, OrderedSet, [{1,1,"1"}]), 3528 equal_results(ets, lookup, Set, OrderedSet, [10]), 3529 equal_results(ets, lookup, Set, OrderedSet, [1000]), 3530 equal_results(ets, delete, Set, OrderedSet, [10]), 3531 equal_results(ets, delete, Set, OrderedSet, [nott]), 3532 equal_results(ets, lookup, Set, OrderedSet, [1000]), 3533 equal_results(ets, insert, Set, OrderedSet, [10]), 3534 equal_results(ets, next, Set, OrderedSet, ['$end_of_table']), 3535 equal_results(ets, prev, Set, OrderedSet, ['$end_of_table']), 3536 equal_results(ets, match, Set, OrderedSet, [{'_','_','_'}]), 3537 equal_results(ets, match, Set, OrderedSet, [{'_','_','_','_'}]), 3538 equal_results(ets, match, Set, OrderedSet, [{$3,$2,2}]), 3539 equal_results(ets, match, Set, OrderedSet, ['_']), 3540 equal_results(ets, match, Set, OrderedSet, ['$1']), 3541 equal_results(ets, match, Set, OrderedSet, [{'_','$50',3}]), 3542 equal_results(ets, match, Set, OrderedSet, [['_','$50',3]]), 3543 equal_results(ets, match_delete, Set, OrderedSet, [{'_','_',4}]), 3544 equal_results(ets, match_delete, Set, OrderedSet, [{'_','_',4}]), 3545 equal_results(ets, match_object, Set, OrderedSet, [{'_','_',4}]), 3546 equal_results(ets, match_object, Set, OrderedSet, [{'_','_',5}]), 3547 equal_results(ets, match_object, Set, OrderedSet, [{'_','_',4}]), 3548 equal_results(ets, match_object, Set, OrderedSet, ['_']), 3549 equal_results(ets, match_object, Set, OrderedSet, ['$5011']), 3550 equal_results(ets, match_delete, Set, OrderedSet, ['$20']), 3551 equal_results(ets, lookup_element, Set, OrderedSet, [13,2]), 3552 equal_results(ets, lookup_element, Set, OrderedSet, [13,4]), 3553 equal_results(ets, lookup_element, Set, OrderedSet, [14,2]), 3554 equal_results(ets, delete, Set, OrderedSet, []), 3555 verify_etsmem(EtsMem). 3556 3557equal_results(M, F, FirstArg1, FirstArg2 ,ACommon) -> 3558 Res = maybe_sort((catch apply(M,F, [FirstArg1 | ACommon]))), 3559 Res = maybe_sort((catch apply(M,F,[FirstArg2 | ACommon]))). 3560 3561maybe_sort(L) when is_list(L) -> 3562 lists:sort(L); 3563maybe_sort({'EXIT',{Reason, List}}) when is_list(List) -> 3564 {'EXIT',{Reason, lists:map(fun({Module, Function, _, _}) -> 3565 {Module, Function, '_'} 3566 end, 3567 List)}}; 3568maybe_sort(Any) -> 3569 Any. 3570 3571%% Test match, match_object and match_delete in ordered set's. 3572ordered_match(Config) when is_list(Config)-> 3573 repeat_for_opts(fun ordered_match_do/1). 3574 3575ordered_match_do(Opts) -> 3576 EtsMem = etsmem(), 3577 F = fun(X,T,FF) -> case X of 3578 0 -> true; 3579 _ -> 3580 ets:insert(T, {X, 3581 integer_to_list(X), 3582 X rem 10, 3583 X rem 100, 3584 X rem 1000}), 3585 FF(X-1,T,FF) 3586 end 3587 end, 3588 T1 = ets_new(xxx,[ordered_set| Opts]), 3589 F(3000,T1,F), 3590 [[3,3],[3,3],[3,3]] = ets:match(T1, {'_','_','$1','$2',3}), 3591 F2 = fun(X,Rem,Res,FF) -> case X of 3592 0 -> []; 3593 _ -> 3594 case X rem Rem of 3595 Res -> 3596 FF(X-1,Rem,Res,FF) ++ 3597 [{X, 3598 integer_to_list(X), 3599 X rem 10, 3600 X rem 100, 3601 X rem 1000}]; 3602 _ -> 3603 FF(X-1,Rem,Res,FF) 3604 end 3605 end 3606 end, 3607 OL1 = F2(3000,100,2,F2), 3608 OL1 = ets:match_object(T1, {'_','_','_',2,'_'}), 3609 true = ets:match_delete(T1,{'_','_','_',2,'_'}), 3610 [] = ets:match_object(T1, {'_','_','_',2,'_'}), 3611 OL2 = F2(3000,100,3,F2), 3612 OL2 = ets:match_object(T1, {'_','_','_',3,'_'}), 3613 ets:delete(T1), 3614 verify_etsmem(EtsMem). 3615 3616 3617%% Test basic functionality in ordered_set's. 3618ordered(Config) when is_list(Config) -> 3619 repeat_for_opts(fun ordered_do/1). 3620 3621ordered_do(Opts) -> 3622 EtsMem = etsmem(), 3623 T = ets_new(oset, [ordered_set | Opts]), 3624 InsList = [ 3625 25,26,27,28, 3626 5,6,7,8, 3627 21,22,23,24, 3628 9,10,11,12, 3629 1,2,3,4, 3630 17,18,19,20, 3631 13,14,15,16, 3632 1 bsl 33 3633 ], 3634 lists:foreach(fun(X) -> 3635 ets:insert(T,{X,integer_to_list(X)}) 3636 end, 3637 InsList), 3638 IL2 = lists:map(fun(X) -> {X,integer_to_list(X)} end, InsList), 3639 L1 = pick_all_forward(T), 3640 L2 = pick_all_backwards(T), 3641 S1 = lists:sort(IL2), 3642 S2 = lists:reverse(lists:sort(IL2)), 3643 S1 = L1, 3644 S2 = L2, 3645 [{1,"1"}] = ets:slot(T,0), 3646 [{28,"28"}] = ets:slot(T,27), 3647 [{1 bsl 33,_}] = ets:slot(T,28), 3648 27 = ets:prev(T,28), 3649 [{7,"7"}] = ets:slot(T,6), 3650 '$end_of_table' = ets:next(T,1 bsl 33), 3651 [{12,"12"}] = ets:slot(T,11), 3652 '$end_of_table' = ets:slot(T,29), 3653 [{1,"1"}] = ets:slot(T,0), 3654 28 = ets:prev(T,1 bsl 33), 3655 1 = ets:next(T,0), 3656 pick_all_forward(T), 3657 [{7,"7"}] = ets:slot(T,6), 3658 L2 = pick_all_backwards(T), 3659 [{7,"7"}] = ets:slot(T,6), 3660 ets:delete(T), 3661 verify_etsmem(EtsMem). 3662 3663pick_all(_T,'$end_of_table',_How) -> 3664 []; 3665pick_all(T,Last,How) -> 3666 This = case How of 3667 next -> 3668 ets:next(T,Last); 3669 prev -> 3670 ets:prev(T,Last) 3671 end, 3672 [LastObj] = ets:lookup(T,Last), 3673 [LastObj | pick_all(T,This,How)]. 3674 3675pick_all_forward(T) -> 3676 pick_all(T,ets:first(T),next). 3677pick_all_backwards(T) -> 3678 pick_all(T,ets:last(T),prev). 3679 3680 3681 3682%% Small test case for both set and bag type ets tables. 3683setbag(Config) when is_list(Config) -> 3684 EtsMem = etsmem(), 3685 lists:foreach(fun(SetType) -> 3686 Set = ets_new(SetType,[SetType]), 3687 Bag = ets_new(bag,[bag]), 3688 Key = {foo,bar}, 3689 3690 %% insert some value 3691 ets:insert(Set,{Key,val1}), 3692 ets:insert(Bag,{Key,val1}), 3693 3694 %% insert new value for same key again 3695 ets:insert(Set,{Key,val2}), 3696 ets:insert(Bag,{Key,val2}), 3697 3698 %% check 3699 [{Key,val2}] = ets:lookup(Set,Key), 3700 [{Key,val1},{Key,val2}] = ets:lookup(Bag,Key), 3701 3702 true = ets:delete(Set), 3703 true = ets:delete(Bag) 3704 end, [set, cat_ord_set,stim_cat_ord_set,ordered_set]), 3705 verify_etsmem(EtsMem). 3706 3707%% Test case to check proper return values for illegal ets_new() calls. 3708badnew(Config) when is_list(Config) -> 3709 EtsMem = etsmem(), 3710 {'EXIT',{badarg,_}} = (catch ets:new(12,[])), 3711 {'EXIT',{badarg,_}} = (catch ets:new({a,b},[])), 3712 {'EXIT',{badarg,_}} = (catch ets:new(name,[foo])), 3713 {'EXIT',{badarg,_}} = (catch ets:new(name,{bag})), 3714 {'EXIT',{badarg,_}} = (catch ets:new(name,bag)), 3715 verify_etsmem(EtsMem). 3716 3717%% OTP-2314. Test case to check that a non-proper list does not 3718%% crash the emulator. 3719verybadnew(Config) when is_list(Config) -> 3720 EtsMem = etsmem(), 3721 {'EXIT',{badarg,_}} = (catch ets:new(verybad,[set|protected])), 3722 verify_etsmem(EtsMem). 3723 3724%% Small check to see if named tables work. 3725named(Config) when is_list(Config) -> 3726 EtsMem = etsmem(), 3727 Tab = make_table(foo, 3728 [named_table], 3729 [{key,val}]), 3730 [{key,val}] = ets:lookup(foo,key), 3731 true = ets:delete(Tab), 3732 verify_etsmem(EtsMem). 3733 3734%% Test case to check if specified keypos works. 3735keypos2(Config) when is_list(Config) -> 3736 EtsMem = etsmem(), 3737 lists:foreach(fun(SetType) -> 3738 Tab = make_table(foo, 3739 [SetType,{keypos,2}], 3740 [{val,key}, {val2,key}]), 3741 [{val2,key}] = ets:lookup(Tab,key), 3742 true = ets:delete(Tab) 3743 end, [set, cat_ord_set,stim_cat_ord_set,ordered_set]), 3744 verify_etsmem(EtsMem). 3745 3746%% Privacy check. Check that a named(public/private/protected) table 3747%% cannot be read by the wrong process(es). 3748privacy(Config) when is_list(Config) -> 3749 repeat_for_opts(fun privacy_do/1). 3750 3751privacy_do(Opts) -> 3752 EtsMem = etsmem(), 3753 process_flag(trap_exit,true), 3754 Parent = self(), 3755 Owner = my_spawn_link(fun() -> privacy_owner(Parent, Opts) end), 3756 receive 3757 {'EXIT',Owner,Reason} -> 3758 exit({privacy_test,Reason}); 3759 ok -> 3760 ok 3761 end, 3762 3763 privacy_check(pub,prot,priv), 3764 3765 Owner ! {shift,1,{pub,prot,priv}}, 3766 receive 3767 {Pub1,Prot1,Priv1} -> 3768 ok = privacy_check(Pub1,Prot1,Priv1), 3769 Owner ! {shift,2,{Pub1,Prot1,Priv1}} 3770 end, 3771 3772 receive 3773 {Pub2,Prot2,Priv2} -> 3774 ok = privacy_check(Pub2,Prot2,Priv2), 3775 Owner ! {shift,0,{Pub2,Prot2,Priv2}} 3776 end, 3777 3778 receive 3779 {Pub3,Prot3,Priv3} -> 3780 ok = privacy_check(Pub3,Prot3,Priv3) 3781 end, 3782 3783 Owner ! die, 3784 receive {'EXIT',Owner,_} -> ok end, 3785 verify_etsmem(EtsMem). 3786 3787privacy_check(Pub,Prot,Priv) -> 3788 %% check read rights 3789 [] = ets:lookup(Pub, foo), 3790 [] = ets:lookup(Prot,foo), 3791 {'EXIT',{badarg,_}} = (catch ets:lookup(Priv,foo)), 3792 3793 %% check write rights 3794 true = ets:insert(Pub, {1,foo}), 3795 {'EXIT',{badarg,_}} = (catch ets:insert(Prot,{2,foo})), 3796 {'EXIT',{badarg,_}} = (catch ets:insert(Priv,{3,foo})), 3797 3798 %% check that it really wasn't written, either 3799 [] = ets:lookup(Prot,foo), 3800 ok. 3801 3802privacy_owner(Boss, Opts) -> 3803 ets_new(pub, [public,named_table | Opts]), 3804 ets_new(prot,[protected,named_table | Opts]), 3805 ets_new(priv,[private,named_table | Opts]), 3806 Boss ! ok, 3807 privacy_owner_loop(Boss). 3808 3809privacy_owner_loop(Boss) -> 3810 receive 3811 {shift,N,Pub_Prot_Priv} -> 3812 {Pub,Prot,Priv} = rotate_tuple(Pub_Prot_Priv, N), 3813 3814 ets:setopts(Pub,{protection,public}), 3815 ets:setopts(Prot,{protection,protected}), 3816 ets:setopts(Priv,{protection,private}), 3817 Boss ! {Pub,Prot,Priv}, 3818 privacy_owner_loop(Boss); 3819 3820 die -> ok 3821 end. 3822 3823rotate_tuple(Tuple, 0) -> 3824 Tuple; 3825rotate_tuple(Tuple, N) -> 3826 [H|T] = tuple_to_list(Tuple), 3827 rotate_tuple(list_to_tuple(T ++ [H]), N-1). 3828 3829 3830%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3831 3832 3833%% Check lookup in an empty table and lookup of a non-existing key. 3834empty(Config) when is_list(Config) -> 3835 repeat_for_opts_all_table_types(fun empty_do/1). 3836 3837empty_do(Opts) -> 3838 EtsMem = etsmem(), 3839 Tab = ets_new(foo,Opts), 3840 [] = ets:lookup(Tab,key), 3841 true = ets:insert(Tab,{key2,val}), 3842 [] = ets:lookup(Tab,key), 3843 true = ets:delete(Tab), 3844 verify_etsmem(EtsMem). 3845 3846%% Check proper return values for illegal insert operations. 3847badinsert(Config) when is_list(Config) -> 3848 repeat_for_opts_all_table_types(fun badinsert_do/1). 3849 3850badinsert_do(Opts) -> 3851 EtsMem = etsmem(), 3852 {'EXIT',{badarg,_}} = (catch ets:insert(foo,{key,val})), 3853 3854 Tab = ets_new(foo,Opts), 3855 {'EXIT',{badarg,_}} = (catch ets:insert(Tab,{})), 3856 3857 Tab3 = ets_new(foo,[{keypos,3}| Opts]), 3858 {'EXIT',{badarg,_}} = (catch ets:insert(Tab3,{a,b})), 3859 3860 {'EXIT',{badarg,_}} = (catch ets:insert(Tab,[key,val2])), 3861 true = ets:delete(Tab), 3862 true = ets:delete(Tab3), 3863 verify_etsmem(EtsMem). 3864 3865%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3866 3867 3868%% Check proper return values from bad lookups in existing/non existing 3869%% ets tables. 3870badlookup(Config) when is_list(Config) -> 3871 EtsMem = etsmem(), 3872 {'EXIT',{badarg,_}} = (catch ets:lookup(foo,key)), 3873 Tab = ets_new(foo,[]), 3874 ets:delete(Tab), 3875 {'EXIT',{badarg,_}} = (catch ets:lookup(Tab,key)), 3876 verify_etsmem(EtsMem). 3877 3878%% Test that lookup returns objects in order of insertion for bag and dbag. 3879lookup_order(Config) when is_list(Config) -> 3880 EtsMem = etsmem(), 3881 repeat_for_opts(fun lookup_order_do/1, 3882 [write_concurrency,[bag,duplicate_bag]]), 3883 verify_etsmem(EtsMem), 3884 ok. 3885 3886lookup_order_do(Opts) -> 3887 lookup_order_2(Opts, false), 3888 lookup_order_2(Opts, true). 3889 3890lookup_order_2(Opts, Fixed) -> 3891 io:format("Opts=~p Fixed=~p\n",[Opts,Fixed]), 3892 3893 A = 1, B = 2, C = 3, 3894 ABC = [A,B,C], 3895 Pair = [{A,B},{B,A},{A,C},{C,A},{B,C},{C,B}], 3896 Combos = [{D1,D2,D3} || D1<-ABC, D2<-Pair, D3<-Pair], 3897 lists:foreach(fun({D1,{D2a,D2b},{D3a,D3b}}) -> 3898 T = ets_new(foo,Opts), 3899 case Fixed of 3900 true -> ets:safe_fixtable(T,true); 3901 false -> ok 3902 end, 3903 S10 = {T,[],key}, 3904 S20 = check_insert(S10,A), 3905 S30 = check_insert(S20,B), 3906 S40 = check_insert(S30,C), 3907 S50 = check_delete(S40,D1), 3908 S55 = check_insert(S50,D1), 3909 S60 = check_insert(S55,D1), 3910 S70 = check_delete(S60,D2a), 3911 S80 = check_delete(S70,D2b), 3912 S90 = check_insert(S80,D2a), 3913 SA0 = check_delete(S90,D3a), 3914 SB0 = check_delete(SA0,D3b), 3915 check_insert_new(SB0,D3b), 3916 3917 true = ets:delete(T) 3918 end, 3919 Combos). 3920 3921 3922check_insert({T,List0,Key},Val) -> 3923 %%io:format("insert ~p into ~p\n",[Val,List0]), 3924 ets:insert(T,{Key,Val}), 3925 List1 = case (ets:info(T,type) =:= bag andalso 3926 lists:member({Key,Val},List0)) of 3927 true -> List0; 3928 false -> [{Key,Val} | List0] 3929 end, 3930 check_check({T,List1,Key}). 3931 3932check_insert_new({T,List0,Key},Val) -> 3933 %%io:format("insert_new ~p into ~p\n",[Val,List0]), 3934 Ret = ets:insert_new(T,{Key,Val}), 3935 Ret = (List0 =:= []), 3936 List1 = case Ret of 3937 true -> [{Key,Val}]; 3938 false -> List0 3939 end, 3940 check_check({T,List1,Key}). 3941 3942 3943check_delete({T,List0,Key},Val) -> 3944 %%io:format("delete ~p from ~p\n",[Val,List0]), 3945 ets:delete_object(T,{Key,Val}), 3946 List1 = lists:filter(fun(Obj) -> Obj =/= {Key,Val} end, 3947 List0), 3948 check_check({T,List1,Key}). 3949 3950check_check(S={T,List,Key}) -> 3951 case lists:reverse(ets:lookup(T,Key)) of 3952 List -> ok; 3953 ETS -> io:format("check failed:\nETS: ~p\nCHK: ~p\n", [ETS,List]), 3954 ct:fail("Invalid return value from ets:lookup") 3955 end, 3956 Items = ets:info(T,size), 3957 Items = length(List), 3958 S. 3959 3960fill_tab(Tab,Val) -> 3961 ets:insert(Tab,{key,Val}), 3962 ets:insert(Tab,{{a,144},Val}), 3963 ets:insert(Tab,{{a,key2},Val}), 3964 ets:insert(Tab,{14,Val}), 3965 ok. 3966 3967 3968%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3969 3970 3971%% OTP-2386. Multiple return elements. 3972lookup_element_mult(Config) when is_list(Config) -> 3973 repeat_for_opts(fun lookup_element_mult_do/1). 3974 3975lookup_element_mult_do(Opts) -> 3976 EtsMem = etsmem(), 3977 T = ets_new(service, [bag, {keypos, 2} | Opts]), 3978 D = lists:reverse(lem_data()), 3979 lists:foreach(fun(X) -> ets:insert(T, X) end, D), 3980 ok = lem_crash_3(T), 3981 ets:insert(T, {0, "heap_key"}), 3982 ets:lookup_element(T, "heap_key", 2), 3983 true = ets:delete(T), 3984 verify_etsmem(EtsMem). 3985 3986lem_data() -> 3987 [{service,'eddie2@boromir',{150,236,14,103},httpd88,self()}, 3988 {service,'eddie2@boromir',{150,236,14,103},httpd80,self()}, 3989 {service,'eddie3@boromir',{150,236,14,107},httpd88,self()}, 3990 {service,'eddie3@boromir',{150,236,14,107},httpd80,self()}, 3991 {service,'eddie4@boromir',{150,236,14,108},httpd88,self()}]. 3992 3993lem_crash(T) -> 3994 L = ets:lookup_element(T, 'eddie2@boromir', 3), 3995 {erlang:phash(L, 256), L}. 3996 3997lem_crash_3(T) -> 3998 lem_crash(T), 3999 io:format("Survived once~n"), 4000 lem_crash(T), 4001 io:format("Survived twice~n"), 4002 lem_crash(T), 4003 io:format("Survived all!~n"), 4004 ok. 4005 4006%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4007 4008 4009%% Check delete of an element inserted in a `filled' table. 4010delete_elem(Config) when is_list(Config) -> 4011 repeat_for_opts(fun delete_elem_do/1, 4012 [write_concurrency, all_types]). 4013 4014delete_elem_do(Opts) -> 4015 EtsMem = etsmem(), 4016 Tab = ets_new(foo,Opts), 4017 fill_tab(Tab,foo), 4018 ets:insert(Tab,{{b,key},foo}), 4019 ets:insert(Tab,{{c,key},foo}), 4020 true = ets:delete(Tab,{b,key}), 4021 [] = ets:lookup(Tab,{b,key}), 4022 [{{c,key},foo}] = ets:lookup(Tab,{c,key}), 4023 true = ets:delete(Tab), 4024 verify_etsmem(EtsMem). 4025 4026%% Check that ets:delete() works and releases the name of the 4027%% deleted table. 4028delete_tab(Config) when is_list(Config) -> 4029 repeat_for_opts(fun delete_tab_do/1, 4030 [write_concurrency,all_types]). 4031 4032delete_tab_do(Opts) -> 4033 Name = foo, 4034 EtsMem = etsmem(), 4035 Name = ets_new(Name, [named_table | Opts]), 4036 true = ets:delete(foo), 4037 %% The name should be available again. 4038 Name = ets_new(Name, [named_table | Opts]), 4039 true = ets:delete(Name), 4040 verify_etsmem(EtsMem). 4041 4042%% Check that ets:delete/1 works and that other processes can run. 4043delete_large_tab(Config) when is_list(Config) -> 4044 ct:timetrap({minutes,60}), %% valgrind needs a lot 4045 KeyRange = 16#ffffff, 4046 Data = [{erlang:phash2(I, KeyRange),I} || I <- lists:seq(1, 200000)], 4047 EtsMem = etsmem(), 4048 repeat_for_opts(fun(Opts) -> delete_large_tab_do(Config, 4049 key_range(Opts,KeyRange), 4050 Data) 4051 end), 4052 verify_etsmem(EtsMem). 4053 4054delete_large_tab_do(Config, Opts,Data) -> 4055 delete_large_tab_1(Config, foo_hash, Opts, Data, false), 4056 delete_large_tab_1(Config, foo_tree, [ordered_set | Opts], Data, false), 4057 delete_large_tab_1(Config, foo_tree, [stim_cat_ord_set | Opts], Data, false), 4058 delete_large_tab_1(Config, foo_hash_fix, Opts, Data, true). 4059 4060 4061delete_large_tab_1(Config, Name, Flags, Data, Fix) -> 4062 case is_redundant_opts_combo(Flags) of 4063 true -> skip; 4064 false -> 4065 delete_large_tab_2(Config, Name, Flags, Data, Fix) 4066 end. 4067 4068delete_large_tab_2(Config, Name, Flags, Data, Fix) -> 4069 Tab = ets_new(Name, Flags), 4070 ets:insert(Tab, Data), 4071 4072 case Fix of 4073 false -> ok; 4074 true -> 4075 true = ets:safe_fixtable(Tab, true), 4076 lists:foreach(fun({K,_}) -> ets:delete(Tab, K) end, Data) 4077 end, 4078 4079 {priority, Prio} = process_info(self(), priority), 4080 Deleter = self(), 4081 ForceTrap = proplists:get_bool(ets_force_trap, Config), 4082 [SchedTracer] 4083 = start_loopers(1, 4084 Prio, 4085 fun (SC) -> 4086 receive 4087 {trace, Deleter, out, _} -> 4088 case {ets:info(Tab), SC, ForceTrap} of 4089 {undefined, _, _} -> ok; 4090 {_, 0, true} -> 4091 %% Forced first trap of ets:delete, 4092 %% tab still reachable 4093 ok 4094 end, 4095 SC+1; 4096 {trace, 4097 Deleter, 4098 register, 4099 delete_large_tab_done_marker}-> 4100 Deleter ! {schedule_count, SC}, 4101 exit(normal); 4102 _ -> 4103 SC 4104 end 4105 end, 4106 0), 4107 SchedTracerMon = monitor(process, SchedTracer), 4108 Loopers = start_loopers(erlang:system_info(schedulers), 4109 Prio, 4110 fun (_) -> erlang:yield() end, 4111 ok), 4112 erlang:yield(), 4113 1 = erlang:trace(self(),true,[running,procs,{tracer,SchedTracer}]), 4114 true = ets:delete(Tab), 4115 %% The register stuff is just a trace marker 4116 true = register(delete_large_tab_done_marker, self()), 4117 true = unregister(delete_large_tab_done_marker), 4118 undefined = ets:info(Tab), 4119 ok = stop_loopers(Loopers), 4120 receive 4121 {schedule_count, N} -> 4122 io:format("~s: context switches: ~p", [Name,N]), 4123 if 4124 N >= 5 -> ok; 4125 true -> ct:fail(failed) 4126 end 4127 end, 4128 receive {'DOWN',SchedTracerMon,process,SchedTracer,_} -> ok end, 4129 ok. 4130 4131%% Delete a large name table and try to create a new table with 4132%% the same name in another process. 4133delete_large_named_table(Config) when is_list(Config) -> 4134 KeyRange = 16#ffffff, 4135 Data = [{erlang:phash2(I, KeyRange),I} || I <- lists:seq(1, 200000)], 4136 EtsMem = etsmem(), 4137 repeat_for_opts(fun(Opts) -> 4138 delete_large_named_table_do(key_range(Opts,KeyRange), 4139 Data) 4140 end), 4141 verify_etsmem(EtsMem), 4142 ok. 4143 4144delete_large_named_table_do(Opts,Data) -> 4145 delete_large_named_table_1(foo_hash, [named_table | Opts], Data, false), 4146 delete_large_named_table_1(foo_tree, [ordered_set,named_table | Opts], Data, false), 4147 delete_large_named_table_1(foo_tree, [stim_cat_ord_set,named_table | Opts], Data, false), 4148 delete_large_named_table_1(foo_hash, [named_table | Opts], Data, true). 4149 4150delete_large_named_table_1(Name, Flags, Data, Fix) -> 4151 case is_redundant_opts_combo(Flags) of 4152 true -> skip; 4153 false -> 4154 delete_large_named_table_2(Name, Flags, Data, Fix) 4155 end. 4156 4157delete_large_named_table_2(Name, Flags, Data, Fix) -> 4158 Tab = ets_new(Name, Flags), 4159 ets:insert(Tab, Data), 4160 4161 case Fix of 4162 false -> ok; 4163 true -> 4164 true = ets:safe_fixtable(Tab, true), 4165 lists:foreach(fun({K,_}) -> ets:delete(Tab, K) end, Data) 4166 end, 4167 {Pid, MRef} = my_spawn_opt(fun() -> 4168 receive 4169 ets_new -> 4170 ets_new(Name, [named_table]) 4171 end 4172 end, 4173 [link, monitor]), 4174 true = ets:delete(Tab), 4175 Pid ! ets_new, 4176 receive {'DOWN',MRef,process,Pid,_} -> ok end, 4177 ok. 4178 4179%% Delete a large table, and kill the process during the delete. 4180evil_delete(Config) when is_list(Config) -> 4181 KeyRange = 100000, 4182 Data = [{I,I*I} || I <- lists:seq(1, KeyRange)], 4183 repeat_for_opts(fun(Opts) -> 4184 evil_delete_do(key_range(Opts,KeyRange), 4185 Data) 4186 end). 4187 4188evil_delete_do(Opts,Data) -> 4189 EtsMem = etsmem(), 4190 evil_delete_owner(foo_hash, Opts, Data, false), 4191 verify_etsmem(EtsMem), 4192 evil_delete_owner(foo_hash, Opts, Data, true), 4193 verify_etsmem(EtsMem), 4194 evil_delete_owner(foo_tree, [ordered_set | Opts], Data, false), 4195 verify_etsmem(EtsMem), 4196 evil_delete_owner(foo_catree, [stim_cat_ord_set | Opts], Data, false), 4197 verify_etsmem(EtsMem), 4198 TabA = evil_delete_not_owner(foo_hash, Opts, Data, false), 4199 verify_etsmem(EtsMem), 4200 TabB = evil_delete_not_owner(foo_hash, Opts, Data, true), 4201 verify_etsmem(EtsMem), 4202 TabC = evil_delete_not_owner(foo_tree, [ordered_set | Opts], Data, false), 4203 verify_etsmem(EtsMem), 4204 TabD = evil_delete_not_owner(foo_catree, [stim_cat_ord_set | Opts], Data, false), 4205 verify_etsmem(EtsMem), 4206 lists:foreach(fun(T) -> undefined = ets:info(T) end, 4207 [TabA,TabB,TabC,TabD]). 4208 4209evil_delete_not_owner(Name, Flags, Data, Fix) -> 4210 case is_redundant_opts_combo(Flags) of 4211 true -> skip; 4212 false -> 4213 evil_delete_not_owner_1(Name, Flags, Data, Fix) 4214 end. 4215 4216evil_delete_not_owner_1(Name, Flags, Data, Fix) -> 4217 io:format("Not owner: ~p, fix = ~p", [Name,Fix]), 4218 Tab = ets_new(Name, [public|Flags]), 4219 ets:insert(Tab, Data), 4220 case Fix of 4221 false -> ok; 4222 true -> 4223 true = ets:safe_fixtable(Tab, true), 4224 lists:foreach(fun({K,_}) -> ets:delete(Tab, K) end, Data) 4225 end, 4226 Pid = my_spawn(fun() -> 4227 P = my_spawn_link( 4228 fun() -> 4229 receive kill -> ok end, 4230 erlang:yield(), 4231 exit(kill_linked_processes_now) 4232 end), 4233 erlang:yield(), 4234 P ! kill, 4235 true = ets:delete(Tab) 4236 end), 4237 Ref = erlang:monitor(process, Pid), 4238 receive {'DOWN',Ref,_,_,_} -> ok end, 4239 Tab. 4240 4241evil_delete_owner(Name, Flags, Data, Fix) -> 4242 case is_redundant_opts_combo(Flags) of 4243 true -> skip; 4244 false -> 4245 evil_delete_owner_1(Name, Flags, Data, Fix) 4246 end. 4247 4248evil_delete_owner_1(Name, Flags, Data, Fix) -> 4249 Fun = fun() -> 4250 Tab = ets_new(Name, [public|Flags]), 4251 ets:insert(Tab, Data), 4252 case Fix of 4253 false -> ok; 4254 true -> 4255 true = ets:safe_fixtable(Tab, true), 4256 lists:foreach(fun({K,_}) -> 4257 ets:delete(Tab, K) 4258 end, Data) 4259 end, 4260 erlang:yield(), 4261 my_spawn_link(fun() -> 4262 erlang:yield(), 4263 exit(kill_linked_processes_now) 4264 end), 4265 true = ets:delete(Tab) 4266 end, 4267 Pid = my_spawn(Fun), 4268 Ref = erlang:monitor(process, Pid), 4269 receive {'DOWN',Ref,_,_,_} -> ok end. 4270 4271 4272exit_large_table_owner(Config) when is_list(Config) -> 4273 %%Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 500000)], 4274 Laps = 500000 div syrup_factor(), 4275 FEData = fun(Do) -> repeat_while(fun(I) when I =:= Laps -> {false,ok}; 4276 (I) -> Do({erlang:phash2(I, 16#ffffff),I}), 4277 {true, I+1} 4278 end, 1) 4279 end, 4280 EtsMem = etsmem(), 4281 repeat_for_opts(fun(Opts) -> 4282 exit_large_table_owner_do(Opts, 4283 FEData, 4284 Config) 4285 end), 4286 verify_etsmem(EtsMem). 4287 4288exit_large_table_owner_do(Opts, FEData, Config) -> 4289 verify_rescheduling_exit(Config, FEData, [named_table | Opts], true, 1, 1), 4290 verify_rescheduling_exit(Config, FEData, Opts, false, 1, 1). 4291 4292exit_many_large_table_owner(Config) when is_list(Config) -> 4293 ct:timetrap({minutes,30}), %% valgrind needs a lot 4294 %%Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 500000)], 4295 Laps = 500000 div syrup_factor(), 4296 FEData = fun(Do) -> repeat_while(fun(I) when I =:= Laps -> {false,ok}; 4297 (I) -> Do({erlang:phash2(I, 16#ffffff),I}), 4298 {true, I+1} 4299 end, 1) 4300 end, 4301 EtsMem = etsmem(), 4302 repeat_for_opts(fun(Opts) -> exit_many_large_table_owner_do(Opts,FEData,Config) end), 4303 verify_etsmem(EtsMem). 4304 4305exit_many_large_table_owner_do(Opts,FEData,Config) -> 4306 verify_rescheduling_exit(Config, FEData, Opts, true, 1, 4), 4307 verify_rescheduling_exit(Config, FEData, [named_table | Opts], false, 1, 4). 4308 4309exit_many_tables_owner(Config) when is_list(Config) -> 4310 NoData = fun(_Do) -> ok end, 4311 EtsMem = etsmem(), 4312 verify_rescheduling_exit(Config, NoData, [named_table], false, 1000, 1), 4313 verify_rescheduling_exit(Config, NoData, [named_table,{write_concurrency,true}], false, 1000, 1), 4314 verify_etsmem(EtsMem). 4315 4316exit_many_many_tables_owner(Config) when is_list(Config) -> 4317 Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 50)], 4318 FEData = fun(Do) -> lists:foreach(Do, Data) end, 4319 repeat_for_opts(fun(Opts) -> exit_many_many_tables_owner_do(Opts,FEData,Config) end). 4320 4321exit_many_many_tables_owner_do(Opts,FEData,Config) -> 4322 verify_rescheduling_exit(Config, FEData, [named_table | Opts], true, 200, 5), 4323 verify_rescheduling_exit(Config, FEData, Opts, false, 200, 5), 4324 wait_for_test_procs(), 4325 EtsMem = etsmem(), 4326 verify_rescheduling_exit(Config, FEData, Opts, true, 200, 5), 4327 verify_rescheduling_exit(Config, FEData, [named_table | Opts], false, 200, 5), 4328 verify_etsmem(EtsMem). 4329 4330 4331count_exit_sched(TP) -> 4332 receive 4333 {trace, TP, in_exiting, 0} -> 4334 count_exit_sched_out(TP, 1); 4335 {trace, TP, out_exiting, 0} -> 4336 count_exit_sched_in(TP, 1); 4337 {trace, TP, out_exited, 0} -> 4338 0 4339 end. 4340 4341count_exit_sched_in(TP, N) -> 4342 receive 4343 {trace, TP, in_exiting, 0} -> 4344 count_exit_sched_out(TP, N); 4345 {trace, TP, _, _} = Msg -> 4346 exit({unexpected_trace_msg, Msg}) 4347 end. 4348 4349count_exit_sched_out(TP, N) -> 4350 receive 4351 {trace, TP, out_exiting, 0} -> 4352 count_exit_sched_in(TP, N+1); 4353 {trace, TP, out_exited, 0} -> 4354 N; 4355 {trace, TP, _, _} = Msg -> 4356 exit({unexpected_trace_msg, Msg}) 4357 end. 4358 4359vre_fix_tables(Tab) -> 4360 Parent = self(), 4361 Go = make_ref(), 4362 my_spawn_link(fun () -> 4363 true = ets:safe_fixtable(Tab, true), 4364 Parent ! Go, 4365 receive infinity -> ok end 4366 end), 4367 receive Go -> ok end, 4368 ok. 4369 4370verify_rescheduling_exit(Config, ForEachData, Flags, Fix, NOTabs, NOProcs) -> 4371 NoFix = 5, 4372 TestCase = atom_to_list(proplists:get_value(test_case, Config)), 4373 Parent = self(), 4374 KillMe = make_ref(), 4375 PFun = 4376 fun () -> 4377 repeat( 4378 fun () -> 4379 Uniq = erlang:unique_integer([positive]), 4380 Name = list_to_atom(TestCase ++ "-" ++ 4381 integer_to_list(Uniq)), 4382 Tab = ets_new(Name, Flags), 4383 ForEachData(fun(Data) -> ets:insert(Tab, Data) end), 4384 case Fix of 4385 false -> ok; 4386 true -> 4387 lists:foreach(fun (_) -> 4388 vre_fix_tables(Tab) 4389 end, 4390 lists:seq(1,NoFix)), 4391 KeyPos = ets:info(Tab,keypos), 4392 ForEachData(fun(Data) -> 4393 ets:delete(Tab, element(KeyPos,Data)) 4394 end) 4395 end 4396 end, 4397 NOTabs), 4398 Parent ! {KillMe, self()}, 4399 receive after infinity -> ok end 4400 end, 4401 TPs = lists:map(fun (_) -> 4402 TP = my_spawn_link(PFun), 4403 1 = erlang:trace(TP, true, [exiting]), 4404 TP 4405 end, 4406 lists:seq(1, NOProcs)), 4407 lists:foreach(fun (TP) -> 4408 receive {KillMe, TP} -> ok end 4409 end, 4410 TPs), 4411 LPs = start_loopers(erlang:system_info(schedulers), 4412 normal, 4413 fun (_) -> 4414 erlang:yield() 4415 end, 4416 ok), 4417 lists:foreach(fun (TP) -> 4418 unlink(TP), 4419 exit(TP, bang) 4420 end, 4421 TPs), 4422 lists:foreach(fun (TP) -> 4423 XScheds = count_exit_sched(TP), 4424 io:format("~p XScheds=~p~n", 4425 [TP, XScheds]), 4426 true = XScheds >= 3 4427 end, 4428 TPs), 4429 stop_loopers(LPs), 4430 ok. 4431 4432 4433 4434%% Make sure that slots for ets tables are cleared properly. 4435table_leak(Config) when is_list(Config) -> 4436 repeat_for_opts_all_non_stim_table_types(fun(Opts) -> table_leak_1(Opts,20000) end). 4437 4438table_leak_1(_,0) -> ok; 4439table_leak_1(Opts,N) -> 4440 T = ets_new(fooflarf, Opts), 4441 true = ets:delete(T), 4442 table_leak_1(Opts,N-1). 4443 4444%% Check proper return values for illegal delete operations. 4445baddelete(Config) when is_list(Config) -> 4446 EtsMem = etsmem(), 4447 {'EXIT',{badarg,_}} = (catch ets:delete(foo)), 4448 Tab = ets_new(foo,[]), 4449 true = ets:delete(Tab), 4450 {'EXIT',{badarg,_}} = (catch ets:delete(Tab)), 4451 verify_etsmem(EtsMem). 4452 4453%% Check that match_delete works. Also tests tab2list function. 4454match_delete(Config) when is_list(Config) -> 4455 EtsMem = etsmem(), 4456 repeat_for_opts(fun match_delete_do/1, 4457 [write_concurrency,all_types]), 4458 verify_etsmem(EtsMem). 4459 4460match_delete_do(Opts) -> 4461 EtsMem = etsmem(), 4462 Tab = ets_new(kad,Opts), 4463 fill_tab(Tab,foo), 4464 ets:insert(Tab,{{c,key},bar}), 4465 _ = ets:match_delete(Tab,{'_',foo}), 4466 [{{c,key},bar}] = ets:tab2list(Tab), 4467 _ = ets:match_delete(Tab,'_'), 4468 [] = ets:tab2list(Tab), 4469 true = ets:delete(Tab), 4470 verify_etsmem(EtsMem). 4471 4472%% OTP-3005: check match_delete with constant argument. 4473match_delete3(Config) when is_list(Config) -> 4474 repeat_for_opts(fun match_delete3_do/1). 4475 4476match_delete3_do(Opts) -> 4477 EtsMem = etsmem(), 4478 T = make_table(test, 4479 [duplicate_bag | Opts], 4480 [{aa,17}, 4481 {cA,1000}, 4482 {cA,17}, 4483 {cA,1000}, 4484 {aa,17}]), 4485 %% 'aa' and 'cA' have the same hash value in the current 4486 %% implementation. This causes the aa's to precede the cA's, to make 4487 %% the test more interesting. 4488 [{cA,1000},{cA,1000}] = ets:match_object(T, {'_', 1000}), 4489 ets:match_delete(T, {cA,1000}), 4490 [] = ets:match_object(T, {'_', 1000}), 4491 ets:delete(T), 4492 verify_etsmem(EtsMem). 4493 4494 4495%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4496 4497%% Test ets:first/1 & ets:next/2. 4498firstnext(Config) when is_list(Config) -> 4499 repeat_for_opts_all_set_table_types(fun firstnext_do/1). 4500 4501firstnext_do(Opts) -> 4502 EtsMem = etsmem(), 4503 Tab = ets_new(foo,Opts), 4504 [] = firstnext_collect(Tab,ets:first(Tab),[]), 4505 fill_tab(Tab,foo), 4506 Len = length(ets:tab2list(Tab)), 4507 Len = length(firstnext_collect(Tab,ets:first(Tab),[])), 4508 true = ets:delete(Tab), 4509 verify_etsmem(EtsMem). 4510 4511firstnext_collect(_Tab,'$end_of_table',List) -> 4512 List; 4513firstnext_collect(Tab,Key,List) -> 4514 firstnext_collect(Tab,ets:next(Tab,Key),[Key|List]). 4515 4516 4517%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4518 4519%% Tests ets:first/1 & ets:next/2. 4520firstnext_concurrent(Config) when is_list(Config) -> 4521 lists:foreach( 4522 fun(TableType) -> 4523 register(master, self()), 4524 TableName = list_to_atom(atom_to_list(?MODULE) ++ atom_to_list(TableType)), 4525 ets_init(TableName, 20, TableType), 4526 [dynamic_go(TableName) || _ <- lists:seq(1, 2)], 4527 receive 4528 after 5000 -> ok 4529 end, 4530 unregister(master) 4531 end, repeat_for_opts_atom2list(ord_set_types)). 4532 4533ets_init(Tab, N, TableType) -> 4534 ets_new(Tab, [named_table,public,TableType]), 4535 cycle(Tab, lists:seq(1,N+1)). 4536 4537cycle(_Tab, [H|T]) when H > length(T)-> ok; 4538cycle(Tab, L) -> 4539 ets:insert(Tab,list_to_tuple(L)), 4540 cycle(Tab, tl(L)++[hd(L)]). 4541 4542dynamic_go(TableName) -> my_spawn_link(fun() -> dynamic_init(TableName) end). 4543 4544dynamic_init(TableName) -> [dyn_lookup(TableName) || _ <- lists:seq(1, 10)]. 4545 4546dyn_lookup(T) -> dyn_lookup(T, ets:first(T)). 4547 4548dyn_lookup(_T, '$end_of_table') -> []; 4549dyn_lookup(T, K) -> 4550 NextKey = ets:next(T,K), 4551 case ets:next(T,K) of 4552 NextKey -> 4553 dyn_lookup(T, NextKey); 4554 NK -> 4555 io:fwrite("hmmm... ~p =/= ~p~n", [NextKey,NK]), 4556 exit(failed) 4557 end. 4558 4559%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4560 4561slot(Config) when is_list(Config) -> 4562 repeat_for_opts_all_set_table_types(fun slot_do/1). 4563 4564slot_do(Opts) -> 4565 EtsMem = etsmem(), 4566 Tab = ets_new(foo,Opts), 4567 fill_tab(Tab,foo), 4568 Elts = ets:info(Tab,size), 4569 Elts = slot_loop(Tab,0,0), 4570 case ets:info(Tab, type) of 4571 ordered_set -> 4572 '$end_of_table' = ets:slot(Tab,Elts); 4573 _ -> ok 4574 end, 4575 true = ets:delete(Tab), 4576 verify_etsmem(EtsMem). 4577 4578slot_loop(Tab,SlotNo,EltsSoFar) -> 4579 case ets:slot(Tab,SlotNo) of 4580 '$end_of_table' -> 4581 {'EXIT',{badarg,_}} = 4582 (catch ets:slot(Tab,SlotNo+1)), 4583 EltsSoFar; 4584 Elts -> 4585 slot_loop(Tab,SlotNo+1,EltsSoFar+length(Elts)) 4586 end. 4587 4588%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4589 4590hash_clash(Config) when is_list(Config) -> 4591 %% ensure that erlang:phash2 and ets:slot use different hash seed 4592 Tab = ets:new(tab, [set]), 4593 Buckets = erlang:element(1, ets:info(Tab, stats)), 4594 Phash = erlang:phash2(<<"123">>, Buckets), 4595 true = ets:insert(Tab, {<<"123">>, "extra"}), 4596 [] = ets:slot(Tab, Phash). 4597 4598%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4599 4600 4601match1(Config) when is_list(Config) -> 4602 repeat_for_opts_all_set_table_types(fun match1_do/1). 4603 4604match1_do(Opts) -> 4605 EtsMem = etsmem(), 4606 Tab = ets_new(foo,Opts), 4607 fill_tab(Tab,foo), 4608 [] = ets:match(Tab,{}), 4609 ets:insert(Tab,{{one,4},4}), 4610 ets:insert(Tab,{{one,5},5}), 4611 ets:insert(Tab,{{two,4},4}), 4612 ets:insert(Tab,{{two,5},6}), 4613 case ets:match(Tab,{{one,'_'},'$0'}) of 4614 [[4],[5]] -> ok; 4615 [[5],[4]] -> ok 4616 end, 4617 case ets:match(Tab,{{two,'$1'},'$0'}) of 4618 [[4,4],[6,5]] -> ok; 4619 [[6,5],[4,4]] -> ok 4620 end, 4621 case ets:match(Tab,{{two,'$9'},'$4'}) of 4622 [[4,4],[6,5]] -> ok; 4623 [[6,5],[4,4]] -> ok 4624 end, 4625 case ets:match(Tab,{{two,'$9'},'$22'}) of 4626 [[4,4],[5,6]] -> ok; 4627 [[5,6],[4,4]] -> ok 4628 end, 4629 [[4]] = ets:match(Tab,{{two,'$0'},'$0'}), 4630 Len = length(ets:match(Tab,'$0')), 4631 Len = length(ets:match(Tab,'_')), 4632 if Len > 4 -> ok end, 4633 true = ets:delete(Tab), 4634 verify_etsmem(EtsMem). 4635 4636%% Test match with specified keypos bag table. 4637match2(Config) when is_list(Config) -> 4638 repeat_for_opts(fun match2_do/1). 4639 4640match2_do(Opts) -> 4641 EtsMem = etsmem(), 4642 Tab = make_table(foobar, 4643 [bag, named_table, {keypos, 2} | Opts], 4644 [{value1, key1}, 4645 {value2_1, key2}, 4646 {value2_2, key2}, 4647 {value3_1, key3}, 4648 {value3_2, key3}, 4649 {value2_1, key2_wannabe}]), 4650 case length(ets:match(Tab, '$1')) of 4651 6 -> ok; 4652 _ -> ct:fail("Length of matched list is wrong.") 4653 end, 4654 [[value3_1],[value3_2]] = ets:match(Tab, {'$1', key3}), 4655 [[key1]] = ets:match(Tab, {value1, '$1'}), 4656 [[key2_wannabe],[key2]] = ets:match(Tab, {value2_1, '$2'}), 4657 [] = ets:match(Tab,{'$1',nosuchkey}), 4658 [] = ets:match(Tab,{'$1',kgY2}), % same hash as key2 4659 [] = ets:match(Tab,{nosuchvalue,'$1'}), 4660 true = ets:delete(Tab), 4661 verify_etsmem(EtsMem). 4662 4663%% Some ets:match_object tests. 4664match_object(Config) when is_list(Config) -> 4665 repeat_for_opts_all_set_table_types(fun match_object_do/1). 4666 4667match_object_do(Opts) -> 4668 EtsMem = etsmem(), 4669 Tab = ets_new(foobar, Opts), 4670 fill_tab(Tab, foo), 4671 ets:insert(Tab,{{one,4},4}), 4672 ets:insert(Tab,{{one,5},5}), 4673 ets:insert(Tab,{{two,4},4}), 4674 ets:insert(Tab,{{two,5},6}), 4675 ets:insert(Tab, {#{camembert=>cabécou},7}), 4676 ets:insert(Tab, {#{"hi"=>"hello","wazzup"=>"awesome","1337"=>"42"},8}), 4677 ets:insert(Tab, {#{"hi"=>"hello",#{"wazzup"=>3}=>"awesome","1337"=>"42"},9}), 4678 ets:insert(Tab, {#{"hi"=>"hello","wazzup"=>#{"awesome"=>3},"1337"=>"42"},10}), 4679 Is = lists:seq(1,100), 4680 M1 = maps:from_list([{I,I}||I <- Is]), 4681 M2 = maps:from_list([{I,"hi"}||I <- Is]), 4682 ets:insert(Tab, {M1,11}), 4683 ets:insert(Tab, {M2,12}), 4684 4685 case ets:match_object(Tab, {{one, '_'}, '$0'}) of 4686 [{{one,5},5},{{one,4},4}] -> ok; 4687 [{{one,4},4},{{one,5},5}] -> ok; 4688 _ -> ct:fail("ets:match_object() returned something funny.") 4689 end, 4690 case ets:match_object(Tab, {{two, '$1'}, '$0'}) of 4691 [{{two,5},6},{{two,4},4}] -> ok; 4692 [{{two,4},4},{{two,5},6}] -> ok; 4693 _ -> ct:fail("ets:match_object() returned something funny.") 4694 end, 4695 case ets:match_object(Tab, {{two, '$9'}, '$4'}) of 4696 [{{two,5},6},{{two,4},4}] -> ok; 4697 [{{two,4},4},{{two,5},6}] -> ok; 4698 _ -> ct:fail("ets:match_object() returned something funny.") 4699 end, 4700 case ets:match_object(Tab, {{two, '$9'}, '$22'}) of 4701 [{{two,5},6},{{two,4},4}] -> ok; 4702 [{{two,4},4},{{two,5},6}] -> ok; 4703 _ -> ct:fail("ets:match_object() returned something funny.") 4704 end, 4705 4706 %% Check that maps are inspected for variables. 4707 [{#{camembert:=cabécou},7}] = ets:match_object(Tab, {#{camembert=>'_'},7}), 4708 4709 [{#{"hi":="hello",#{"wazzup"=>3}:="awesome","1337":="42"},9}] = 4710 ets:match_object(Tab, {#{#{"wazzup"=>3}=>"awesome","hi"=>"hello","1337"=>"42"},9}), 4711 [{#{"hi":="hello",#{"wazzup"=>3}:="awesome","1337":="42"},9}] = 4712 ets:match_object(Tab, {#{#{"wazzup"=>3}=>"awesome","hi"=>"hello","1337"=>'_'},'_'}), 4713 [{#{"hi":="hello","wazzup":=#{"awesome":=3},"1337":="42"},10}] = 4714 ets:match_object(Tab, {#{"wazzup"=>'_',"hi"=>'_',"1337"=>'_'},10}), 4715 4716 %% multiple patterns 4717 Pat = {{#{#{"wazzup"=>3}=>"awesome","hi"=>"hello","1337"=>'_'},'$1'},[{is_integer,'$1'}],['$_']}, 4718 [{#{"hi":="hello",#{"wazzup"=>3}:="awesome","1337":="42"},9}] = 4719 ets:select(Tab, [Pat,Pat,Pat,Pat]), 4720 case ets:match_object(Tab, {#{"hi"=>"hello","wazzup"=>'_',"1337"=>"42"},'_'}) of 4721 [{#{"1337" := "42","hi" := "hello","wazzup" := "awesome"},8}, 4722 {#{"1337" := "42","hi" := "hello","wazzup" := #{"awesome" := 3}},10}] -> ok; 4723 [{#{"1337" := "42","hi" := "hello","wazzup" := #{"awesome" := 3}},10}, 4724 {#{"1337" := "42","hi" := "hello","wazzup" := "awesome"},8}] -> ok; 4725 _ -> ct:fail("ets:match_object() returned something funny.") 4726 end, 4727 case ets:match_object(Tab, {#{"hi"=>'_'},'_'}) of 4728 [{#{"1337":="42", "hi":="hello"},_}, 4729 {#{"1337":="42", "hi":="hello"},_}, 4730 {#{"1337":="42", "hi":="hello"},_}] -> ok; 4731 _ -> ct:fail("ets:match_object() returned something funny.") 4732 end, 4733 4734 %% match large maps 4735 [{#{1:=1,2:=2,99:=99,100:=100},11}] = ets:match_object(Tab, {M1,11}), 4736 [{#{1:="hi",2:="hi",99:="hi",100:="hi"},12}] = ets:match_object(Tab, {M2,12}), 4737 case ets:match_object(Tab, {#{1=>'_',2=>'_'},'_'}) of 4738 %% only match a part of the map 4739 [{#{1:=1,5:=5,99:=99,100:=100},11},{#{1:="hi",6:="hi",99:="hi"},12}] -> ok; 4740 [{#{1:="hi",2:="hi",59:="hi"},12},{#{1:=1,2:=2,39:=39,100:=100},11}] -> ok; 4741 _ -> ct:fail("ets:match_object() returned something funny.") 4742 end, 4743 case ets:match_object(Tab, {maps:from_list([{I,'_'}||I<-Is]),'_'}) of 4744 %% only match a part of the map 4745 [{#{1:=1,5:=5,99:=99,100:=100},11},{#{1:="hi",6:="hi",99:="hi"},12}] -> ok; 4746 [{#{1:="hi",2:="hi",59:="hi"},12},{#{1:=1,2:=2,39:=39,100:=100},11}] -> ok; 4747 _ -> ct:fail("ets:match_object() returned something funny.") 4748 end, 4749 {'EXIT',{badarg,_}} = (catch ets:match_object(Tab, {#{'$1'=>'_'},7})), 4750 Mve = maps:from_list([{list_to_atom([$$|integer_to_list(I)]),'_'}||I<-Is]), 4751 {'EXIT',{badarg,_}} = (catch ets:match_object(Tab, {Mve,11})), 4752 4753 %% Check that unsuccessful match returns an empty list. 4754 [] = ets:match_object(Tab, {{three,'$0'}, '$92'}), 4755 %% Check that '$0' equals '_'. 4756 Len = length(ets:match_object(Tab, '$0')), 4757 Len = length(ets:match_object(Tab, '_')), 4758 if Len > 4 -> ok end, 4759 true = ets:delete(Tab), 4760 verify_etsmem(EtsMem). 4761 4762%% Tests that db_match_object does not generate a `badarg' when 4763%% resuming a search with no previous matches. 4764match_object2(Config) when is_list(Config) -> 4765 repeat_for_opts_all_table_types(fun match_object2_do/1). 4766 4767match_object2_do(Opts) -> 4768 EtsMem = etsmem(), 4769 KeyRange = 13005, 4770 Tab = ets_new(foo, [{keypos, 2} | Opts], KeyRange), 4771 fill_tab2(Tab, 0, KeyRange), % match_db_object does 1000 4772 % elements per pass, might 4773 % change in the future. 4774 [] = ets:match_object(Tab, {hej, '$1'}), 4775 ets:delete(Tab), 4776 verify_etsmem(EtsMem). 4777 4778%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4779 4780 4781%% OTP-3319. Test tab2list. 4782tab2list(Config) when is_list(Config) -> 4783 repeat_for_all_ord_set_table_types( 4784 fun(Opts) -> 4785 EtsMem = etsmem(), 4786 Tab = make_table(foo, 4787 Opts, 4788 [{a,b}, {c,b}, {b,b}, {a,c}]), 4789 [{a,c},{b,b},{c,b}] = ets:tab2list(Tab), 4790 true = ets:delete(Tab), 4791 verify_etsmem(EtsMem) 4792 end). 4793 4794%% Simple general small test. If this fails, ets is in really bad 4795%% shape. 4796misc1(Config) when is_list(Config) -> 4797 repeat_for_opts_all_table_types(fun misc1_do/1). 4798 4799misc1_do(Opts) -> 4800 EtsMem = etsmem(), 4801 Tab = ets_new(foo,Opts), 4802 true = lists:member(Tab,ets:all()), 4803 ets:delete(Tab), 4804 false = lists:member(Tab,ets:all()), 4805 case catch ets:delete(Tab) of 4806 {'EXIT',_Reason} -> 4807 verify_etsmem(EtsMem); 4808 true -> 4809 ct:fail("Delete of nonexisting table returned `true'.") 4810 end, 4811 ok. 4812 4813%% Check the safe_fixtable function. 4814safe_fixtable(Config) when is_list(Config) -> 4815 repeat_for_opts_all_table_types(fun safe_fixtable_do/1). 4816 4817safe_fixtable_do(Opts) -> 4818 EtsMem = etsmem(), 4819 Tab = ets_new(foo, Opts), 4820 fill_tab(Tab, foobar), 4821 true = ets:safe_fixtable(Tab, true), 4822 receive after 1 -> ok end, 4823 true = ets:safe_fixtable(Tab, false), 4824 false = ets:info(Tab,safe_fixed_monotonic_time), 4825 false = ets:info(Tab,safe_fixed), 4826 SysBefore = erlang:timestamp(), 4827 MonBefore = erlang:monotonic_time(), 4828 true = ets:safe_fixtable(Tab, true), 4829 MonAfter = erlang:monotonic_time(), 4830 SysAfter = erlang:timestamp(), 4831 Self = self(), 4832 {FixMonTime,[{Self,1}]} = ets:info(Tab,safe_fixed_monotonic_time), 4833 {FixSysTime,[{Self,1}]} = ets:info(Tab,safe_fixed), 4834 true = is_integer(FixMonTime), 4835 true = MonBefore =< FixMonTime, 4836 true = FixMonTime =< MonAfter, 4837 {FstMs,FstS,FstUs} = FixSysTime, 4838 true = is_integer(FstMs), 4839 true = is_integer(FstS), 4840 true = is_integer(FstUs), 4841 case erlang:system_info(time_warp_mode) of 4842 no_time_warp -> 4843 true = timer:now_diff(FixSysTime, SysBefore) >= 0, 4844 true = timer:now_diff(SysAfter, FixSysTime) >= 0; 4845 _ -> 4846 %% ets:info(Tab,safe_fixed) not timewarp safe... 4847 ignore 4848 end, 4849 %% Test that an unjustified 'unfix' is a no-op. 4850 {Pid,MRef} = my_spawn_monitor(fun() -> true = ets:safe_fixtable(Tab,false) end), 4851 {'DOWN', MRef, process, Pid, normal} = receive M -> M end, 4852 true = ets:info(Tab,fixed), 4853 {FixMonTime,[{Self,1}]} = ets:info(Tab,safe_fixed_monotonic_time), 4854 {FixSysTime,[{Self,1}]} = ets:info(Tab,safe_fixed), 4855 %% badarg's 4856 {'EXIT', {badarg, _}} = (catch ets:safe_fixtable(Tab, foobar)), 4857 true = ets:info(Tab,fixed), 4858 true = ets:safe_fixtable(Tab, false), 4859 false = ets:info(Tab,fixed), 4860 {'EXIT', {badarg, _}} = (catch ets:safe_fixtable(Tab, foobar)), 4861 false = ets:info(Tab,fixed), 4862 ets:delete(Tab), 4863 case catch ets:safe_fixtable(Tab, true) of 4864 {'EXIT', _Reason} -> 4865 verify_etsmem(EtsMem); 4866 _ -> 4867 ct:fail("Fixtable on nonexisting table returned `true'") 4868 end, 4869 ok. 4870 4871-define(ets_info(Tab,Item,SlavePid), ets_info(Tab, Item, SlavePid, ?LINE)). 4872 4873%% Tests ets:info result for required tuples. 4874info(Config) when is_list(Config) -> 4875 repeat_for_opts(fun info_do/1, 4876 [[void, set, bag, duplicate_bag, ordered_set], 4877 [void, private, protected, public], 4878 write_concurrency, read_concurrency, compressed]), 4879 4880 undefined = ets:info(non_existing_table_xxyy), 4881 undefined = ets:info(non_existing_table_xxyy,type), 4882 undefined = ets:info(non_existing_table_xxyy,node), 4883 undefined = ets:info(non_existing_table_xxyy,named_table), 4884 undefined = ets:info(non_existing_table_xxyy,safe_fixed_monotonic_time), 4885 undefined = ets:info(non_existing_table_xxyy,safe_fixed), 4886 4887 {'EXIT',{badarg,_}} = (catch ets:info(42)), 4888 {'EXIT',{badarg,_}} = (catch ets:info(42, type)), 4889 {'EXIT',{badarg,_}} = (catch ets:info(make_ref())), 4890 {'EXIT',{badarg,_}} = (catch ets:info(make_ref(), type)), 4891 4892 ok. 4893 4894info_do(Opts) -> 4895 EtsMem = etsmem(), 4896 TableType = lists:foldl( 4897 fun(Item, Curr) -> 4898 case Item of 4899 set -> set; 4900 ordered_set -> ordered_set; 4901 cat_ord_set -> ordered_set; 4902 stim_cat_ord_set -> ordered_set; 4903 bag -> bag; 4904 duplicate_bag -> duplicate_bag; 4905 _ -> Curr 4906 end 4907 end, set, Opts), 4908 PublicOrCurr = 4909 fun(Curr) -> 4910 case lists:member({write_concurrency, false}, Opts) or 4911 lists:member(private, Opts) or 4912 lists:member(protected, Opts) of 4913 true -> Curr; 4914 false -> public 4915 end 4916 end, 4917 Protection = lists:foldl( 4918 fun(Item, Curr) -> 4919 case Item of 4920 public -> public; 4921 protected -> protected; 4922 private -> private; 4923 cat_ord_set -> PublicOrCurr(Curr); %% Special items 4924 stim_cat_ord_set -> PublicOrCurr(Curr); 4925 _ -> Curr 4926 end 4927 end, protected, Opts), 4928 MeMyselfI=self(), 4929 ThisNode=node(), 4930 Tab = ets_new(foobar, [{keypos, 2} | Opts]), 4931 4932 %% Start slave to also do ets:info from a process not owning the table. 4933 SlavePid = spawn_link(fun Slave() -> 4934 receive 4935 {Master, Item} -> 4936 Master ! {self(), Item, ets:info(Tab, Item)} 4937 end, 4938 Slave() 4939 end), 4940 4941 %% Note: ets:info/1 used to return a tuple, but from R11B onwards it 4942 %% returns a list. 4943 Res = ets:info(Tab), 4944 {value, {memory, _Mem}} = lists:keysearch(memory, 1, Res), 4945 {value, {owner, MeMyselfI}} = lists:keysearch(owner, 1, Res), 4946 {value, {name, foobar}} = lists:keysearch(name, 1, Res), 4947 {value, {size, 0}} = lists:keysearch(size, 1, Res), 4948 {value, {node, ThisNode}} = lists:keysearch(node, 1, Res), 4949 {value, {named_table, false}} = lists:keysearch(named_table, 1, Res), 4950 {value, {type, TableType}} = lists:keysearch(type, 1, Res), 4951 {value, {keypos, 2}} = lists:keysearch(keypos, 1, Res), 4952 {value, {protection, Protection}} = 4953 lists:keysearch(protection, 1, Res), 4954 {value, {id, Tab}} = lists:keysearch(id, 1, Res), 4955 {value, {decentralized_counters, _DecentralizedCtrs}} = 4956 lists:keysearch(decentralized_counters, 1, Res), 4957 4958 %% Test 'binary' 4959 [] = ?ets_info(Tab, binary, SlavePid), 4960 BinSz = 100, 4961 RefcBin = list_to_binary(lists:seq(1,BinSz)), 4962 ets:insert(Tab, {RefcBin,key}), 4963 [{BinPtr,BinSz,2}] = ?ets_info(Tab,binary, SlavePid), 4964 ets:insert(Tab, {RefcBin,key2}), 4965 [{BinPtr,BinSz,3}, {BinPtr,BinSz,3}] = ?ets_info(Tab,binary,SlavePid), 4966 ets:delete(Tab, key), 4967 [{BinPtr,BinSz,2}] = ?ets_info(Tab,binary, SlavePid), 4968 case TableType of 4969 ordered_set -> 4970 ets:delete(Tab, key2); 4971 _ -> 4972 ets:safe_fixtable(Tab, true), 4973 ets:delete(Tab, key2), 4974 [{BinPtr,BinSz,2}] = ?ets_info(Tab,binary, SlavePid), 4975 ets:safe_fixtable(Tab, false) 4976 end, 4977 [] = ?ets_info(Tab,binary, SlavePid), 4978 RefcBin = id(RefcBin), % keep alive 4979 4980 unlink(SlavePid), 4981 exit(SlavePid,kill), 4982 4983 true = ets:delete(Tab), 4984 verify_etsmem(EtsMem). 4985 4986ets_info(Tab, Item, SlavePid, _Line) -> 4987 R = ets:info(Tab, Item), 4988 %%io:format("~p: ets:info(~p) -> ~p\n", [_Line, Item, R]), 4989 SlavePid ! {self(), Item}, 4990 {SlavePid, Item, R} = receive M -> M end, 4991 R. 4992 4993 4994 4995info_binary_stress(_Config) -> 4996 repeat_for_opts(fun info_binary_stress_do/1, 4997 [[set,bag,duplicate_bag,ordered_set], 4998 compressed]). 4999 5000info_binary_stress_do(Opts) -> 5001 Tab = ets_new(info_binary_stress, [public, {write_concurrency,true} | Opts]), 5002 5003 KeyRange = 1000, 5004 ValueRange = 3, 5005 RefcBin = list_to_binary(lists:seq(1,100)), 5006 InitF = fun (_) -> #{insert => 0, delete => 0, delete_object => 0} 5007 end, 5008 ExecF = fun (Counters) -> 5009 Key = rand:uniform(KeyRange), 5010 Value = rand:uniform(ValueRange), 5011 Op = element(rand:uniform(4),{insert,insert,delete,delete_object}), 5012 case Op of 5013 insert -> 5014 ets:insert(Tab, {Key,Value,RefcBin}); 5015 delete -> 5016 ets:delete(Tab, Key); 5017 delete_object -> 5018 ets:delete_object(Tab, {Key,Value,RefcBin}) 5019 end, 5020 Acc = incr_counter(Op, Counters), 5021 5022 receive stop -> 5023 [end_of_work | Acc] 5024 after 0 -> 5025 Acc 5026 end 5027 end, 5028 FiniF = fun (Acc) -> Acc end, 5029 Pids = run_sched_workers(InitF, ExecF, FiniF, infinite), 5030 timer:send_after(500, stop), 5031 5032 Rounds = fun Loop(N, Fix) -> 5033 ets:info(Tab, binary), 5034 ets:safe_fixtable(Tab, Fix), 5035 receive 5036 stop -> 5037 ets:safe_fixtable(Tab, false), 5038 false = ets:info(Tab, fixed), 5039 N 5040 after 0 -> 5041 Loop(N+1, not Fix) 5042 end 5043 end (1, true), 5044 [P ! stop || P <- Pids], 5045 Results = wait_pids(Pids), 5046 Size = ets:info(Tab,size), 5047 io:format("Ops = ~p\n", [maps_sum(Results)]), 5048 io:format("Size = ~p\n", [Size]), 5049 io:format("Stats = ~p\n", [ets:info(Tab,stats)]), 5050 io:format("Rounds = ~p\n", [Rounds]), 5051 Size = length(ets:info(Tab, binary)), 5052 5053 ets:delete_all_objects(Tab), 5054 [] = ets:info(Tab, binary), 5055 true = ets:delete(Tab), 5056 ok. 5057 5058 5059size_loop(_T, 0, _, _) -> 5060 ok; 5061size_loop(T, I, PrevSize, WhatToTest) -> 5062 Size = ets:info(T, WhatToTest), 5063 case Size < PrevSize of 5064 true -> 5065 io:format("Bad ets:info/2 (got ~p expected >=~p)", 5066 [Size, PrevSize]), 5067 ct:fail("Bad ets:info/2)"); 5068 _ -> ok 5069 end, 5070 size_loop(T, I -1, Size, WhatToTest). 5071 5072add_loop(_T, 0) -> 5073 ok; 5074add_loop(T, I) -> 5075 ets:insert(T, {I}), 5076 add_loop(T, I -1). 5077 5078 5079test_table_counter_concurrency(WhatToTest, TableOptions) -> 5080 IntStatePrevOn = 5081 erts_debug:set_internal_state(available_internal_state, true), 5082 ItemsToAdd = 1000000, 5083 SizeLoopSize = 1000, 5084 T = ets:new(k, TableOptions), 5085 case lists:member(ordered_set, TableOptions) of 5086 true -> 5087 erts_debug:set_internal_state(ets_debug_random_split_join, {T, false}); 5088 false -> ok 5089 end, 5090 0 = ets:info(T, size), 5091 P = self(), 5092 SpawnedSizeProcs = 5093 [spawn_link(fun() -> 5094 size_loop(T, SizeLoopSize, 0, WhatToTest), 5095 P ! done 5096 end) 5097 || _ <- lists:seq(1, 6)], 5098 spawn_link(fun() -> 5099 add_loop(T, ItemsToAdd), 5100 P ! done_add 5101 end), 5102 [receive 5103 done -> ok; 5104 done_add -> ok 5105 end 5106 || _ <- [ok|SpawnedSizeProcs]], 5107 case WhatToTest =:= size of 5108 true -> 5109 ItemsToAdd = ets:info(T, size); 5110 _ -> 5111 ok 5112 end, 5113 erts_debug:set_internal_state(available_internal_state, IntStatePrevOn), 5114 ok. 5115 5116test_table_size_concurrency(Config) when is_list(Config) -> 5117 case erlang:system_info(schedulers) of 5118 1 -> {skip,"Only valid on smp > 1 systems"}; 5119 _ -> 5120 BaseOptions = [public, {write_concurrency, true}], 5121 test_table_counter_concurrency(size, [set | BaseOptions]), 5122 test_table_counter_concurrency(size, [ordered_set | BaseOptions]) 5123 end. 5124 5125test_table_memory_concurrency(Config) when is_list(Config) -> 5126 case erlang:system_info(schedulers) of 5127 1 -> {skip,"Only valid on smp > 1 systems"}; 5128 _ -> 5129 BaseOptions = [public, {write_concurrency, true}], 5130 test_table_counter_concurrency(memory, [set | BaseOptions]), 5131 test_table_counter_concurrency(memory, [ordered_set | BaseOptions]) 5132 end. 5133 5134%% Tests that calling the ets:delete operation on a table T with 5135%% decentralized counters works while ets:info(T, size) operations are 5136%% active 5137test_delete_table_while_size_snapshot(Config) when is_list(Config) -> 5138 %% Run test case in a slave node as other test suites in stdlib 5139 %% depend on that pids are ordered in creation order which is no 5140 %% longer the case when many processes have been started before 5141 Node = start_slave(), 5142 [ok = rpc:call(Node, 5143 ?MODULE, 5144 test_delete_table_while_size_snapshot_helper, 5145 [TableType]) 5146 || TableType <- [set, ordered_set]], 5147 test_server:stop_node(Node), 5148 ok. 5149 5150test_delete_table_while_size_snapshot_helper(TableType) -> 5151 TopParent = self(), 5152 repeat_par( 5153 fun() -> 5154 Table = ets:new(t, [public, TableType, 5155 {decentralized_counters, true}, 5156 {write_concurrency, true}]), 5157 Parent = self(), 5158 NrOfSizeProcs = 100, 5159 Pids = [ spawn(fun()-> size_process(Table, Parent) end) 5160 || _ <- lists:seq(1, NrOfSizeProcs)], 5161 timer:sleep(1), 5162 ets:delete(Table), 5163 [receive 5164 table_gone -> ok; 5165 Problem -> TopParent ! Problem 5166 end || _ <- Pids] 5167 end, 5168 100*erlang:system_info(schedulers_online)), 5169 receive 5170 Problem -> throw(Problem) 5171 after 0 -> ok 5172 end. 5173 5174size_process(Table, Parent) -> 5175 try ets:info(Table, size) of 5176 N when is_integer(N) -> 5177 size_process(Table, Parent); 5178 undefined -> Parent ! table_gone; 5179 E -> Parent ! {got_unexpected, E} 5180 catch 5181 E -> Parent ! {got_unexpected_exception, E} 5182 end. 5183 5184start_slave() -> 5185 MicroSecs = erlang:monotonic_time(), 5186 Name = "ets_" ++ integer_to_list(MicroSecs), 5187 Pa = filename:dirname(code:which(?MODULE)), 5188 {ok, Node} = test_server:start_node(list_to_atom(Name), slave, [{args, "-pa " ++ Pa}]), 5189 Node. 5190 5191repeat_par(FunToRepeat, NrOfTimes) -> 5192 repeat_par_help(FunToRepeat, NrOfTimes, NrOfTimes). 5193 5194repeat_par_help(_FunToRepeat, 0, OrgNrOfTimes) -> 5195 repeat(fun()-> receive done -> ok end end, OrgNrOfTimes); 5196repeat_par_help(FunToRepeat, NrOfTimes, OrgNrOfTimes) -> 5197 Parent = self(), 5198 case NrOfTimes rem 5 of 5199 0 -> timer:sleep(1); 5200 _ -> ok 5201 end, 5202 spawn(fun()-> 5203 FunToRepeat(), 5204 Parent ! done 5205 end), 5206 repeat_par_help(FunToRepeat, NrOfTimes-1, OrgNrOfTimes). 5207 5208test_decentralized_counters_setting(Config) when is_list(Config) -> 5209 case erlang:system_info(schedulers) of 5210 1 -> {skip,"Only relevant when the number of shedulers > 1"}; 5211 _ -> EtsMem = etsmem(), 5212 do_test_decentralized_counters_setting(set), 5213 do_test_decentralized_counters_setting(ordered_set), 5214 do_test_decentralized_counters_default_setting(), 5215 verify_etsmem(EtsMem) 5216 end. 5217 5218do_test_decentralized_counters_setting(TableType) -> 5219 wait_for_memory_deallocations(), 5220 FlxCtrMemUsage = erts_debug:get_internal_state(flxctr_memory_usage), 5221 lists:foreach( 5222 fun(OptList) -> 5223 T1 = ets:new(t1, [public, TableType] ++ OptList ++ [TableType]), 5224 check_decentralized_counters(T1, false, FlxCtrMemUsage), 5225 ets:delete(T1) 5226 end, 5227 [[{write_concurrency, false}], 5228 [{write_concurrency, true}, {decentralized_counters, false}]]), 5229 lists:foreach( 5230 fun(OptList) -> 5231 T1 = ets:new(t1, [public, 5232 TableType, 5233 {write_concurrency, true}] ++ OptList ++ [TableType]), 5234 check_decentralized_counters(T1, true, FlxCtrMemUsage), 5235 ets:delete(T1), 5236 wait_for_memory_deallocations(), 5237 FlxCtrMemUsage = erts_debug:get_internal_state(flxctr_memory_usage) 5238 end, 5239 [[{decentralized_counters, true}]]), 5240 ok. 5241 5242do_test_decentralized_counters_default_setting() -> 5243 wait_for_memory_deallocations(), 5244 FlxCtrMemUsage = erts_debug:get_internal_state(flxctr_memory_usage), 5245 Set = ets:new(t1, [public, {write_concurrency, true}]), 5246 check_decentralized_counters(Set, false, FlxCtrMemUsage), 5247 ets:delete(Set), 5248 Set2 = ets:new(t1, [public, set, {write_concurrency, true}]), 5249 check_decentralized_counters(Set2, false, FlxCtrMemUsage), 5250 ets:delete(Set2), 5251 OrdSet = ets:new(t1, [public, ordered_set, {write_concurrency, true}]), 5252 check_decentralized_counters(OrdSet, true, FlxCtrMemUsage), 5253 ets:delete(OrdSet), 5254 ok. 5255 5256check_decentralized_counters(T, ExpectedState, InitMemUsage) -> 5257 case {ExpectedState, erts_debug:get_internal_state(flxctr_memory_usage)} of 5258 {false, notsup} -> ok; 5259 {false, X} -> InitMemUsage = X; 5260 {true, notsup} -> ok; 5261 {true, X} when X > InitMemUsage -> ok; 5262 {true, _} -> ct:fail("Decentralized counter not used.") 5263 end, 5264 ExpectedState = ets:info(T, decentralized_counters). 5265 5266%% Test various duplicate_bags stuff. 5267dups(Config) when is_list(Config) -> 5268 repeat_for_opts(fun dups_do/1). 5269 5270dups_do(Opts) -> 5271 EtsMem = etsmem(), 5272 T = make_table(funky, 5273 [duplicate_bag | Opts], 5274 [{1, 2}, {1, 2}]), 5275 2 = length(ets:tab2list(T)), 5276 ets:delete(T, 1), 5277 [] = ets:lookup(T, 1), 5278 5279 ets:insert(T, {1, 2, 2}), 5280 ets:insert(T, {1, 2, 4}), 5281 ets:insert(T, {1, 2, 2}), 5282 ets:insert(T, {1, 2, 2}), 5283 ets:insert(T, {1, 2, 4}), 5284 5285 5 = length(ets:tab2list(T)), 5286 5287 5 = length(ets:match(T, {'$1', 2, '$2'})), 5288 3 = length(ets:match(T, {'_', '$1', '$1'})), 5289 ets:match_delete(T, {'_', '$1', '$1'}), 5290 0 = length(ets:match(T, {'_', '$1', '$1'})), 5291 ets:delete(T), 5292 verify_etsmem(EtsMem). 5293 5294%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 5295 5296%% Test the ets:tab2file function on an empty ets table. 5297tab2file(Config) when is_list(Config) -> 5298 FName = filename:join([proplists:get_value(priv_dir, Config),"tab2file_case"]), 5299 tab2file_do(FName, [], set), 5300 tab2file_do(FName, [], ordered_set), 5301 tab2file_do(FName, [], cat_ord_set), 5302 tab2file_do(FName, [], stim_cat_ord_set), 5303 tab2file_do(FName, [{sync,true}], set), 5304 tab2file_do(FName, [{sync,false}], set), 5305 {'EXIT',{{badmatch,{error,_}},_}} = (catch tab2file_do(FName, [{sync,yes}], set)), 5306 {'EXIT',{{badmatch,{error,_}},_}} = (catch tab2file_do(FName, [sync], set)), 5307 ok. 5308 5309tab2file_do(FName, Opts, TableType) -> 5310 %% Write an empty ets table to a file, read back and check properties. 5311 Tab = ets_new(ets_SUITE_foo_tab, [named_table, TableType, public, 5312 {keypos, 2}, 5313 compressed, 5314 {write_concurrency,true}, 5315 {read_concurrency,true}]), 5316 ActualTableType = 5317 case TableType of 5318 cat_ord_set -> ordered_set; 5319 stim_cat_ord_set -> ordered_set; 5320 _ -> TableType 5321 end, 5322 catch file:delete(FName), 5323 Res = ets:tab2file(Tab, FName, Opts), 5324 true = ets:delete(Tab), 5325 ok = Res, 5326 %% 5327 EtsMem = etsmem(), 5328 {ok, Tab2} = ets:file2tab(FName), 5329 public = ets:info(Tab2, protection), 5330 true = ets:info(Tab2, named_table), 5331 2 = ets:info(Tab2, keypos), 5332 ActualTableType = ets:info(Tab2, type), 5333 true = ets:info(Tab2, compressed), 5334 Smp = erlang:system_info(smp_support), 5335 Smp = ets:info(Tab2, read_concurrency), 5336 Smp = ets:info(Tab2, write_concurrency) orelse erlang:system_info(schedulers) == 1, 5337 true = ets:delete(Tab2), 5338 verify_etsmem(EtsMem). 5339 5340 5341%% Check the ets:tab2file function on a filled set/bag type ets table. 5342tab2file2(Config) when is_list(Config) -> 5343 repeat_for_opts(fun(Opts) -> 5344 tab2file2_do(Opts, Config) 5345 end, [[stim_cat_ord_set,cat_ord_set,set,bag],compressed]). 5346 5347tab2file2_do(Opts, Config) -> 5348 EtsMem = etsmem(), 5349 KeyRange = 10000, 5350 Tab = ets_new(ets_SUITE_foo_tab, [named_table, private, {keypos, 2} | Opts], 5351 KeyRange), 5352 FName = filename:join([proplists:get_value(priv_dir, Config),"tab2file2_case"]), 5353 ok = fill_tab2(Tab, 0, KeyRange), % Fill up the table (grucho mucho!) 5354 Len = length(ets:tab2list(Tab)), 5355 Mem = ets:info(Tab, memory), 5356 Type = ets:info(Tab, type), 5357 %%io:format("org tab: ~p\n",[ets:info(Tab)]), 5358 ok = ets:tab2file(Tab, FName), 5359 true = ets:delete(Tab), 5360 5361 EtsMem4 = etsmem(), 5362 5363 {ok, Tab2} = ets:file2tab(FName), 5364 %%io:format("loaded tab: ~p\n",[ets:info(Tab2)]), 5365 private = ets:info(Tab2, protection), 5366 true = ets:info(Tab2, named_table), 5367 2 = ets:info(Tab2, keypos), 5368 Type = ets:info(Tab2, type), 5369 Len = length(ets:tab2list(Tab2)), 5370 Mem = ets:info(Tab2, memory), 5371 true = ets:delete(Tab2), 5372 io:format("Between = ~p\n", [EtsMem4]), 5373 verify_etsmem(EtsMem). 5374 5375-define(test_list, [8,5,4,1,58,125,255, 250, 245, 240, 235, 5376 230, Num rem 255, 255, 125, 130, 135, 140, 145, 5377 150, 134, 12, 54, Val rem 255, 12, 3, 6, 9, 126]). 5378-define(big_test_list, [Num rem 256|lists:seq(1, 66)]). 5379-define(test_integer, 2846287468+Num). 5380-define(test_float, 187263.18236-Val). 5381-define(test_atom, some_crazy_atom). 5382-define(test_tuple, {just, 'Some', 'Tuple', 1, [list, item], Val+Num}). 5383 5384%% Insert different datatypes into a ets table. 5385fill_tab2(_Tab, _Val, 0) -> 5386 ok; 5387fill_tab2(Tab, Val, Num) -> 5388 Item = 5389 case Num rem 10 of 5390 0 -> "String"; 5391 1 -> ?test_atom; 5392 2 -> ?test_tuple; 5393 3 -> ?test_integer; 5394 4 -> ?test_float; 5395 5 -> list_to_binary(?test_list); %Heap binary 5396 6 -> list_to_binary(?big_test_list); %Refc binary 5397 7 -> make_sub_binary(?test_list, Num); %Sub binary 5398 8 -> ?test_list; 5399 9 -> fun(X) -> {Tab,Val,X*Num} end 5400 end, 5401 true=ets:insert(Tab, {Item, Val}), 5402 fill_tab2(Tab, Val+1, Num-1), 5403 ok. 5404 5405%% Test verification of tables with object count extended_info. 5406tabfile_ext1(Config) when is_list(Config) -> 5407 repeat_for_opts_all_set_table_types(fun(Opts) -> tabfile_ext1_do(Opts, Config) end). 5408 5409tabfile_ext1_do(Opts,Config) -> 5410 FName = filename:join([proplists:get_value(priv_dir, Config),"nisse.dat"]), 5411 FName2 = filename:join([proplists:get_value(priv_dir, Config),"countflip.dat"]), 5412 KeyRange = 10, 5413 L = lists:seq(1,KeyRange), 5414 T = ets_new(x,Opts,KeyRange), 5415 Name = make_ref(), 5416 [ets:insert(T,{X,integer_to_list(X)}) || X <- L], 5417 ok = ets:tab2file(T,FName,[{extended_info,[object_count]}]), 5418 true = lists:sort(ets:tab2list(T)) =:= 5419 lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))), 5420 true = lists:sort(ets:tab2list(T)) =:= 5421 lists:sort(ets:tab2list( 5422 element(2,ets:file2tab(FName,[{verify,true}])))), 5423 {ok,Name} = disk_log:open([{name,Name},{file,FName}]), 5424 {_,[H0|T0]} = disk_log:chunk(Name,start), 5425 disk_log:close(Name), 5426 LH0=tuple_to_list(H0), 5427 {value,{size,N}}=lists:keysearch(size,1,LH0), 5428 NewLH0 = lists:keyreplace(size,1,LH0,{size,N-1}), 5429 NewH0 = list_to_tuple(NewLH0), 5430 NewT0=lists:keydelete(8,1,T0), 5431 file:delete(FName2), 5432 disk_log:open([{name,Name},{file,FName2},{mode,read_write}]), 5433 disk_log:log_terms(Name,[NewH0|NewT0]), 5434 disk_log:close(Name), 5435 9 = length(ets:tab2list(element(2,ets:file2tab(FName2)))), 5436 {error,invalid_object_count} = ets:file2tab(FName2,[{verify,true}]), 5437 {ok, _} = ets:tabfile_info(FName2), 5438 {ok, _} = ets:tabfile_info(FName), 5439 file:delete(FName), 5440 file:delete(FName2), 5441 ok. 5442 5443 5444%% Test verification of tables with md5sum extended_info. 5445tabfile_ext2(Config) when is_list(Config) -> 5446 repeat_for_opts_all_set_table_types(fun(Opts) -> tabfile_ext2_do(Opts,Config) end). 5447 5448tabfile_ext2_do(Opts,Config) -> 5449 FName = filename:join([proplists:get_value(priv_dir, Config),"olle.dat"]), 5450 FName2 = filename:join([proplists:get_value(priv_dir, Config),"bitflip.dat"]), 5451 KeyRange = 10, 5452 L = lists:seq(1, KeyRange), 5453 T = ets_new(x, Opts, KeyRange), 5454 Name = make_ref(), 5455 [ets:insert(T,{X,integer_to_list(X)}) || X <- L], 5456 ok = ets:tab2file(T,FName,[{extended_info,[md5sum]}]), 5457 true = lists:sort(ets:tab2list(T)) =:= 5458 lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))), 5459 true = lists:sort(ets:tab2list(T)) =:= 5460 lists:sort(ets:tab2list( 5461 element(2,ets:file2tab(FName,[{verify,true}])))), 5462 {ok, Name} = disk_log:open([{name,Name},{file,FName}]), 5463 {_,[H1|T1]} = disk_log:chunk(Name,start), 5464 disk_log:close(Name), 5465 NewT1=lists:keyreplace(8,1,T1,{8,"9"}), 5466 file:delete(FName2), 5467 disk_log:open([{name,Name},{file,FName2},{mode,read_write}]), 5468 disk_log:log_terms(Name,[H1|NewT1]), 5469 disk_log:close(Name), 5470 {value,{8,"9"}} = lists:keysearch(8,1, 5471 ets:tab2list( 5472 element(2,ets:file2tab(FName2)))), 5473 {error,checksum_error} = ets:file2tab(FName2,[{verify,true}]), 5474 {value,{extended_info,[md5sum]}} = 5475 lists:keysearch(extended_info,1,element(2,ets:tabfile_info(FName2))), 5476 {value,{extended_info,[md5sum]}} = 5477 lists:keysearch(extended_info,1,element(2,ets:tabfile_info(FName))), 5478 file:delete(FName), 5479 file:delete(FName2), 5480 ok. 5481 5482%% Test verification of (named) tables without extended info. 5483tabfile_ext3(Config) when is_list(Config) -> 5484 repeat_for_all_set_table_types( 5485 fun(Opts) -> 5486 FName = filename:join([proplists:get_value(priv_dir, Config),"namn.dat"]), 5487 FName2 = filename:join([proplists:get_value(priv_dir, Config),"ncountflip.dat"]), 5488 L = lists:seq(1,10), 5489 Name = make_ref(), 5490 ?MODULE = ets_new(?MODULE,[named_table|Opts]), 5491 [ets:insert(?MODULE,{X,integer_to_list(X)}) || X <- L], 5492 ets:tab2file(?MODULE,FName), 5493 {error,cannot_create_table} = ets:file2tab(FName), 5494 true = ets:delete(?MODULE), 5495 {ok,?MODULE} = ets:file2tab(FName), 5496 true = ets:delete(?MODULE), 5497 disk_log:open([{name,Name},{file,FName}]), 5498 {_,[H2|T2]} = disk_log:chunk(Name,start), 5499 disk_log:close(Name), 5500 NewT2=lists:keydelete(8,1,T2), 5501 file:delete(FName2), 5502 disk_log:open([{name,Name},{file,FName2},{mode,read_write}]), 5503 disk_log:log_terms(Name,[H2|NewT2]), 5504 disk_log:close(Name), 5505 9 = length(ets:tab2list(element(2,ets:file2tab(FName2)))), 5506 true = ets:delete(?MODULE), 5507 {error,invalid_object_count} = ets:file2tab(FName2,[{verify,true}]), 5508 {'EXIT',_} = (catch ets:delete(?MODULE)), 5509 {ok,_} = ets:tabfile_info(FName2), 5510 {ok,_} = ets:tabfile_info(FName), 5511 file:delete(FName), 5512 file:delete(FName2) 5513 end), 5514 ok. 5515 5516%% Tests verification of large table with md5 sum. 5517tabfile_ext4(Config) when is_list(Config) -> 5518 repeat_for_all_set_table_types( 5519 fun(Opts) -> 5520 FName = filename:join([proplists:get_value(priv_dir, Config),"bauta.dat"]), 5521 LL = lists:seq(1,10000), 5522 TL = ets_new(x,Opts), 5523 Name2 = make_ref(), 5524 [ets:insert(TL,{X,integer_to_list(X)}) || X <- LL], 5525 ok = ets:tab2file(TL,FName,[{extended_info,[md5sum]}]), 5526 {ok, Name2} = disk_log:open([{name, Name2}, {file, FName}, 5527 {mode, read_only}]), 5528 {C,[_|_]} = disk_log:chunk(Name2,start), 5529 {_,[_|_]} = disk_log:chunk(Name2,C), 5530 disk_log:close(Name2), 5531 true = lists:sort(ets:tab2list(TL)) =:= 5532 lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))), 5533 Res = [begin 5534 {ok,FD} = file:open(FName,[binary,read,write]), 5535 {ok, Bin} = file:pread(FD,0,1000), 5536 <<B1:N/binary,Ch:8,B2/binary>> = Bin, 5537 Ch2 = (Ch + 1) rem 255, 5538 Bin2 = <<B1/binary,Ch2:8,B2/binary>>, 5539 ok = file:pwrite(FD,0,Bin2), 5540 ok = file:close(FD), 5541 X = case ets:file2tab(FName) of 5542 {ok,TL2} -> 5543 true = lists:sort(ets:tab2list(TL)) =/= 5544 lists:sort(ets:tab2list(TL2)); 5545 _ -> 5546 totally_broken 5547 end, 5548 {error,Y} = ets:file2tab(FName,[{verify,true}]), 5549 ets:tab2file(TL,FName,[{extended_info,[md5sum]}]), 5550 {X,Y} 5551 end || N <- lists:seq(700,800)], 5552 io:format("~p~n",[Res]), 5553 file:delete(FName) 5554 end), 5555 ok. 5556 5557%% Test that no disk_log is left open when file has been corrupted. 5558badfile(Config) when is_list(Config) -> 5559 PrivDir = proplists:get_value(priv_dir,Config), 5560 File = filename:join(PrivDir, "badfile"), 5561 _ = file:delete(File), 5562 T = ets:new(table, []), 5563 true = ets:insert(T, [{a,1},{b,2}]), 5564 ok = ets:tab2file(T, File, []), 5565 true = ets:delete(T), 5566 [H0 | Ts ] = get_all_terms(l, File), 5567 H1 = tuple_to_list(H0), 5568 H2 = [{K,V} || {K,V} <- H1, K =/= protection], 5569 H = list_to_tuple(H2), 5570 ok = file:delete(File), 5571 write_terms(l, File, [H | Ts]), 5572 %% All mandatory keys are no longer members of the header 5573 {error, badfile} = ets:file2tab(File), 5574 {error, badfile} = ets:tabfile_info(File), 5575 file:delete(File), 5576 [] = disk_log:all(), 5577 ok. 5578 5579get_all_terms(Log, File) -> 5580 {ok, Log} = disk_log:open([{name,Log}, 5581 {file, File}, 5582 {mode, read_only}]), 5583 Ts = get_all_terms(Log), 5584 ok = disk_log:close(Log), 5585 Ts. 5586 5587get_all_terms(Log) -> 5588 get_all_terms1(Log, start, []). 5589 5590get_all_terms1(Log, Cont, Res) -> 5591 case disk_log:chunk(Log, Cont) of 5592 {error, _R} -> 5593 throw(fel); 5594 {Cont2, Terms} -> 5595 get_all_terms1(Log, Cont2, Res ++ Terms); 5596 eof -> 5597 Res 5598 end. 5599 5600write_terms(Log, File, Terms) -> 5601 {ok, Log} = disk_log:open([{name,Log},{file, File},{mode,read_write}]), 5602 ok = disk_log:log(Log, Terms), 5603 ok = disk_log:close(Log). 5604 5605%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 5606 5607make_sub_binary(List, Num) when is_list(List) -> 5608 N = Num rem 23, 5609 Bin = list_to_binary([lists:seq(0, N)|List]), 5610 {_,B} = split_binary(Bin, N+1), 5611 B. 5612 5613 5614%% Lookup stuff like crazy... 5615 5616%% Perform multiple lookups for every key in a large table. 5617heavy_lookup(Config) when is_list(Config) -> 5618 repeat_for_opts_all_set_table_types(fun heavy_lookup_do/1). 5619 5620heavy_lookup_do(Opts) -> 5621 EtsMem = etsmem(), 5622 KeyRange = 7000, 5623 Tab = ets_new(foobar_table, [{keypos, 2} | Opts], KeyRange), 5624 ok = fill_tab2(Tab, 0, KeyRange), 5625 _ = [do_lookup(Tab, KeyRange-1) || _ <- lists:seq(1, 50)], 5626 true = ets:delete(Tab), 5627 verify_etsmem(EtsMem). 5628 5629do_lookup(_Tab, 0) -> ok; 5630do_lookup(Tab, N) -> 5631 case ets:lookup(Tab, N) of 5632 [] -> 5633 io:format("Set #~p was reported as empty. Not valid.", 5634 [N]), 5635 exit('Invalid lookup'); 5636 _ -> 5637 do_lookup(Tab, N-1) 5638 end. 5639 5640%% Perform multiple lookups for every element in a large table. 5641heavy_lookup_element(Config) when is_list(Config) -> 5642 repeat_for_opts_all_set_table_types(fun heavy_lookup_element_do/1). 5643 5644heavy_lookup_element_do(Opts) -> 5645 EtsMem = etsmem(), 5646 KeyRange = 7000, 5647 Tab = ets_new(foobar_table, [{keypos, 2} | Opts], KeyRange), 5648 ok = fill_tab2(Tab, 0, KeyRange), 5649 %% lookup ALL elements 50 times 5650 Laps = 50 div syrup_factor(), 5651 _ = [do_lookup_element(Tab, KeyRange-1, 1) || _ <- lists:seq(1, Laps)], 5652 true = ets:delete(Tab), 5653 verify_etsmem(EtsMem). 5654 5655do_lookup_element(_Tab, 0, _) -> ok; 5656do_lookup_element(Tab, N, M) -> 5657 case catch ets:lookup_element(Tab, N, M) of 5658 {'EXIT', {badarg, _}} -> 5659 case M of 5660 1 -> ct:fail("Set #~p reported as empty. Not valid.", 5661 [N]), 5662 exit('Invalid lookup_element'); 5663 _ -> do_lookup_element(Tab, N-1, 1) 5664 end; 5665 _ -> do_lookup_element(Tab, N, M+1) 5666 end. 5667 5668 5669heavy_concurrent(Config) when is_list(Config) -> 5670 ct:timetrap({minutes,120}), %% valgrind needs a lot of time 5671 repeat_for_opts_all_set_table_types(fun do_heavy_concurrent/1). 5672 5673do_heavy_concurrent(Opts) -> 5674 KeyRange = 10000, 5675 Laps = 10000 div syrup_factor(), 5676 EtsMem = etsmem(), 5677 Tab = ets_new(blupp, [public, {keypos, 2} | Opts], KeyRange), 5678 ok = fill_tab2(Tab, 0, KeyRange), 5679 Procs = lists:map( 5680 fun (N) -> 5681 my_spawn_link( 5682 fun () -> 5683 do_heavy_concurrent_proc(Tab, Laps, N) 5684 end) 5685 end, 5686 lists:seq(1, 500)), 5687 lists:foreach(fun (P) -> 5688 M = erlang:monitor(process, P), 5689 receive 5690 {'DOWN', M, process, P, _} -> 5691 ok 5692 end 5693 end, 5694 Procs), 5695 true = ets:delete(Tab), 5696 verify_etsmem(EtsMem). 5697 5698do_heavy_concurrent_proc(_Tab, 0, _Offs) -> 5699 done; 5700do_heavy_concurrent_proc(Tab, N, Offs) when (N+Offs) rem 100 == 0 -> 5701 Data = {"here", are, "S O M E ", data, "toooooooooooooooooo", insert, 5702 make_ref(), make_ref(), make_ref()}, 5703 true=ets:insert(Tab, {{self(),Data}, N}), 5704 do_heavy_concurrent_proc(Tab, N-1, Offs); 5705do_heavy_concurrent_proc(Tab, N, Offs) -> 5706 _ = ets:lookup(Tab, N), 5707 do_heavy_concurrent_proc(Tab, N-1, Offs). 5708 5709 5710fold_empty(Config) when is_list(Config) -> 5711 repeat_for_opts_all_set_table_types( 5712 fun(Opts) -> 5713 EtsMem = etsmem(), 5714 Tab = make_table(a, Opts, []), 5715 [] = ets:foldl(fun(_X) -> exit(hej) end, [], Tab), 5716 [] = ets:foldr(fun(_X) -> exit(hej) end, [], Tab), 5717 true = ets:delete(Tab), 5718 verify_etsmem(EtsMem) 5719 end), 5720 ok. 5721 5722foldl(Config) when is_list(Config) -> 5723 repeat_for_opts_all_table_types( 5724 fun(Opts) -> 5725 EtsMem = etsmem(), 5726 L = [{a,1}, {c,3}, {b,2}], 5727 LS = lists:sort(L), 5728 Tab = make_table(a, Opts, L), 5729 LS = lists:sort(ets:foldl(fun(E,A) -> [E|A] end, [], Tab)), 5730 true = ets:delete(Tab), 5731 verify_etsmem(EtsMem) 5732 end), 5733 ok. 5734 5735foldr(Config) when is_list(Config) -> 5736 repeat_for_opts_all_table_types( 5737 fun(Opts) -> 5738 EtsMem = etsmem(), 5739 L = [{a,1}, {c,3}, {b,2}], 5740 LS = lists:sort(L), 5741 Tab = make_table(a, Opts, L), 5742 LS = lists:sort(ets:foldr(fun(E,A) -> [E|A] end, [], Tab)), 5743 true = ets:delete(Tab), 5744 verify_etsmem(EtsMem) 5745 end), 5746 ok. 5747 5748foldl_ordered(Config) when is_list(Config) -> 5749 repeat_for_opts_all_ord_set_table_types( 5750 fun(Opts) -> 5751 EtsMem = etsmem(), 5752 L = [{a,1}, {c,3}, {b,2}], 5753 LS = lists:sort(L), 5754 Tab = make_table(a, Opts, L), 5755 LS = lists:reverse(ets:foldl(fun(E,A) -> [E|A] end, [], Tab)), 5756 true = ets:delete(Tab), 5757 verify_etsmem(EtsMem) 5758 end), 5759 ok. 5760 5761foldr_ordered(Config) when is_list(Config) -> 5762 repeat_for_opts_all_ord_set_table_types( 5763 fun(Opts) -> 5764 EtsMem = etsmem(), 5765 L = [{a,1}, {c,3}, {b,2}], 5766 LS = lists:sort(L), 5767 Tab = make_table(a, Opts, L), 5768 LS = ets:foldr(fun(E,A) -> [E|A] end, [], Tab), 5769 true = ets:delete(Tab), 5770 verify_etsmem(EtsMem) 5771 end), 5772 ok. 5773 5774%% Test ets:member BIF. 5775member(Config) when is_list(Config) -> 5776 repeat_for_opts(fun member_do/1, [write_concurrency, all_types]). 5777 5778member_do(Opts) -> 5779 EtsMem = etsmem(), 5780 T = ets_new(xxx, Opts), 5781 false = ets:member(T,hej), 5782 E = fun(0,_F)->ok; 5783 (N,F) -> 5784 ets:insert(T,{N,N rem 10}), 5785 F(N-1,F) 5786 end, 5787 E(10000,E), 5788 false = ets:member(T,hej), 5789 true = ets:member(T,1), 5790 false = ets:member(T,20000), 5791 ets:delete(T,5), 5792 false = ets:member(T,5), 5793 ets:safe_fixtable(T,true), 5794 ets:delete(T,6), 5795 false = ets:member(T,6), 5796 ets:safe_fixtable(T,false), 5797 false = ets:member(T,6), 5798 ets:delete(T), 5799 {'EXIT',{badarg,_}} = (catch ets:member(finnsinte, 23)), 5800 {'EXIT',{badarg,_}} = (catch ets:member(T, 23)), 5801 verify_etsmem(EtsMem). 5802 5803 5804build_table(L1,L2,Num) -> 5805 T = ets_new(xxx, [ordered_set]), 5806 lists:foreach( 5807 fun(X1) -> 5808 lists:foreach( 5809 fun(X2) -> 5810 F = fun(FF,N) -> 5811 ets:insert(T,{{X1,X2,N}, X1, X2, N}), 5812 case N of 5813 0 -> 5814 ok; 5815 _ -> 5816 FF(FF,N-1) 5817 end 5818 end, 5819 F(F,Num) 5820 end, L2) 5821 end, L1), 5822 T. 5823 5824build_table2(L1,L2,Num) -> 5825 T = ets_new(xxx, [ordered_set]), 5826 lists:foreach( 5827 fun(X1) -> 5828 lists:foreach( 5829 fun(X2) -> 5830 F = fun(FF,N) -> 5831 ets:insert(T,{{N,X1,X2}, N, X1, X2}), 5832 case N of 5833 0 -> 5834 ok; 5835 _ -> 5836 FF(FF,N-1) 5837 end 5838 end, 5839 F(F,Num) 5840 end, L2) 5841 end, L1), 5842 T. 5843 5844time_match_object(Tab,Match, Res) -> 5845 T1 = erlang:monotonic_time(microsecond), 5846 Res = ets:match_object(Tab,Match), 5847 T2 = erlang:monotonic_time(microsecond), 5848 T2 - T1. 5849 5850time_match(Tab,Match) -> 5851 T1 = erlang:monotonic_time(microsecond), 5852 ets:match(Tab,Match), 5853 T2 = erlang:monotonic_time(microsecond), 5854 T2 - T1. 5855 5856seventyfive_percent_success(_,S,Fa,0) -> 5857 true = (S > ((S + Fa) * 0.75)); 5858 5859seventyfive_percent_success(F, S, Fa, N) when is_function(F, 0) -> 5860 try F() of 5861 _ -> 5862 seventyfive_percent_success(F, S+1, Fa, N-1) 5863 catch error:_ -> 5864 seventyfive_percent_success(F, S, Fa+1, N-1) 5865 end. 5866 5867fifty_percent_success(_,S,Fa,0) -> 5868 true = (S > ((S + Fa) * 0.5)); 5869 5870fifty_percent_success(F, S, Fa, N) when is_function(F, 0) -> 5871 try F() of 5872 _ -> 5873 fifty_percent_success(F, S+1, Fa, N-1) 5874 catch 5875 error:_ -> 5876 fifty_percent_success(F, S, Fa+1, N-1) 5877 end. 5878 5879create_random_string(0) -> 5880 []; 5881 5882create_random_string(OfLength) -> 5883 C = case rand:uniform(2) of 5884 1 -> 5885 (rand:uniform($Z - $A + 1) - 1) + $A; 5886 _ -> 5887 (rand:uniform($z - $a + 1) - 1) + $a 5888 end, 5889 [C | create_random_string(OfLength - 1)]. 5890 5891 5892create_random_tuple(OfLength) -> 5893 list_to_tuple(lists:map(fun(X) -> 5894 list_to_atom([X]) 5895 end,create_random_string(OfLength))). 5896 5897create_partly_bound_tuple(OfLength) -> 5898 case rand:uniform(2) of 5899 1 -> 5900 create_partly_bound_tuple1(OfLength); 5901 _ -> 5902 create_partly_bound_tuple3(OfLength) 5903 end. 5904 5905create_partly_bound_tuple1(OfLength) -> 5906 T0 = create_random_tuple(OfLength), 5907 I = rand:uniform(OfLength), 5908 setelement(I,T0,'$1'). 5909 5910 5911set_n_random_elements(T0,0,_,_) -> 5912 T0; 5913set_n_random_elements(T0,N,OfLength,GenFun) -> 5914 I = rand:uniform(OfLength), 5915 What = GenFun(I), 5916 case element(I,T0) of 5917 What -> 5918 set_n_random_elements(T0,N,OfLength,GenFun); 5919 _Else -> 5920 set_n_random_elements(setelement(I,T0,What), 5921 N-1,OfLength,GenFun) 5922 end. 5923 5924make_dollar_atom(I) -> 5925 list_to_atom([$$] ++ integer_to_list(I)). 5926create_partly_bound_tuple2(OfLength) -> 5927 T0 = create_random_tuple(OfLength), 5928 I = rand:uniform(OfLength - 1), 5929 set_n_random_elements(T0,I,OfLength,fun make_dollar_atom/1). 5930 5931create_partly_bound_tuple3(OfLength) -> 5932 T0 = create_random_tuple(OfLength), 5933 I = rand:uniform(OfLength - 1), 5934 set_n_random_elements(T0,I,OfLength,fun(_) -> '_' end). 5935 5936do_n_times(_,0) -> 5937 ok; 5938do_n_times(Fun,N) -> 5939 Fun(), 5940 case N rem 1000 of 5941 0 -> 5942 io:format("."); 5943 _ -> 5944 ok 5945 end, 5946 do_n_times(Fun,N-1). 5947 5948make_table(Name, Options, Elements) -> 5949 T = ets_new(Name, Options), 5950 lists:foreach(fun(E) -> ets:insert(T, E) end, Elements), 5951 T. 5952 5953filltabint(Tab,0) -> 5954 Tab; 5955filltabint(Tab,N) -> 5956 ets:insert(Tab,{N,integer_to_list(N)}), 5957 filltabint(Tab,N-1). 5958 5959filltabint2(Tab,0) -> 5960 Tab; 5961filltabint2(Tab,N) -> 5962 ets:insert(Tab,{N + N rem 2,integer_to_list(N)}), 5963 filltabint2(Tab,N-1). 5964filltabint3(Tab,0) -> 5965 Tab; 5966filltabint3(Tab,N) -> 5967 ets:insert(Tab,{N + N rem 2,integer_to_list(N + N rem 2)}), 5968 filltabint3(Tab,N-1). 5969xfilltabint(Tab,N) -> 5970 case ets:info(Tab,type) of 5971 bag -> 5972 filltabint2(Tab,N); 5973 duplicate_bag -> 5974 ets:select_delete(Tab,[{'_',[],[true]}]), 5975 filltabint3(Tab,N); 5976 _ -> 5977 filltabint(Tab,N) 5978 end. 5979 5980filltabintup(Tab,0) -> 5981 Tab; 5982filltabintup(Tab,N) -> 5983 ets:insert(Tab,{{N,integer_to_list(N)},integer_to_list(N)}), 5984 filltabintup(Tab,N-1). 5985 5986filltabintup2(Tab,0) -> 5987 Tab; 5988filltabintup2(Tab,N) -> 5989 ets:insert(Tab,{{N + N rem 2,integer_to_list(N)},integer_to_list(N)}), 5990 filltabintup2(Tab,N-1). 5991filltabintup3(Tab,0) -> 5992 Tab; 5993filltabintup3(Tab,N) -> 5994 ets:insert(Tab,{{N + N rem 2,integer_to_list(N + N rem 2)},integer_to_list(N + N rem 2)}), 5995 filltabintup3(Tab,N-1). 5996 5997filltabstr(Tab,N) -> 5998 filltabstr(Tab,0,N). 5999filltabstr(Tab,N,N) -> 6000 Tab; 6001filltabstr(Tab,Floor,N) when N > Floor -> 6002 ets:insert(Tab,{integer_to_list(N),N}), 6003 filltabstr(Tab,Floor,N-1). 6004 6005filltabstr2(Tab,0) -> 6006 Tab; 6007filltabstr2(Tab,N) -> 6008 ets:insert(Tab,{integer_to_list(N),N}), 6009 ets:insert(Tab,{integer_to_list(N),N+1}), 6010 filltabstr2(Tab,N-1). 6011filltabstr3(Tab,0) -> 6012 Tab; 6013filltabstr3(Tab,N) -> 6014 ets:insert(Tab,{integer_to_list(N),N}), 6015 ets:insert(Tab,{integer_to_list(N),N}), 6016 filltabstr3(Tab,N-1). 6017xfilltabstr(Tab,N) -> 6018 case ets:info(Tab,type) of 6019 bag -> 6020 filltabstr2(Tab,N); 6021 duplicate_bag -> 6022 ets:select_delete(Tab,[{'_',[],[true]}]), 6023 filltabstr3(Tab,N); 6024 _ -> 6025 filltabstr(Tab,N) 6026 end. 6027 6028fill_sets_int(N) -> 6029 fill_sets_int(N,[]). 6030fill_sets_int(N,Opts) -> 6031 Tab1 = ets_new(xxx, [ordered_set|Opts]), 6032 filltabint(Tab1,N), 6033 Tab2 = ets_new(xxx, [set|Opts]), 6034 filltabint(Tab2,N), 6035 Tab3 = ets_new(xxx, [bag|Opts]), 6036 filltabint2(Tab3,N), 6037 Tab4 = ets_new(xxx, [duplicate_bag|Opts]), 6038 filltabint3(Tab4,N), 6039 [Tab1,Tab2,Tab3,Tab4]. 6040 6041fill_sets_intup(N,Opts) -> 6042 Tab1 = ets_new(xxx, [ordered_set|Opts]), 6043 filltabintup(Tab1,N), 6044 Tab2 = ets_new(xxx, [set|Opts]), 6045 filltabintup(Tab2,N), 6046 Tab3 = ets_new(xxx, [bag|Opts]), 6047 filltabintup2(Tab3,N), 6048 Tab4 = ets_new(xxx, [duplicate_bag|Opts]), 6049 filltabintup3(Tab4,N), 6050 [Tab1,Tab2,Tab3,Tab4]. 6051 6052check_fun(_Tab,_Fun,'$end_of_table') -> 6053 ok; 6054check_fun(Tab,Fun,Item) -> 6055 lists:foreach(fun(Obj) -> 6056 true = Fun(Obj) 6057 end, 6058 ets:lookup(Tab,Item)), 6059 check_fun(Tab,Fun,ets:next(Tab,Item)). 6060 6061check(Tab,Fun,N) -> 6062 N = ets:info(Tab, size), 6063 check_fun(Tab,Fun,ets:first(Tab)). 6064 6065 6066 6067del_one_by_one_set(T,N,N) -> 6068 0 = ets:info(T,size), 6069 ok; 6070del_one_by_one_set(T,From,To) -> 6071 N = ets:info(T,size), 6072 ets:delete_object(T,{From, integer_to_list(From)}), 6073 N = (ets:info(T,size) + 1), 6074 Next = if 6075 From < To -> 6076 From + 1; 6077 true -> 6078 From - 1 6079 end, 6080 del_one_by_one_set(T,Next,To). 6081 6082del_one_by_one_bag(T,N,N) -> 6083 0 = ets:info(T,size), 6084 ok; 6085del_one_by_one_bag(T,From,To) -> 6086 N = ets:info(T,size), 6087 ets:delete_object(T,{From + From rem 2, integer_to_list(From)}), 6088 N = (ets:info(T,size) + 1), 6089 Next = if 6090 From < To -> 6091 From + 1; 6092 true -> 6093 From - 1 6094 end, 6095 del_one_by_one_bag(T,Next,To). 6096 6097 6098del_one_by_one_dbag_1(T,N,N) -> 6099 0 = ets:info(T,size), 6100 ok; 6101del_one_by_one_dbag_1(T,From,To) -> 6102 N = ets:info(T,size), 6103 ets:delete_object(T,{From, integer_to_list(From)}), 6104 case From rem 2 of 6105 0 -> 6106 N = (ets:info(T,size) + 2); 6107 1 -> 6108 N = ets:info(T,size) 6109 end, 6110 Next = if 6111 From < To -> 6112 From + 1; 6113 true -> 6114 From - 1 6115 end, 6116 del_one_by_one_dbag_1(T,Next,To). 6117 6118del_one_by_one_dbag_2(T,N,N) -> 6119 0 = ets:info(T,size), 6120 ok; 6121del_one_by_one_dbag_2(T,From,To) -> 6122 N = ets:info(T,size), 6123 ets:delete_object(T,{From, integer_to_list(From)}), 6124 case From rem 2 of 6125 0 -> 6126 N = (ets:info(T,size) + 3); 6127 1 -> 6128 N = (ets:info(T,size) + 1) 6129 end, 6130 Next = if 6131 From < To -> 6132 From + 1; 6133 true -> 6134 From - 1 6135 end, 6136 del_one_by_one_dbag_2(T,Next,To). 6137 6138del_one_by_one_dbag_3(T,N,N) -> 6139 0 = ets:info(T,size), 6140 ok; 6141del_one_by_one_dbag_3(T,From,To) -> 6142 N = ets:info(T,size), 6143 Obj = {From + From rem 2, integer_to_list(From)}, 6144 ets:delete_object(T,Obj), 6145 case From rem 2 of 6146 0 -> 6147 N = (ets:info(T,size) + 2); 6148 1 -> 6149 N = (ets:info(T,size) + 1), 6150 Obj2 = {From, integer_to_list(From)}, 6151 ets:delete_object(T,Obj2), 6152 N = (ets:info(T,size) + 2) 6153 end, 6154 Next = if 6155 From < To -> 6156 From + 1; 6157 true -> 6158 From - 1 6159 end, 6160 del_one_by_one_dbag_3(T,Next,To). 6161 6162 6163successive_delete(Table,From,To,Type) -> 6164 successive_delete(Table,From,To,Type,ets:info(Table,type)). 6165 6166successive_delete(_Table,N,N,_,_) -> 6167 ok; 6168successive_delete(Table,From,To,Type,TType) -> 6169 MS = case Type of 6170 bound -> 6171 [{{From,'_'},[],[true]}]; 6172 unbound -> 6173 [{{'$1','_'},[],[{'==', '$1', From}]}] 6174 end, 6175 case TType of 6176 X when X == bag; X == duplicate_bag -> 6177 %%erlang:display(From), 6178 case From rem 2 of 6179 0 -> 6180 2 = ets:select_delete(Table,MS); 6181 _ -> 6182 0 = ets:select_delete(Table,MS) 6183 end; 6184 _ -> 6185 1 = ets:select_delete(Table,MS) 6186 end, 6187 Next = if 6188 From < To -> 6189 From + 1; 6190 true -> 6191 From - 1 6192 end, 6193 successive_delete(Table, Next, To, Type,TType). 6194 6195gen_dets_filename(Config,N) -> 6196 filename:join(proplists:get_value(priv_dir,Config), 6197 "testdets_" ++ integer_to_list(N) ++ ".dets"). 6198 6199otp_6842_select_1000(Config) when is_list(Config) -> 6200 repeat_for_opts_all_ord_set_table_types( 6201 fun(Opts) -> 6202 KeyRange = 10000, 6203 Tab = ets_new(xxx, Opts, KeyRange), 6204 [ets:insert(Tab,{X,X}) || X <- lists:seq(1,KeyRange)], 6205 AllTrue = lists:duplicate(10,true), 6206 AllTrue = 6207 [ length( 6208 element(1, 6209 ets:select(Tab,[{'_',[],['$_']}],X*1000))) =:= 6210 X*1000 || X <- lists:seq(1,10) ], 6211 Sequences = [[1000,1000,1000,1000,1000,1000,1000,1000,1000,1000], 6212 [2000,2000,2000,2000,2000], 6213 [3000,3000,3000,1000], 6214 [4000,4000,2000], 6215 [5000,5000], 6216 [6000,4000], 6217 [7000,3000], 6218 [8000,2000], 6219 [9000,1000], 6220 [10000]], 6221 AllTrue = [ check_seq(Tab, ets:select(Tab,[{'_',[],['$_']}],hd(L)),L) || 6222 L <- Sequences ], 6223 ets:delete(Tab) 6224 end), 6225 ok. 6226 6227check_seq(_,'$end_of_table',[]) -> 6228 true; 6229check_seq(Tab,{L,C},[H|T]) when length(L) =:= H -> 6230 check_seq(Tab, ets:select(C),T); 6231check_seq(A,B,C) -> 6232 erlang:display({A,B,C}), 6233 false. 6234 6235otp_6338(Config) when is_list(Config) -> 6236 repeat_for_opts_all_ord_set_table_types( 6237 fun(Opts) -> 6238 L = binary_to_term(<<131,108,0,0,0,2,104,2,108,0,0,0,2,103,100,0,19,112,112, 6239 98,49,95,98,115,49,50,64,98,108,97,100,101,95,48,95,53, 6240 0,0,33,50,0,0,0,4,1,98,0,0,23,226,106,100,0,4,101,120, 6241 105,116,104,2,108,0,0,0,2,104,2,100,0,3,115,98,109,100, 6242 0,19,112,112,98,50,95,98,115,49,50,64,98,108,97,100, 6243 101,95,48,95,56,98,0,0,18,231,106,100,0,4,114,101,99, 6244 118,106>>), 6245 T = ets_new(xxx,Opts), 6246 lists:foreach(fun(X) -> ets:insert(T,X) end,L), 6247 [[4839,recv]] = ets:match(T,{[{sbm,ppb2_bs12@blade_0_8},'$1'],'$2'}), 6248 ets:delete(T) 6249 end), 6250 ok. 6251 6252%% OTP-15660: Verify select not doing excessive trapping 6253%% when process have mbuf heap fragments. 6254select_mbuf_trapping(Config) when is_list(Config) -> 6255 select_mbuf_trapping_do(set), 6256 select_mbuf_trapping_do(ordered_set). 6257 6258select_mbuf_trapping_do(Type) -> 6259 T = ets:new(xxx, [Type]), 6260 NKeys = 50, 6261 [ets:insert(T, {K, value}) || K <- lists:seq(1,NKeys)], 6262 6263 {priority, Prio} = process_info(self(), priority), 6264 Tracee = self(), 6265 [SchedTracer] 6266 = start_loopers(1, Prio, 6267 fun (SC) -> 6268 receive 6269 {trace, Tracee, out, _} -> 6270 SC+1; 6271 done -> 6272 Tracee ! {schedule_count, SC}, 6273 exit(normal) 6274 end 6275 end, 6276 0), 6277 6278 erlang:garbage_collect(), 6279 1 = erlang:trace(self(), true, [running,{tracer,SchedTracer}]), 6280 6281 %% Artificially create an mbuf heap fragment 6282 MbufTerm = "Frag me up", 6283 MbufTerm = erts_debug:set_internal_state(mbuf, MbufTerm), 6284 6285 Keys = ets:select(T, [{{'$1', value}, [], ['$1']}]), 6286 NKeys = length(Keys), 6287 6288 1 = erlang:trace(self(), false, [running]), 6289 Ref = erlang:trace_delivered(Tracee), 6290 receive 6291 {trace_delivered, Tracee, Ref} -> 6292 SchedTracer ! done 6293 end, 6294 receive 6295 {schedule_count, N} -> 6296 io:format("~p context switches: ~p", [Type,N]), 6297 if 6298 N < 3 -> ok; 6299 true -> ct:fail(failed) 6300 end 6301 end, 6302 true = ets:delete(T), 6303 ok. 6304 6305 6306 6307%% Elements could come in the wrong order in a bag if a rehash occurred. 6308otp_5340(Config) when is_list(Config) -> 6309 repeat_for_opts(fun otp_5340_do/1). 6310 6311otp_5340_do(Opts) -> 6312 N = 3000, 6313 T = ets_new(otp_5340, [bag,public | Opts]), 6314 Ids = [1,2,3,4,5], 6315 [w(T, N, Id) || Id <- Ids], 6316 verify(T, Ids), 6317 ets:delete(T). 6318 6319w(_,0, _) -> ok; 6320w(T,N, Id) -> 6321 ets:insert(T, {N, Id}), 6322 w(T,N-1,Id). 6323 6324verify(T, Ids) -> 6325 List = my_tab_to_list(T), 6326 Errors = lists:filter(fun(Bucket) -> 6327 verify2(Bucket, Ids) 6328 end, List), 6329 case Errors of 6330 [] -> 6331 ok; 6332 _ -> 6333 io:format("Failed:\n~p\n", [Errors]), 6334 ct:fail(failed) 6335 end. 6336 6337verify2([{_N,Id}|RL], [Id|R]) -> 6338 verify2(RL,R); 6339verify2([],[]) -> false; 6340verify2(_Err, _) -> 6341 true. 6342 6343%% delete_object followed by delete on fixed bag failed to delete objects. 6344otp_7665(Config) when is_list(Config) -> 6345 repeat_for_opts(fun otp_7665_do/1). 6346 6347otp_7665_do(Opts) -> 6348 Tab = ets_new(otp_7665,[bag | Opts]), 6349 Min = 0, 6350 Max = 10, 6351 lists:foreach(fun(N)-> otp_7665_act(Tab,Min,Max,N) end, 6352 lists:seq(Min,Max)), 6353 true = ets:delete(Tab). 6354 6355otp_7665_act(Tab,Min,Max,DelNr) -> 6356 List1 = [{key,N} || N <- lists:seq(Min,Max)], 6357 true = ets:insert(Tab, List1), 6358 true = ets:safe_fixtable(Tab, true), 6359 true = ets:delete_object(Tab, {key,DelNr}), 6360 List2 = lists:sort(lists:delete({key,DelNr}, List1)), 6361 6362 %% Now verify that we find all remaining objects 6363 List2 = lists:sort(ets:lookup(Tab,key)), 6364 EList2 = lists:sort(lists:map(fun({key,N})-> N end, 6365 List2)), 6366 EList2 = lists:sort(ets:lookup_element(Tab,key,2)), 6367 true = ets:delete(Tab, key), 6368 [] = ets:lookup(Tab, key), 6369 true = ets:safe_fixtable(Tab, false), 6370 ok. 6371 6372%% Whitebox testing of meta name table hashing. 6373meta_wb(Config) when is_list(Config) -> 6374 EtsMem = etsmem(), 6375 repeat_for_opts_all_non_stim_table_types(fun meta_wb_do/1), 6376 verify_etsmem(EtsMem). 6377 6378 6379meta_wb_do(Opts) -> 6380 %% Do random new/delete/rename of colliding named tables 6381 Names0 = [pioneer | colliding_names(pioneer)], 6382 6383 %% Remove any names that happen to exist as tables already 6384 Names = lists:filter(fun(Name) -> ets:info(Name) == undefined end, 6385 Names0), 6386 Len = length(Names), 6387 OpFuns = {fun meta_wb_new/4, fun meta_wb_delete/4, fun meta_wb_rename/4}, 6388 6389 true = (Len >= 3), 6390 6391 io:format("Colliding names = ~p\n",[Names]), 6392 F = fun(0,_,_) -> ok; 6393 (N,Tabs,Me) -> 6394 Name1 = lists:nth(rand:uniform(Len), Names), 6395 Name2 = lists:nth(rand:uniform(Len), Names), 6396 Op = element(rand:uniform(3),OpFuns), 6397 NTabs = Op(Name1, Name2, Tabs, Opts), 6398 Me(N-1, NTabs, Me) 6399 end, 6400 F(Len*100, [], F), 6401 6402 %% cleanup 6403 lists:foreach(fun(Name)->catch ets:delete(Name) end, 6404 Names). 6405 6406meta_wb_new(Name, _, Tabs, Opts) -> 6407 case (catch ets_new(Name,[named_table|Opts])) of 6408 Name -> 6409 false = lists:member(Name, Tabs), 6410 [Name | Tabs]; 6411 {'EXIT',{badarg,_}} -> 6412 true = lists:member(Name, Tabs), 6413 Tabs 6414 end. 6415meta_wb_delete(Name, _, Tabs, _) -> 6416 case (catch ets:delete(Name)) of 6417 true -> 6418 true = lists:member(Name, Tabs), 6419 lists:delete(Name, Tabs); 6420 {'EXIT',{badarg,_}} -> 6421 false = lists:member(Name, Tabs), 6422 Tabs 6423 end. 6424meta_wb_rename(Old, New, Tabs, _) -> 6425 case (catch ets:rename(Old,New)) of 6426 New -> 6427 true = lists:member(Old, Tabs) 6428 andalso not lists:member(New, Tabs), 6429 [New | lists:delete(Old, Tabs)]; 6430 {'EXIT',{badarg,_}} -> 6431 true = not lists:member(Old, Tabs) 6432 orelse lists:member(New,Tabs), 6433 Tabs 6434 end. 6435 6436 6437colliding_names(Name) -> 6438 erts_debug:set_internal_state(colliding_names, {Name,5}). 6439 6440 6441%% OTP_6913: Grow and shrink. 6442 6443grow_shrink(Config) when is_list(Config) -> 6444 repeat_for_all_set_table_types( 6445 fun(Opts) -> 6446 EtsMem = etsmem(), 6447 6448 Set = ets_new(a, Opts, 5000), 6449 grow_shrink_0(0, 3071, 3000, 5000, Set), 6450 ets:delete(Set), 6451 6452 verify_etsmem(EtsMem) 6453 end). 6454 6455grow_shrink_0(N, _, _, Max, _) when N >= Max -> 6456 ok; 6457grow_shrink_0(N0, GrowN, ShrinkN, Max, T) -> 6458 N1 = grow_shrink_1(N0, GrowN, ShrinkN, T), 6459 grow_shrink_0(N1, GrowN, ShrinkN, Max, T). 6460 6461grow_shrink_1(N0, GrowN, ShrinkN, T) -> 6462 N1 = grow_shrink_2(N0+1, N0 + GrowN, T), 6463 grow_shrink_3(N1, N1 - ShrinkN, T). 6464 6465grow_shrink_2(N, GrowTo, _) when N > GrowTo -> 6466 GrowTo; 6467grow_shrink_2(N, GrowTo, T) -> 6468 true = ets:insert(T, {N,a}), 6469 grow_shrink_2(N+1, GrowTo, T). 6470 6471grow_shrink_3(N, ShrinkTo, _) when N =< ShrinkTo -> 6472 ShrinkTo; 6473grow_shrink_3(N, ShrinkTo, T) -> 6474 true = ets:delete(T, N), 6475 grow_shrink_3(N-1, ShrinkTo, T). 6476 6477%% Grow a hash table that still contains pseudo-deleted objects. 6478grow_pseudo_deleted(Config) when is_list(Config) -> 6479 only_if_smp(fun() -> grow_pseudo_deleted_do() end). 6480 6481grow_pseudo_deleted_do() -> 6482 lists:foreach(fun(Type) -> grow_pseudo_deleted_do(Type) end, 6483 [set,bag,duplicate_bag]). 6484 6485grow_pseudo_deleted_do(Type) -> 6486 process_flag(scheduler,1), 6487 Self = self(), 6488 T = ets_new(kalle,[Type,public,{write_concurrency,true}]), 6489 Mod = 7, Mult = 10000, 6490 filltabint(T,Mod*Mult), 6491 true = ets:safe_fixtable(T,true), 6492 Mult = ets:select_delete(T, 6493 [{{'$1', '_'}, 6494 [{'=:=', {'rem', '$1', Mod}, 0}], 6495 [true]}]), 6496 Left = Mult*(Mod-1), 6497 Left = ets:info(T,size), 6498 Mult = get_kept_objects(T), 6499 filltabstr(T,Mult), 6500 my_spawn_opt( 6501 fun() -> 6502 true = ets:info(T,fixed), 6503 Self ! start, 6504 io:put_chars("Starting to filltabstr...\n"), 6505 do_tc(fun() -> 6506 filltabstr(T, Mult, Mult+10000) 6507 end, 6508 fun(Elapsed) -> 6509 io:format("Done with filltabstr in ~p ms\n", 6510 [Elapsed]) 6511 end), 6512 Self ! done 6513 end, [link, {scheduler,2}]), 6514 start = receive_any(), 6515 io:format("Unfixing table... nitems=~p\n", [ets:info(T, size)]), 6516 do_tc(fun() -> 6517 true = ets:safe_fixtable(T, false) 6518 end, 6519 fun(Elapsed) -> 6520 io:format("Unfix table done in ~p ms. nitems=~p\n", 6521 [Elapsed,ets:info(T, size)]) 6522 end), 6523 false = ets:info(T,fixed), 6524 0 = get_kept_objects(T), 6525 done = receive_any(), 6526 %%verify_table_load(T), % may fail if concurrency is poor (genny) 6527 ets:delete(T), 6528 process_flag(scheduler,0). 6529 6530%% Shrink a hash table that still contains pseudo-deleted objects. 6531shrink_pseudo_deleted(Config) when is_list(Config) -> 6532 only_if_smp(fun()->shrink_pseudo_deleted_do() end). 6533 6534shrink_pseudo_deleted_do() -> 6535 lists:foreach(fun(Type) -> shrink_pseudo_deleted_do(Type) end, 6536 [set,bag,duplicate_bag]). 6537 6538shrink_pseudo_deleted_do(Type) -> 6539 process_flag(scheduler,1), 6540 Self = self(), 6541 T = ets_new(kalle,[Type,public,{write_concurrency,true}]), 6542 Half = 10000, 6543 filltabint(T,Half*2), 6544 true = ets:safe_fixtable(T,true), 6545 Half = ets:select_delete(T, 6546 [{{'$1', '_'}, 6547 [{'>', '$1', Half}], 6548 [true]}]), 6549 Half = ets:info(T,size), 6550 Half = get_kept_objects(T), 6551 my_spawn_opt( 6552 fun()-> true = ets:info(T,fixed), 6553 Self ! start, 6554 io:put_chars("Starting to delete... ~p\n"), 6555 do_tc(fun() -> 6556 del_one_by_one_set(T, 1, Half+1) 6557 end, 6558 fun(Elapsed) -> 6559 io:format("Done with delete in ~p ms.\n", 6560 [Elapsed]) 6561 end), 6562 Self ! done 6563 end, [link, {scheduler,2}]), 6564 start = receive_any(), 6565 io:format("Unfixing table... nitems=~p\n", [ets:info(T, size)]), 6566 do_tc(fun() -> 6567 true = ets:safe_fixtable(T, false) 6568 end, 6569 fun(Elapsed) -> 6570 io:format("Unfix table done in ~p ms. nitems=~p\n", 6571 [Elapsed,ets:info(T, size)]) 6572 end), 6573 false = ets:info(T,fixed), 6574 0 = get_kept_objects(T), 6575 done = receive_any(), 6576 %%verify_table_load(T), % may fail if concurrency is poor (genny) 6577 ets:delete(T), 6578 process_flag(scheduler,0). 6579 6580 6581 6582meta_lookup_unnamed_read(Config) when is_list(Config) -> 6583 InitF = fun(_) -> Tab = ets_new(unnamed,[]), 6584 true = ets:insert(Tab,{key,data}), 6585 Tab 6586 end, 6587 ExecF = fun(Tab) -> [{key,data}] = ets:lookup(Tab,key), 6588 Tab 6589 end, 6590 FiniF = fun(Tab) -> true = ets:delete(Tab) 6591 end, 6592 run_smp_workers(InitF,ExecF,FiniF,10000). 6593 6594meta_lookup_unnamed_write(Config) when is_list(Config) -> 6595 InitF = fun(_) -> Tab = ets_new(unnamed,[]), 6596 {Tab,0} 6597 end, 6598 ExecF = fun({Tab,N}) -> true = ets:insert(Tab,{key,N}), 6599 {Tab,N+1} 6600 end, 6601 FiniF = fun({Tab,_}) -> true = ets:delete(Tab) 6602 end, 6603 run_smp_workers(InitF,ExecF,FiniF,10000). 6604 6605meta_lookup_named_read(Config) when is_list(Config) -> 6606 InitF = fun([ProcN|_]) -> Name = list_to_atom(integer_to_list(ProcN)), 6607 Tab = ets_new(Name,[named_table]), 6608 true = ets:insert(Tab,{key,data}), 6609 Tab 6610 end, 6611 ExecF = fun(Tab) -> [{key,data}] = ets:lookup(Tab,key), 6612 Tab 6613 end, 6614 FiniF = fun(Tab) -> true = ets:delete(Tab) 6615 end, 6616 run_smp_workers(InitF,ExecF,FiniF,10000). 6617 6618meta_lookup_named_write(Config) when is_list(Config) -> 6619 InitF = fun([ProcN|_]) -> Name = list_to_atom(integer_to_list(ProcN)), 6620 Tab = ets_new(Name,[named_table]), 6621 {Tab,0} 6622 end, 6623 ExecF = fun({Tab,N}) -> true = ets:insert(Tab,{key,N}), 6624 {Tab,N+1} 6625 end, 6626 FiniF = fun({Tab,_}) -> true = ets:delete(Tab) 6627 end, 6628 run_smp_workers(InitF,ExecF,FiniF,10000). 6629 6630meta_newdel_unnamed(Config) when is_list(Config) -> 6631 InitF = fun(_) -> ok end, 6632 ExecF = fun(_) -> Tab = ets_new(unnamed,[]), 6633 true = ets:delete(Tab) 6634 end, 6635 FiniF = fun(_) -> ok end, 6636 run_smp_workers(InitF,ExecF,FiniF,10000). 6637 6638meta_newdel_named(Config) when is_list(Config) -> 6639 InitF = fun([ProcN|_]) -> list_to_atom(integer_to_list(ProcN)) 6640 end, 6641 ExecF = fun(Name) -> Name = ets_new(Name,[named_table]), 6642 true = ets:delete(Name), 6643 Name 6644 end, 6645 FiniF = fun(_) -> ok end, 6646 run_smp_workers(InitF,ExecF,FiniF,10000). 6647 6648%% Concurrent insert's on same table. 6649smp_insert(Config) when is_list(Config) -> 6650 repeat_for_opts(fun smp_insert_do/1, 6651 [[set,ordered_set,stim_cat_ord_set]]). 6652 6653smp_insert_do(Opts) -> 6654 KeyRange = 10000, 6655 ets_new(smp_insert,[named_table,public,{write_concurrency,true}|Opts], 6656 KeyRange), 6657 InitF = fun(_) -> ok end, 6658 ExecF = fun(_) -> true = ets:insert(smp_insert,{rand:uniform(KeyRange)}) 6659 end, 6660 FiniF = fun(_) -> ok end, 6661 run_smp_workers(InitF,ExecF,FiniF,100000), 6662 verify_table_load(smp_insert), 6663 ets:delete(smp_insert). 6664 6665%% Concurrent deletes on same fixated table. 6666smp_fixed_delete(Config) when is_list(Config) -> 6667 only_if_smp(fun() -> smp_fixed_delete_do() end). 6668 6669smp_fixed_delete_do() -> 6670 T = ets_new(foo,[public,{write_concurrency,true}]), 6671 %%Mem = ets:info(T,memory), 6672 NumOfObjs = 100000, 6673 filltabint(T,NumOfObjs), 6674 ets:safe_fixtable(T,true), 6675 Buckets = num_of_buckets(T), 6676 InitF = fun([ProcN,NumOfProcs|_]) -> {ProcN,NumOfProcs} end, 6677 ExecF = fun({Key,_}) when Key > NumOfObjs -> 6678 [end_of_work]; 6679 ({Key,Increment}) -> 6680 true = ets:delete(T,Key), 6681 {Key+Increment,Increment} 6682 end, 6683 FiniF = fun(_) -> ok end, 6684 run_sched_workers(InitF,ExecF,FiniF,NumOfObjs), 6685 0 = ets:info(T,size), 6686 true = ets:info(T,fixed), 6687 Buckets = num_of_buckets(T), 6688 case ets:info(T,type) of 6689 set -> NumOfObjs = get_kept_objects(T); 6690 _ -> ok 6691 end, 6692 ets:safe_fixtable(T,false), 6693 %% Will fail as unfix does not shrink the table: 6694 %%Mem = ets:info(T,memory), 6695 %%verify_table_load(T), 6696 ets:delete(T). 6697 6698%% ERL-720 6699%% Provoke race between ets:delete and table unfix (by select_count) 6700%% that caused ets_misc memory counter to indicate false leak. 6701delete_unfix_race(Config) when is_list(Config) -> 6702 EtsMem = etsmem(), 6703 Table = ets:new(t,[set,public,{write_concurrency,true}]), 6704 InsertOp = 6705 fun() -> 6706 receive stop -> 6707 false 6708 after 0 -> 6709 ets:insert(Table, {rand:uniform(10)}), 6710 true 6711 end 6712 end, 6713 DeleteOp = 6714 fun() -> 6715 receive stop -> 6716 false 6717 after 0 -> 6718 ets:delete(Table, rand:uniform(10)), 6719 true 6720 end 6721 end, 6722 SelectOp = 6723 fun() -> 6724 ets:select_count(Table, ets:fun2ms(fun(X) -> true end)) 6725 end, 6726 Main = self(), 6727 Ins = spawn(fun()-> repeat_while(InsertOp), Main ! self() end), 6728 Del = spawn(fun()-> repeat_while(DeleteOp), Main ! self() end), 6729 spawn(fun()-> 6730 repeat(SelectOp, 10000), 6731 Del ! stop, 6732 Ins ! stop 6733 end), 6734 [receive Pid -> ok end || Pid <- [Ins,Del]], 6735 ets:delete(Table), 6736 verify_etsmem(EtsMem). 6737 6738num_of_buckets(T) -> 6739 case ets:info(T,type) of 6740 set -> element(1,ets:info(T,stats)); 6741 bag -> element(1,ets:info(T,stats)); 6742 duplicate_bag -> element(1,ets:info(T,stats)); 6743 _ -> ok 6744 end. 6745 6746%% Fixate hash table while other process is busy doing unfix. 6747smp_unfix_fix(Config) when is_list(Config) -> 6748 only_if_smp(fun()-> smp_unfix_fix_do() end). 6749 6750smp_unfix_fix_do() -> 6751 process_flag(scheduler,1), 6752 Parent = self(), 6753 T = ets_new(foo,[public,{write_concurrency,true}]), 6754 %%Mem = ets:info(T,memory), 6755 NumOfObjs = 100000, 6756 Deleted = 50000, 6757 filltabint(T,NumOfObjs), 6758 ets:safe_fixtable(T,true), 6759 Buckets = num_of_buckets(T), 6760 Deleted = ets:select_delete(T,[{{'$1', '_'}, 6761 [{'=<','$1', Deleted}], 6762 [true]}]), 6763 Buckets = num_of_buckets(T), 6764 Left = NumOfObjs - Deleted, 6765 Left = ets:info(T,size), 6766 true = ets:info(T,fixed), 6767 Deleted = get_kept_objects(T), 6768 6769 {Child, Mref} = 6770 my_spawn_opt( 6771 fun()-> 6772 true = ets:info(T,fixed), 6773 Parent ! start, 6774 io:format("Child waiting for table to be unfixed... mem=~p\n", 6775 [ets:info(T, memory)]), 6776 do_tc(fun() -> 6777 repeat_while(fun()-> ets:info(T, fixed) end) 6778 end, 6779 fun(Elapsed) -> 6780 io:format("Table unfixed in ~p ms." 6781 " Child Fixating! mem=~p\n", 6782 [Elapsed,ets:info(T,memory)]) 6783 end), 6784 true = ets:safe_fixtable(T,true), 6785 repeat_while(fun(Key) when Key =< NumOfObjs -> 6786 ets:delete(T,Key), {true,Key+1}; 6787 (Key) -> {false,Key} 6788 end, 6789 Deleted), 6790 0 = ets:info(T,size), 6791 true = get_kept_objects(T) >= Left, 6792 done = receive_any() 6793 end, 6794 [link, monitor, {scheduler,2}]), 6795 6796 start = receive_any(), 6797 true = ets:info(T,fixed), 6798 io:put_chars("Parent starting to unfix... ~p\n"), 6799 do_tc(fun() -> 6800 ets:safe_fixtable(T, false) 6801 end, 6802 fun(Elapsed) -> 6803 io:format("Parent done with unfix in ~p ms.\n", 6804 [Elapsed]) 6805 end), 6806 Child ! done, 6807 {'DOWN', Mref, process, Child, normal} = receive_any(), 6808 false = ets:info(T,fixed), 6809 0 = get_kept_objects(T), 6810 %%verify_table_load(T), 6811 ets:delete(T), 6812 process_flag(scheduler,0). 6813 6814%% Unsafe unfix was done by trapping select/match. 6815otp_8166(Config) when is_list(Config) -> 6816 only_if_smp(3, fun()-> otp_8166_do(false), 6817 otp_8166_do(true) 6818 end). 6819 6820otp_8166_do(WC) -> 6821 %% Bug scenario: One process segv while reading the table because another 6822 %% process is doing unfix without write-lock at the end of a trapping match_object. 6823 process_flag(scheduler,1), 6824 T = ets_new(foo,[public, {write_concurrency,WC}]), 6825 NumOfObjs = 3000, %% Need more than 1000 live objects for match_object to trap one time 6826 Deleted = NumOfObjs div 2, 6827 filltabint(T,NumOfObjs), 6828 {ReaderPid, ReaderMref} = my_spawn_opt(fun()-> otp_8166_reader(T,NumOfObjs) end, 6829 [link, monitor, {scheduler,2}]), 6830 {ZombieCrPid, ZombieCrMref} = my_spawn_opt(fun()-> otp_8166_zombie_creator(T,Deleted) end, 6831 [link, monitor, {scheduler,3}]), 6832 6833 repeat(fun() -> ZombieCrPid ! {loop, self()}, 6834 zombies_created = receive_any(), 6835 otp_8166_trapper(T, 10, ZombieCrPid) 6836 end, 100), 6837 6838 ReaderPid ! quit, 6839 {'DOWN', ReaderMref, process, ReaderPid, normal} = receive_any(), 6840 ZombieCrPid ! quit, 6841 {'DOWN', ZombieCrMref, process, ZombieCrPid, normal} = receive_any(), 6842 false = ets:info(T,fixed), 6843 0 = get_kept_objects(T), 6844 %%verify_table_load(T), 6845 ets:delete(T), 6846 process_flag(scheduler,0). 6847 6848%% Keep reading the table 6849otp_8166_reader(T, NumOfObjs) -> 6850 repeat_while(fun(0) -> 6851 receive quit -> {false,done} 6852 after 0 -> {true,NumOfObjs} 6853 end; 6854 (Key) -> 6855 ets:lookup(T,Key), 6856 {true, Key-1} 6857 end, 6858 NumOfObjs). 6859 6860%% Do a match_object that will trap and thereby fixate and then unfixate the table 6861otp_8166_trapper(T, Try, ZombieCrPid) -> 6862 [] = ets:match_object(T,{'_',"Pink Unicorn"}), 6863 case {ets:info(T,fixed),Try} of 6864 {true,1} -> 6865 io:format("failed to provoke unsafe unfix, give up...\n",[]), 6866 ZombieCrPid ! unfix; 6867 {true,_} -> 6868 io:format("trapper too fast, trying again...\n",[]), 6869 otp_8166_trapper(T, Try-1, ZombieCrPid); 6870 {false,_} -> done 6871 end. 6872 6873 6874%% Fixate table and create some pseudo-deleted objects (zombies) 6875%% Then wait for trapper to fixate before unfixing, as we want the trappers' 6876%% unfix to be the one that purges the zombies. 6877otp_8166_zombie_creator(T,Deleted) -> 6878 case receive_any() of 6879 quit -> done; 6880 6881 {loop,Pid} -> 6882 filltabint(T,Deleted), 6883 ets:safe_fixtable(T,true), 6884 Deleted = ets:select_delete(T,[{{'$1', '_'}, 6885 [{'=<','$1', Deleted}], 6886 [true]}]), 6887 Pid ! zombies_created, 6888 repeat_while(fun() -> case ets:info(T,safe_fixed_monotonic_time) of 6889 {_,[_P1,_P2]} -> 6890 false; 6891 _ -> 6892 receive unfix -> false 6893 after 0 -> true 6894 end 6895 end 6896 end), 6897 ets:safe_fixtable(T,false), 6898 otp_8166_zombie_creator(T,Deleted); 6899 6900 unfix -> 6901 io:format("ignore unfix in outer loop?\n",[]), 6902 otp_8166_zombie_creator(T,Deleted) 6903 end. 6904 6905 6906 6907 6908verify_table_load(T) -> 6909 case ets:info(T,type) of 6910 ordered_set -> ok; 6911 _ -> 6912 Stats = ets:info(T,stats), 6913 {Buckets,AvgLen,StdDev,ExpSD,_MinLen,_MaxLen,_} = Stats, 6914 ok = if 6915 AvgLen > 1.2 -> 6916 io:format("Table overloaded: Stats=~p\n~p\n", 6917 [Stats, ets:info(T)]), 6918 false; 6919 6920 Buckets>256, AvgLen < 0.47 -> 6921 io:format("Table underloaded: Stats=~p\n~p\n", 6922 [Stats, ets:info(T)]), 6923 false; 6924 6925 StdDev > ExpSD*2 -> 6926 io:format("Too large standard deviation (poor hashing?)," 6927 " stats=~p\n~p\n",[Stats, ets:info(T)]), 6928 false; 6929 6930 true -> 6931 io:format("Stats = ~p\n~p\n",[Stats, ets:info(T)]), 6932 ok 6933 end 6934 end. 6935 6936 6937%% ets:select on a tree with NIL key object. 6938otp_8732(Config) when is_list(Config) -> 6939 repeat_for_all_ord_set_table_types( 6940 fun(Opts) -> 6941 KeyRange = 999, 6942 KeyFun = fun(K) -> integer_to_list(K) end, 6943 Tab = ets_new(noname,Opts, KeyRange, KeyFun), 6944 filltabstr(Tab, KeyRange), 6945 ets:insert(Tab,{[],"nasty NIL object"}), 6946 [] = ets:match(Tab,{'_',nomatch}) %% Will hang if bug not fixed 6947 end), 6948 ok. 6949 6950 6951%% Run concurrent select_delete (and inserts) on same table. 6952smp_select_delete(Config) when is_list(Config) -> 6953 repeat_for_opts(fun smp_select_delete_do/1, 6954 [[set,ordered_set,stim_cat_ord_set], 6955 read_concurrency, compressed]). 6956 6957smp_select_delete_do(Opts) -> 6958 KeyRange = 10000, 6959 begin % indentation 6960 T = ets_new(smp_select_delete,[named_table,public,{write_concurrency,true}|Opts], 6961 KeyRange), 6962 Mod = 17, 6963 Zeros = erlang:make_tuple(Mod,0), 6964 InitF = fun(_) -> Zeros end, 6965 ExecF = fun(Diffs0) -> 6966 case rand:uniform(20) of 6967 1 -> 6968 Mod = 17, 6969 Eq = rand:uniform(Mod) - 1, 6970 Deleted = ets:select_delete(T, 6971 [{{'_', '$1'}, 6972 [{'=:=', {'rem', '$1', Mod}, Eq}], 6973 [true]}]), 6974 Diffs1 = setelement(Eq+1, Diffs0, 6975 element(Eq+1,Diffs0) - Deleted), 6976 Diffs1; 6977 _ -> 6978 Key = rand:uniform(KeyRange), 6979 Eq = Key rem Mod, 6980 case ets:insert_new(T,{Key,Key}) of 6981 true -> 6982 Diffs1 = setelement(Eq+1, Diffs0, 6983 element(Eq+1,Diffs0)+1), 6984 Diffs1; 6985 false -> Diffs0 6986 end 6987 end 6988 end, 6989 FiniF = fun(Result) -> Result end, 6990 Results = run_sched_workers(InitF,ExecF,FiniF,20000), 6991 TotCnts = lists:foldl(fun(Diffs, Sum) -> add_lists(Sum,tuple_to_list(Diffs)) end, 6992 lists:duplicate(Mod, 0), Results), 6993 io:format("TotCnts = ~p\n",[TotCnts]), 6994 LeftInTab = lists:foldl(fun(N,Sum) -> Sum+N end, 6995 0, TotCnts), 6996 io:format("LeftInTab = ~p\n",[LeftInTab]), 6997 LeftInTab = ets:info(T,size), 6998 lists:foldl(fun(Cnt,Eq) -> 6999 WasCnt = ets:select_count(T, 7000 [{{'_', '$1'}, 7001 [{'=:=', {'rem', '$1', Mod}, Eq}], 7002 [true]}]), 7003 io:format("~p: ~p =?= ~p\n",[Eq,Cnt,WasCnt]), 7004 Cnt = WasCnt, 7005 Eq+1 7006 end, 7007 0, TotCnts), 7008 %% May fail as select_delete does not shrink table (enough) 7009 %%verify_table_load(T), 7010 LeftInTab = ets:select_delete(T, [{{'$1','$1'}, [], [true]}]), 7011 0 = ets:info(T,size), 7012 false = ets:info(T,fixed), 7013 ets:delete(T) 7014 end, % indentation 7015 ok. 7016 7017smp_select_replace(Config) when is_list(Config) -> 7018 repeat_for_opts(fun smp_select_replace_do/1, 7019 [[set,ordered_set,stim_cat_ord_set,duplicate_bag], 7020 compressed]). 7021 7022smp_select_replace_do(Opts) -> 7023 KeyRange = 20, 7024 T = ets_new(smp_select_replace, 7025 [public, {write_concurrency, true} | Opts], 7026 KeyRange), 7027 InitF = fun (_) -> 0 end, 7028 ExecF = fun (Cnt0) -> 7029 CounterId = rand:uniform(KeyRange), 7030 Match = [{{'$1', '$2'}, 7031 [{'=:=', '$1', CounterId}], 7032 [{{'$1', {'+', '$2', 1}}}]}], 7033 Cnt1 = case ets:select_replace(T, Match) of 7034 1 -> Cnt0+1; 7035 0 -> 7036 ets:insert_new(T, {CounterId, 0}), 7037 Cnt0 7038 end, 7039 receive stop -> 7040 [end_of_work | Cnt1] 7041 after 0 -> 7042 Cnt1 7043 end 7044 end, 7045 FiniF = fun (Cnt) -> Cnt end, 7046 Pids = run_sched_workers(InitF, ExecF, FiniF, infinite), 7047 receive after 3*1000 -> ok end, 7048 [P ! stop || P <- Pids], 7049 Results = wait_pids(Pids), 7050 FinalCounts = ets:select(T, [{{'_', '$1'}, [], ['$1']}]), 7051 Total = lists:sum(FinalCounts), 7052 Total = lists:sum(Results), 7053 KeyRange = ets:select_delete(T, [{{'_', '_'}, [], [true]}]), 7054 0 = ets:info(T, size), 7055 true = ets:delete(T), 7056 ok. 7057 7058%% Iterate ordered_set with write_concurrency 7059%% and make sure we hit all "stable" long lived keys 7060%% while "volatile" objects are randomly inserted and deleted. 7061smp_ordered_iteration(Config) when is_list(Config) -> 7062 repeat_for_opts(fun smp_ordered_iteration_do/1, 7063 [[cat_ord_set,stim_cat_ord_set]]). 7064 7065 7066smp_ordered_iteration_do(Opts) -> 7067 KeyRange = 1000, 7068 OffHeap = erts_test_utils:mk_ext_pid({a@b,1}, 4711, 1), 7069 KeyFun = fun(K, Type) -> 7070 {K div 10, K rem 10, Type, OffHeap} 7071 end, 7072 StimKeyFun = fun(K) -> 7073 KeyFun(K, element(rand:uniform(3), 7074 {stable, other, volatile})) 7075 end, 7076 T = ets_new(smp_ordered_iteration, [public, {write_concurrency,true} | Opts], 7077 KeyRange, StimKeyFun), 7078 NStable = KeyRange div 4, 7079 prefill_table(T, KeyRange, NStable, fun(K) -> {KeyFun(K, stable), 0} end), 7080 NStable = ets:info(T, size), 7081 NVolatile = KeyRange div 2, 7082 prefill_table(T, KeyRange, NVolatile, fun(K) -> {KeyFun(K, volatile), 0} end), 7083 7084 InitF = fun (_) -> #{insert => 0, delete => 0, 7085 select_delete_bk => 0, select_delete_pbk => 0, 7086 select_replace_bk => 0, select_replace_pbk => 0} 7087 end, 7088 ExecF = fun (Counters) -> 7089 K = rand:uniform(KeyRange), 7090 Key = KeyFun(K, volatile), 7091 Acc = case rand:uniform(22) of 7092 R when R =< 10 -> 7093 ets:insert(T, {Key}), 7094 incr_counter(insert, Counters); 7095 R when R =< 15 -> 7096 ets:delete(T, Key), 7097 incr_counter(delete, Counters); 7098 R when R =< 19 -> 7099 %% Delete bound key 7100 ets:select_delete(T, [{{Key, '_'}, [], [true]}]), 7101 incr_counter(select_delete_bk, Counters); 7102 R when R =< 20 -> 7103 %% Delete partially bound key 7104 ets:select_delete(T, [{{{K div 10, '_', volatile, '_'}, '_'}, [], [true]}]), 7105 incr_counter(select_delete_pbk, Counters); 7106 R when R =< 21 -> 7107 %% Replace bound key 7108 ets:select_replace(T, [{{Key, '$1'}, [], 7109 [{{{const,Key}, {'+','$1',1}}}]}]), 7110 incr_counter(select_replace_bk, Counters); 7111 _ -> 7112 %% Replace partially bound key 7113 ets:select_replace(T, [{{{K div 10, '_', volatile, '_'}, '$1'}, [], 7114 [{{{element,1,'$_'}, {'+','$1',1}}}]}]), 7115 incr_counter(select_replace_pbk, Counters) 7116 end, 7117 receive stop -> 7118 [end_of_work | Acc] 7119 after 0 -> 7120 Acc 7121 end 7122 end, 7123 FiniF = fun (Acc) -> Acc end, 7124 Pids = run_sched_workers(InitF, ExecF, FiniF, infinite), 7125 timer:send_after(1000, stop), 7126 7127 Log2ChunkMax = math:log2(NStable*2), 7128 Rounds = fun Loop(N) -> 7129 MS = [{{{'_', '_', stable, '_'}, '_'}, [], [true]}], 7130 NStable = ets:select_count(T, MS), 7131 NStable = count_stable(T, next, ets:first(T), 0), 7132 NStable = count_stable(T, prev, ets:last(T), 0), 7133 NStable = length(ets:select(T, MS)), 7134 NStable = length(ets:select_reverse(T, MS)), 7135 Chunk = round(math:pow(2, rand:uniform()*Log2ChunkMax)), 7136 NStable = ets_select_chunks_count(T, MS, Chunk), 7137 receive stop -> N 7138 after 0 -> Loop(N+1) 7139 end 7140 end (1), 7141 [P ! stop || P <- Pids], 7142 Results = wait_pids(Pids), 7143 io:format("Ops = ~p\n", [maps_sum(Results)]), 7144 io:format("Diff = ~p\n", [ets:info(T,size) - NStable - NVolatile]), 7145 io:format("Stats = ~p\n", [ets:info(T,stats)]), 7146 io:format("Rounds = ~p\n", [Rounds]), 7147 true = ets:delete(T), 7148 7149 %% Verify no leakage of offheap key data 7150 ok = erts_test_utils:check_node_dist(), 7151 ok. 7152 7153incr_counter(Name, Counters) -> 7154 Counters#{Name => maps:get(Name, Counters, 0) + 1}. 7155 7156count_stable(T, Next, {_, _, stable, _}=Key, N) -> 7157 count_stable(T, Next, ets:Next(T, Key), N+1); 7158count_stable(T, Next, {_, _, volatile, _}=Key, N) -> 7159 count_stable(T, Next, ets:Next(T, Key), N); 7160count_stable(_, _, '$end_of_table', N) -> 7161 N. 7162 7163ets_select_chunks_count(T, MS, Chunk) -> 7164 ets_select_chunks_count(ets:select(T, MS, Chunk), 0). 7165 7166ets_select_chunks_count('$end_of_table', N) -> 7167 N; 7168ets_select_chunks_count({List, Continuation}, N) -> 7169 ets_select_chunks_count(ets:select(Continuation), 7170 length(List) + N). 7171 7172maps_sum([Ma | Tail]) when is_map(Ma) -> 7173 maps_sum([lists:sort(maps:to_list(Ma)) | Tail]); 7174maps_sum([La, Mb | Tail]) -> 7175 Lab = lists:zipwith(fun({K,Va}, {K,Vb}) -> {K,Va+Vb} end, 7176 La, 7177 lists:sort(maps:to_list(Mb))), 7178 maps_sum([Lab | Tail]); 7179maps_sum([L]) -> 7180 L. 7181 7182 7183 7184 7185%% Test different types. 7186types(Config) when is_list(Config) -> 7187 init_externals(), 7188 repeat_for_opts(fun types_do/1, [repeat_for_opts_atom2list(set_types), 7189 compressed]). 7190 7191types_do(Opts) -> 7192 EtsMem = etsmem(), 7193 T = ets_new(xxx,Opts), 7194 Fun = fun(Term) -> 7195 ets:insert(T,{Term}), 7196 [{Term}] = ets:lookup(T,Term), 7197 ets:insert(T,{Term,xxx}), 7198 [{Term,xxx}] = ets:lookup(T,Term), 7199 ets:insert(T,{Term,"xxx"}), 7200 [{Term,"xxx"}] = ets:lookup(T,Term), 7201 ets:insert(T,{xxx,Term}), 7202 [{xxx,Term}] = ets:lookup(T,xxx), 7203 ets:insert(T,{"xxx",Term}), 7204 [{"xxx",Term}] = ets:lookup(T,"xxx"), 7205 ets:delete_all_objects(T), 7206 0 = ets:info(T,size) 7207 end, 7208 test_terms(Fun, strict), 7209 ets:delete(T), 7210 verify_etsmem(EtsMem). 7211 7212 7213%% OTP-9932: Memory overwrite when inserting large integers in compressed bag. 7214%% Will crash with segv on 64-bit opt if not fixed. 7215otp_9932(Config) when is_list(Config) -> 7216 T = ets_new(xxx, [bag, compressed]), 7217 Fun = fun(N) -> 7218 Key = {1316110174588445 bsl N,1316110174588583 bsl N}, 7219 S = {Key, Key}, 7220 true = ets:insert(T, S), 7221 [S] = ets:lookup(T, Key), 7222 true = ets:insert(T, S), 7223 [S] = ets:lookup(T, Key) 7224 end, 7225 lists:foreach(Fun, lists:seq(0, 16)), 7226 ets:delete(T). 7227 7228 7229%% vm-deadlock caused by race between ets:delete and others on 7230%% write_concurrency table. 7231otp_9423(Config) when is_list(Config) -> 7232 repeat_for_all_non_stim_set_table_types( 7233 fun(Opts) -> 7234 InitF = fun(_) -> {0,0} end, 7235 ExecF = fun({S,F}) -> 7236 receive 7237 stop -> 7238 io:format("~p got stop\n", [self()]), 7239 [end_of_work | {"Succeded=",S,"Failed=",F}] 7240 after 0 -> 7241 %%io:format("~p (~p) doing lookup\n", [self(), {S,F}]), 7242 try ets:lookup(otp_9423, key) of 7243 [] -> {S+1,F} 7244 catch 7245 error:badarg -> {S,F+1} 7246 end 7247 end 7248 end, 7249 FiniF = fun(R) -> R end, 7250 case run_smp_workers(InitF, ExecF, FiniF, infinite, 1) of 7251 Pids when is_list(Pids) -> 7252 %%[P ! start || P <- Pids], 7253 repeat(fun() -> ets_new(otp_9423, [named_table, public, 7254 {write_concurrency,true}|Opts]), 7255 ets:delete(otp_9423) 7256 end, 10000), 7257 [P ! stop || P <- Pids], 7258 wait_pids(Pids), 7259 ok; 7260 7261 Skipped -> Skipped 7262 end 7263 end). 7264 7265 7266 7267%% Corrupted binary in compressed table 7268otp_10182(Config) when is_list(Config) -> 7269 repeat_for_opts_all_table_types( 7270 fun(Opts) -> 7271 Bin = <<"aHR0cDovL2hvb3RzdWl0ZS5jb20vYy9wcm8tYWRyb2xsLWFi">>, 7272 Key = {test, Bin}, 7273 Value = base64:decode(Bin), 7274 In = {Key,Value}, 7275 Db = ets_new(undefined, Opts), 7276 ets:insert(Db, In), 7277 [Out] = ets:lookup(Db, Key), 7278 io:format("In : ~p\nOut: ~p\n", [In,Out]), 7279 ets:delete(Db), 7280 In = Out 7281 end). 7282 7283%% Test that ets:all include/exclude tables that we know are created/deleted 7284ets_all(Config) when is_list(Config) -> 7285 Pids = [spawn_link(fun() -> ets_all_run() end) || _ <- [1,2]], 7286 receive after 3*1000 -> ok end, 7287 [begin unlink(P), exit(P,kill) end || P <- Pids], 7288 ok. 7289 7290ets_all_run() -> 7291 Table = ets:new(undefined, []), 7292 true = lists:member(Table, ets:all()), 7293 ets:delete(Table), 7294 false = lists:member(Table, ets:all()), 7295 ets_all_run(). 7296 7297create_tables(N) -> 7298 create_tables(N, []). 7299 7300create_tables(0, Ts) -> 7301 Ts; 7302create_tables(N, Ts) -> 7303 create_tables(N-1, [ets:new(tjo, [])|Ts]). 7304 7305massive_ets_all(Config) when is_list(Config) -> 7306 Me = self(), 7307 InitTables = lists:sort(ets:all()), 7308 io:format("InitTables=~p~n", [InitTables]), 7309 PMs0 = lists:map(fun (Sid) -> 7310 my_spawn_opt(fun () -> 7311 Ts = create_tables(250), 7312 Me ! {self(), up, Ts}, 7313 receive {Me, die} -> ok end 7314 end, 7315 [link, monitor, {scheduler, Sid}]) 7316 end, 7317 lists:seq(1, erlang:system_info(schedulers_online))), 7318 AllRes = lists:sort(lists:foldl(fun ({P, _M}, Ts) -> 7319 receive 7320 {P, up, PTs} -> 7321 PTs ++ Ts 7322 end 7323 end, 7324 InitTables, 7325 PMs0)), 7326 AllRes = lists:sort(ets:all()), 7327 PMs1 = lists:map(fun (_) -> 7328 my_spawn_opt(fun () -> 7329 AllRes = lists:sort(ets:all()) 7330 end, 7331 [link, monitor]) 7332 end, lists:seq(1, 50)), 7333 lists:foreach(fun ({P, M}) -> 7334 receive 7335 {'DOWN', M, process, P, _} -> 7336 ok 7337 end 7338 end, PMs1), 7339 PMs2 = lists:map(fun (_) -> 7340 my_spawn_opt(fun () -> 7341 _ = ets:all() 7342 end, 7343 [link, monitor]) 7344 end, lists:seq(1, 50)), 7345 lists:foreach(fun ({P, _M}) -> 7346 P ! {Me, die} 7347 end, PMs0), 7348 lists:foreach(fun ({P, M}) -> 7349 receive 7350 {'DOWN', M, process, P, _} -> 7351 ok 7352 end 7353 end, PMs0 ++ PMs2), 7354 EndTables = lists:sort(ets:all()), 7355 io:format("EndTables=~p~n", [EndTables]), 7356 InitTables = EndTables, 7357 ok. 7358 7359 7360take(Config) when is_list(Config) -> 7361 %% Simple test for set tables. 7362 T1 = ets_new(a, [set]), 7363 [] = ets:take(T1, foo), 7364 ets:insert(T1, {foo,bar}), 7365 [] = ets:take(T1, bar), 7366 [{foo,bar}] = ets:take(T1, foo), 7367 [] = ets:tab2list(T1), 7368 %% Non-immediate key. 7369 ets:insert(T1, {{'not',<<"immediate">>},ok}), 7370 [{{'not',<<"immediate">>},ok}] = ets:take(T1, {'not',<<"immediate">>}), 7371 %% Same with ordered tables. 7372 repeat_for_all_ord_set_table_types( 7373 fun(Opts) -> 7374 T2 = ets_new(b, Opts), 7375 [] = ets:take(T2, foo), 7376 ets:insert(T2, {foo,bar}), 7377 [] = ets:take(T2, bar), 7378 [{foo,bar}] = ets:take(T2, foo), 7379 [] = ets:tab2list(T2), 7380 ets:insert(T2, {{'not',<<"immediate">>},ok}), 7381 [{{'not',<<"immediate">>},ok}] = ets:take(T2, {'not',<<"immediate">>}), 7382 %% Arithmetically-equal keys. 7383 ets:insert(T2, [{1.0,float},{2,integer}]), 7384 [{1.0,float}] = ets:take(T2, 1), 7385 [{2,integer}] = ets:take(T2, 2.0), 7386 [] = ets:tab2list(T2), 7387 ets:delete(T2) 7388 end), 7389 %% Same with bag. 7390 T3 = ets_new(c, [bag]), 7391 ets:insert(T3, [{1,1},{1,2},{3,3}]), 7392 R = lists:sort([{1,1},{1,2}]), 7393 R = lists:sort(ets:take(T3, 1)), 7394 [{3,3}] = ets:take(T3, 3), 7395 [] = ets:tab2list(T3), 7396 ets:delete(T1), 7397 ets:delete(T3), 7398 ok. 7399 7400whereis_table(Config) when is_list(Config) -> 7401 %% Do we return 'undefined' when the named table doesn't exist? 7402 undefined = ets:whereis(whereis_test), 7403 7404 %% Does the tid() refer to the same table as the name? 7405 whereis_test = ets:new(whereis_test, [named_table]), 7406 Tid = ets:whereis(whereis_test), 7407 7408 ets:insert(whereis_test, [{hello}, {there}]), 7409 7410 [[{hello}],[{there}]] = ets:match(whereis_test, '$1'), 7411 [[{hello}],[{there}]] = ets:match(Tid, '$1'), 7412 7413 true = ets:delete_all_objects(Tid), 7414 7415 [] = ets:match(whereis_test, '$1'), 7416 [] = ets:match(Tid, '$1'), 7417 7418 %% Does the name disappear when deleted through the tid()? 7419 true = ets:delete(Tid), 7420 undefined = ets:info(whereis_test), 7421 {'EXIT',{badarg, _}} = (catch ets:match(whereis_test, '$1')), 7422 7423 %% Is the old tid() broken when the table is re-created with the same 7424 %% name? 7425 whereis_test = ets:new(whereis_test, [named_table]), 7426 [] = ets:match(whereis_test, '$1'), 7427 {'EXIT',{badarg, _}} = (catch ets:match(Tid, '$1')), 7428 7429 ok. 7430 7431ms_excessive_nesting(Config) when is_list(Config) -> 7432 MkMSCond = fun (_Fun, N) when N < 0 -> true; 7433 (Fun, N) -> {'orelse', {'==', N, '$1'}, Fun(Fun, N-1)} 7434 end, 7435 %% Ensure it compiles with substantial but reasonable 7436 %% (hmm...) nesting 7437 MS = [{{'$1', '$2'}, [MkMSCond(MkMSCond, 100)], [{{'$1', blipp}}]}], 7438 io:format("~p~n", [erlang:match_spec_test({1, blupp}, MS, table)]), 7439 _ = ets:match_spec_compile(MS), 7440 %% Now test match_spec_compile() and select_replace() 7441 %% with tree and hash using excessive nesting. These 7442 %% used to seg-fault the emulator due to recursion 7443 %% beyond the end of the C-stack. 7444 %% 7445 %% We expect to get a system_limit error, but don't 7446 %% fail if it compiles (someone must have rewritten 7447 %% compilation of match specs to use an explicit 7448 %% stack instead of using recursion). 7449 ENMS = [{{'$1', '$2'}, [MkMSCond(MkMSCond, 1000000)], [{{'$1', blipp}}]}], 7450 io:format("~p~n", [erlang:match_spec_test({1, blupp}, ENMS, table)]), 7451 ENMSC = try 7452 ets:match_spec_compile(ENMS), 7453 "compiled" 7454 catch 7455 error:system_limit -> 7456 "got system_limit" 7457 end, 7458 Tree = ets:new(tree, [ordered_set]), 7459 SRT = try 7460 ets:select_replace(Tree, ENMS), 7461 "compiled" 7462 catch 7463 error:system_limit -> 7464 "got system_limit" 7465 end, 7466 Hash = ets:new(hash, [set]), 7467 SRH = try 7468 ets:select_replace(Hash, ENMS), 7469 "compiled" 7470 catch 7471 error:system_limit -> 7472 "got system_limit" 7473 end, 7474 {comment, "match_spec_compile() "++ENMSC++"; select_replace(_,[ordered_set]) "++SRT++"; select_replace(_,[set]) "++SRH}. 7475 7476%% The following help functions are used by 7477%% throughput_benchmark. They are declared on the top level beacuse 7478%% declaring them as function local funs cause a scalability issue. 7479get_op([{_,O}], _RandNum) -> 7480 O; 7481get_op([{Prob,O}|Rest], RandNum) -> 7482 case RandNum < Prob of 7483 true -> O; 7484 false -> get_op(Rest, RandNum) 7485 end. 7486do_op(Table, ProbHelpTab, Range, Operations) -> 7487 RandNum = rand:uniform(), 7488 Op = get_op(ProbHelpTab, RandNum), 7489 #{ Op := TheOp} = Operations, 7490 TheOp(Table, Range). 7491do_work(WorksDoneSoFar, Table, ProbHelpTab, Range, Operations) -> 7492 receive 7493 stop -> WorksDoneSoFar 7494 after 7495 0 -> do_op(Table, ProbHelpTab, Range, Operations), 7496 do_work(WorksDoneSoFar + 1, Table, ProbHelpTab, Range, Operations) 7497 end. 7498 7499prefill_table(T, KeyRange, Num, ObjFun) -> 7500 Parent = self(), 7501 spawn_link(fun() -> 7502 prefill_table_helper(T, KeyRange, Num, ObjFun), 7503 Parent ! done 7504 end), 7505 receive done -> ok end. 7506 7507prefill_table_helper(T, KeyRange, Num, ObjFun) -> 7508 Seed = rand:uniform(KeyRange), 7509 %%io:format("prefill_table: Seed = ~p\n", [Seed]), 7510 RState = unique_rand_start(KeyRange, Seed), 7511 prefill_table_loop(T, RState, Num, ObjFun). 7512 7513prefill_table_loop(_, _, 0, _) -> 7514 ok; 7515prefill_table_loop(T, RS0, N, ObjFun) -> 7516 {Key, RS1} = unique_rand_next(RS0), 7517 ets:insert(T, ObjFun(Key)), 7518 prefill_table_loop(T, RS1, N-1, ObjFun). 7519 7520inserter_proc_starter(T, ToInsert, Parent) -> 7521 receive 7522 start -> ok 7523 end, 7524 inserter_proc(T, ToInsert, [], Parent, false). 7525 7526inserter_proc(T, [], Inserted, Parent, _) -> 7527 inserter_proc(T, Inserted, [], Parent, true); 7528inserter_proc(T, [I | ToInsert], Inserted, Parent, CanStop) -> 7529 Stop = 7530 case CanStop of 7531 true -> 7532 receive 7533 stop -> Parent ! stopped 7534 after 0 -> no_stop 7535 end; 7536 false -> no_stop 7537 end, 7538 case Stop of 7539 no_stop -> 7540 ets:insert(T, I), 7541 inserter_proc(T, ToInsert, [I | Inserted], Parent, CanStop); 7542 _ -> ok 7543 end. 7544 7545prefill_table_parallel(T, KeyRange, Num, ObjFun) -> 7546 Parent = self(), 7547 spawn_link(fun() -> 7548 prefill_table_parallel_helper(T, KeyRange, Num, ObjFun), 7549 Parent ! done 7550 end), 7551 receive done -> ok end. 7552 7553prefill_table_parallel_helper(T, KeyRange, Num, ObjFun) -> 7554 NrOfSchedulers = erlang:system_info(schedulers), 7555 Seed = rand:uniform(KeyRange), 7556 %%io:format("prefill_table: Seed = ~p\n", [Seed]), 7557 RState = unique_rand_start(KeyRange, Seed), 7558 InsertMap = prefill_insert_map_loop(T, RState, Num, ObjFun, #{}, NrOfSchedulers), 7559 Self = self(), 7560 Pids = [ 7561 begin 7562 InserterFun = 7563 fun() -> 7564 inserter_proc_starter(T, ToInsert, Self) 7565 end, 7566 spawn_link(InserterFun) 7567 end 7568 || ToInsert <- maps:values(InsertMap)], 7569 [Pid ! start || Pid <- Pids], 7570 timer:sleep(1000), 7571 [Pid ! stop || Pid <- Pids], 7572 [receive stopped -> ok end || _Pid <- Pids]. 7573 7574prefill_insert_map_loop(_, _, 0, _, InsertMap, _NrOfSchedulers) -> 7575 InsertMap; 7576prefill_insert_map_loop(T, RS0, N, ObjFun, InsertMap, NrOfSchedulers) -> 7577 {Key, RS1} = unique_rand_next(RS0), 7578 Sched = N rem NrOfSchedulers, 7579 PrevInserts = maps:get(Sched, InsertMap, []), 7580 NewPrevInserts = [ObjFun(Key) | PrevInserts], 7581 NewInsertMap = maps:put(Sched, NewPrevInserts, InsertMap), 7582 prefill_insert_map_loop(T, RS1, N-1, ObjFun, NewInsertMap, NrOfSchedulers). 7583 7584-record(ets_throughput_bench_config, 7585 {benchmark_duration_ms = 3000, 7586 recover_time_ms = 1000, 7587 thread_counts = not_set, 7588 key_ranges = [1000000], 7589 init_functions = [fun prefill_table/4], 7590 nr_of_repeats = 1, 7591 scenarios = 7592 [ 7593 [ 7594 {0.5, insert}, 7595 {0.5, delete} 7596 ], 7597 [ 7598 {0.1, insert}, 7599 {0.1, delete}, 7600 {0.8, lookup} 7601 ], 7602 [ 7603 {0.01, insert}, 7604 {0.01, delete}, 7605 {0.98, lookup} 7606 ], 7607 [ 7608 {1.0, lookup} 7609 ], 7610 [ 7611 {0.1, insert}, 7612 {0.1, delete}, 7613 {0.4, lookup}, 7614 {0.4, nextseq10} 7615 ], 7616 [ 7617 {0.1, insert}, 7618 {0.1, delete}, 7619 {0.4, lookup}, 7620 {0.4, nextseq100} 7621 ], 7622 [ 7623 {0.1, insert}, 7624 {0.1, delete}, 7625 {0.4, lookup}, 7626 {0.4, nextseq1000} 7627 ], 7628 [ 7629 {1.0, nextseq1000} 7630 ], 7631 [ 7632 {0.1, insert}, 7633 {0.1, delete}, 7634 {0.79, lookup}, 7635 {0.01, selectAll} 7636 ], 7637 [ 7638 {0.1, insert}, 7639 {0.1, delete}, 7640 {0.7999, lookup}, 7641 {0.0001, selectAll} 7642 ], 7643 [ 7644 {0.1, insert}, 7645 {0.1, delete}, 7646 {0.799999, lookup}, 7647 {0.000001, selectAll} 7648 ], 7649 [ 7650 {0.1, insert}, 7651 {0.1, delete}, 7652 {0.79, lookup}, 7653 {0.01, partial_select1000} 7654 ], 7655 [ 7656 {0.1, insert}, 7657 {0.1, delete}, 7658 {0.7999, lookup}, 7659 {0.0001, partial_select1000} 7660 ], 7661 [ 7662 {0.1, insert}, 7663 {0.1, delete}, 7664 {0.799999, lookup}, 7665 {0.000001, partial_select1000} 7666 ] 7667 ], 7668 table_types = 7669 [ 7670 [ordered_set, public], 7671 [ordered_set, public, {write_concurrency, true}], 7672 [ordered_set, public, {read_concurrency, true}], 7673 [ordered_set, public, {write_concurrency, true}, {read_concurrency, true}], 7674 [set, public], 7675 [set, public, {write_concurrency, true}], 7676 [set, public, {read_concurrency, true}], 7677 [set, public, {write_concurrency, true}, {read_concurrency, true}] 7678 ], 7679 etsmem_fun = fun() -> ok end, 7680 verify_etsmem_fun = fun(_) -> true end, 7681 notify_res_fun = fun(_Name, _Throughput) -> ok end, 7682 print_result_paths_fun = 7683 fun(ResultPath, _LatestResultPath) -> 7684 Comment = 7685 io_lib:format("<a href=\"file:///~s\">Result visualization</a>",[ResultPath]), 7686 {comment, Comment} 7687 end 7688 }). 7689 7690stdout_notify_res(ResultPath, LatestResultPath) -> 7691 io:format("Result Location: /~s~n", [ResultPath]), 7692 io:format("Latest Result Location: ~s~n", [LatestResultPath]). 7693 7694throughput_benchmark() -> 7695 throughput_benchmark( 7696 #ets_throughput_bench_config{ 7697 print_result_paths_fun = fun stdout_notify_res/2}). 7698 7699throughput_benchmark( 7700 #ets_throughput_bench_config{ 7701 benchmark_duration_ms = BenchmarkDurationMs, 7702 recover_time_ms = RecoverTimeMs, 7703 thread_counts = ThreadCountsOpt, 7704 key_ranges = KeyRanges, 7705 init_functions = InitFuns, 7706 nr_of_repeats = NrOfRepeats, 7707 scenarios = Scenarios, 7708 table_types = TableTypes, 7709 etsmem_fun = ETSMemFun, 7710 verify_etsmem_fun = VerifyETSMemFun, 7711 notify_res_fun = NotifyResFun, 7712 print_result_paths_fun = PrintResultPathsFun}) -> 7713 NrOfSchedulers = erlang:system_info(schedulers), 7714 %% Definitions of operations that are supported by the benchmark 7715 NextSeqOp = 7716 fun (T, KeyRange, SeqSize) -> 7717 Start = rand:uniform(KeyRange), 7718 Last = 7719 lists:foldl( 7720 fun(_, Prev) -> 7721 case Prev of 7722 '$end_of_table'-> ok; 7723 _ -> 7724 try ets:next(T, Prev) of 7725 Normal -> Normal 7726 catch 7727 error:badarg -> 7728 % sets (not ordered_sets) cannot handle when the argument 7729 % to next is not in the set 7730 rand:uniform(KeyRange) 7731 end 7732 end 7733 end, 7734 Start, 7735 lists:seq(1, SeqSize)), 7736 case Last =:= -1 of 7737 true -> io:format("Will never be printed"); 7738 false -> ok 7739 end 7740 end, 7741 PartialSelectOp = 7742 fun (T, KeyRange, SeqSize) -> 7743 Start = rand:uniform(KeyRange), 7744 Last = Start + SeqSize, 7745 case -1 =:= ets:select_count(T, 7746 ets:fun2ms(fun({X}) when X > Start andalso X =< Last -> true end)) of 7747 true -> io:format("Will never be printed"); 7748 false -> ok 7749 end 7750 7751 end, 7752 %% Mapping benchmark operation names to their corresponding functions that do them 7753 Operations = 7754 #{insert => 7755 fun(T,KeyRange) -> 7756 Num = rand:uniform(KeyRange), 7757 ets:insert(T, {Num}) 7758 end, 7759 delete => 7760 fun(T,KeyRange) -> 7761 Num = rand:uniform(KeyRange), 7762 ets:delete(T, Num) 7763 end, 7764 lookup => 7765 fun(T,KeyRange) -> 7766 Num = rand:uniform(KeyRange), 7767 ets:lookup(T, Num) 7768 end, 7769 nextseq10 => 7770 fun(T,KeyRange) -> NextSeqOp(T,KeyRange,10) end, 7771 nextseq100 => 7772 fun(T,KeyRange) -> NextSeqOp(T,KeyRange,100) end, 7773 nextseq1000 => 7774 fun(T,KeyRange) -> NextSeqOp(T,KeyRange,1000) end, 7775 selectAll => 7776 fun(T,_KeyRange) -> 7777 case -1 =:= ets:select_count(T, ets:fun2ms(fun(_X) -> true end)) of 7778 true -> io:format("Will never be printed"); 7779 false -> ok 7780 end 7781 end, 7782 partial_select1000 => 7783 fun(T,KeyRange) -> PartialSelectOp(T,KeyRange,1000) end 7784 }, 7785 %% Helper functions 7786 CalculateThreadCounts = fun Calculate([Count|Rest]) -> 7787 case Count > NrOfSchedulers of 7788 true -> lists:reverse(Rest); 7789 false -> Calculate([Count*2,Count|Rest]) 7790 end 7791 end, 7792 CalculateOpsProbHelpTab = 7793 fun Calculate([{_, OpName}], _) -> 7794 [{1.0, OpName}]; 7795 Calculate([{OpPropability, OpName}|Res], Current) -> 7796 NewCurrent = Current + OpPropability, 7797 [{NewCurrent, OpName}| Calculate(Res, NewCurrent)] 7798 end, 7799 RenderScenario = 7800 fun R([], StringSoFar) -> 7801 StringSoFar; 7802 R([{Fraction, Operation}], StringSoFar) -> 7803 io_lib:format("~s ~f% ~p",[StringSoFar, Fraction * 100.0, Operation]); 7804 R([{Fraction, Operation}|Rest], StringSoFar) -> 7805 R(Rest, 7806 io_lib:format("~s ~f% ~p, ",[StringSoFar, Fraction * 100.0, Operation])) 7807 end, 7808 SafeFixTableIfRequired = 7809 fun(Table, Scenario, On) -> 7810 case set =:= ets:info(Table, type) of 7811 true -> 7812 HasNotRequiringOp = 7813 lists:search( 7814 fun({_,nextseq10}) -> true; 7815 ({_,nextseq100}) -> true; 7816 ({_,nextseq1000}) -> true; 7817 (_) -> false 7818 end, Scenario), 7819 case HasNotRequiringOp of 7820 false -> ok; 7821 _ -> ets:safe_fixtable(Table, On) 7822 end; 7823 false -> ok 7824 end 7825 end, 7826 DataHolder = 7827 fun DataHolderFun(Data)-> 7828 receive 7829 {get_data, Pid} -> Pid ! {ets_bench_data, Data}; 7830 D -> DataHolderFun([Data,D]) 7831 end 7832 end, 7833 DataHolderPid = spawn_link(fun()-> DataHolder([]) end), 7834 PrintData = 7835 fun (Str, List) -> 7836 io:format(Str, List), 7837 DataHolderPid ! io_lib:format(Str, List) 7838 end, 7839 GetData = 7840 fun () -> 7841 DataHolderPid ! {get_data, self()}, 7842 receive {ets_bench_data, Data} -> Data end 7843 end, 7844 %% Function that runs a benchmark instance and returns the number 7845 %% of operations that were performed 7846 RunBenchmark = 7847 fun({NrOfProcs, TableConfig, Scenario, Range, Duration, InitFun}) -> 7848 ProbHelpTab = CalculateOpsProbHelpTab(Scenario, 0), 7849 Table = ets:new(t, TableConfig), 7850 Nobj = Range div 2, 7851 case InitFun of 7852 not_set -> prefill_table(Table, Range, Nobj, fun(K) -> {K} end); 7853 _ -> InitFun(Table, Range, Nobj, fun(K) -> {K} end) 7854 end, 7855 Nobj = ets:info(Table, size), 7856 SafeFixTableIfRequired(Table, Scenario, true), 7857 ParentPid = self(), 7858 Worker = 7859 fun() -> 7860 receive start -> ok end, 7861 WorksDone = 7862 do_work(0, Table, ProbHelpTab, Range, Operations), 7863 ParentPid ! WorksDone 7864 end, 7865 ChildPids = 7866 lists:map(fun(_N) ->spawn_link(Worker)end, lists:seq(1, NrOfProcs)), 7867 erlang:garbage_collect(), 7868 timer:sleep(RecoverTimeMs), 7869 lists:foreach(fun(Pid) -> Pid ! start end, ChildPids), 7870 timer:sleep(Duration), 7871 lists:foreach(fun(Pid) -> Pid ! stop end, ChildPids), 7872 TotalWorksDone = lists:foldl( 7873 fun(_, Sum) -> 7874 receive 7875 Count -> Sum + Count 7876 end 7877 end, 0, ChildPids), 7878 SafeFixTableIfRequired(Table, Scenario, false), 7879 ets:delete(Table), 7880 TotalWorksDone 7881 end, 7882 RunBenchmarkInSepProcess = 7883 fun(ParameterTuple) -> 7884 P = self(), 7885 Results = 7886 [begin 7887 spawn_link(fun()-> P ! {bench_result, RunBenchmark(ParameterTuple)} end), 7888 receive {bench_result, Res} -> Res end 7889 end || _ <- lists:seq(1, NrOfRepeats)], 7890 lists:sum(Results) / NrOfRepeats 7891 end, 7892 RunBenchmarkAndReport = 7893 fun(ThreadCount, 7894 TableType, 7895 Scenario, 7896 KeyRange, 7897 Duration, 7898 InitFunName, 7899 InitFun) -> 7900 Result = RunBenchmarkInSepProcess({ThreadCount, 7901 TableType, 7902 Scenario, 7903 KeyRange, 7904 Duration, 7905 InitFun}), 7906 Throughput = Result/(Duration/1000.0), 7907 PrintData("; ~f",[Throughput]), 7908 Name = io_lib:format("Scenario: ~s, ~w, Key Range Size: ~w, " 7909 "# of Processes: ~w, Table Type: ~w", 7910 [InitFunName, Scenario, KeyRange, ThreadCount, TableType]), 7911 NotifyResFun(Name, Throughput) 7912 end, 7913 ThreadCounts = 7914 case ThreadCountsOpt of 7915 not_set -> 7916 CalculateThreadCounts([1]); 7917 _ -> ThreadCountsOpt 7918 end, 7919 %% Run the benchmark 7920 PrintData("# Each instance of the benchmark runs for ~w seconds:~n", [BenchmarkDurationMs/1000]), 7921 PrintData("# The result of a benchmark instance is presented as a number representing~n",[]), 7922 PrintData("# the number of operations performed per second:~n~n~n",[]), 7923 PrintData("# To plot graphs for the results below:~n",[]), 7924 PrintData("# 1. Open \"$ERL_TOP/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html\" in a web browser~n",[]), 7925 PrintData("# 2. Copy the lines between \"#BENCHMARK STARTED$\" and \"#BENCHMARK ENDED$\" below~n",[]), 7926 PrintData("# 3. Paste the lines copied in step 2 to the text box in the browser window opened in~n",[]), 7927 PrintData("# step 1 and press the Render button~n~n",[]), 7928 PrintData("#BENCHMARK STARTED$~n",[]), 7929 EtsMem = ETSMemFun(), 7930 %% The following loop runs all benchmark scenarios and prints the results (i.e, operations/second) 7931 lists:foreach( 7932 fun(KeyRange) -> 7933 lists:foreach( 7934 fun(Scenario) -> 7935 PrintData("Scenario: ~s | Key Range Size: ~w$~n", 7936 [RenderScenario(Scenario, ""), KeyRange]), 7937 lists:foreach( 7938 fun(ThreadCount) -> 7939 PrintData("; ~w",[ThreadCount]) 7940 end, 7941 ThreadCounts), 7942 PrintData("$~n",[]), 7943 lists:foreach( 7944 fun(TableType) -> 7945 lists:foreach( 7946 fun(InitFunArg) -> 7947 {InitFunName, InitFun} = 7948 case InitFunArg of 7949 {FunName, Fun} -> {FunName, Fun}; 7950 Fun -> {"", Fun} 7951 end, 7952 PrintData("~s,~w ",[InitFunName,TableType]), 7953 lists:foreach( 7954 fun(ThreadCount) -> 7955 RunBenchmarkAndReport(ThreadCount, 7956 TableType, 7957 Scenario, 7958 KeyRange, 7959 BenchmarkDurationMs, 7960 InitFunName, 7961 InitFun) 7962 end, 7963 ThreadCounts), 7964 PrintData("$~n",[]) 7965 end, 7966 InitFuns) 7967 7968 end, 7969 TableTypes) 7970 end, 7971 Scenarios) 7972 end, 7973 KeyRanges), 7974 PrintData("~n#BENCHMARK ENDED$~n~n",[]), 7975 VerifyETSMemFun(EtsMem), 7976 DataDir = filename:join(filename:dirname(code:which(?MODULE)), "ets_SUITE_data"), 7977 TemplatePath = filename:join(DataDir, "visualize_throughput.html"), 7978 {ok, Template} = file:read_file(TemplatePath), 7979 OutputData = string:replace(Template, "#bench_data_placeholder", GetData()), 7980 OutputPath1 = filename:join(DataDir, "ets_bench_result.html"), 7981 {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_datetime(erlang:timestamp()), 7982 StrTime = lists:flatten(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w",[Year,Month,Day,Hour,Minute,Second])), 7983 OutputPath2 = filename:join(DataDir, io_lib:format("ets_bench_result_~s.html", [StrTime])), 7984 file:write_file(OutputPath1, OutputData), 7985 file:write_file(OutputPath2, OutputData), 7986 PrintResultPathsFun(OutputPath2, OutputPath1). 7987 7988test_throughput_benchmark(Config) when is_list(Config) -> 7989 throughput_benchmark( 7990 #ets_throughput_bench_config{ 7991 benchmark_duration_ms = 100, 7992 recover_time_ms = 0, 7993 thread_counts = [1, erlang:system_info(schedulers)], 7994 key_ranges = [50000], 7995 etsmem_fun = fun etsmem/0, 7996 verify_etsmem_fun = fun verify_etsmem/1}). 7997 7998long_throughput_benchmark(Config) when is_list(Config) -> 7999 N = erlang:system_info(schedulers), 8000 throughput_benchmark( 8001 #ets_throughput_bench_config{ 8002 benchmark_duration_ms = 3000, 8003 recover_time_ms = 1000, 8004 thread_counts = [1, N div 2, N], 8005 key_ranges = [1000000], 8006 scenarios = 8007 [ 8008 [ 8009 {0.5, insert}, 8010 {0.5, delete} 8011 ], 8012 [ 8013 {0.1, insert}, 8014 {0.1, delete}, 8015 {0.8, lookup} 8016 ], 8017 [ 8018 {0.01, insert}, 8019 {0.01, delete}, 8020 {0.98, lookup} 8021 ], 8022 [ 8023 {0.1, insert}, 8024 {0.1, delete}, 8025 {0.4, lookup}, 8026 {0.4, nextseq100} 8027 ], 8028 [ 8029 {0.1, insert}, 8030 {0.1, delete}, 8031 {0.79, lookup}, 8032 {0.01, selectAll} 8033 ], 8034 [ 8035 {0.1, insert}, 8036 {0.1, delete}, 8037 {0.79, lookup}, 8038 {0.01, partial_select1000} 8039 ] 8040 ], 8041 table_types = 8042 [ 8043 [ordered_set, public, {write_concurrency, true}, {read_concurrency, true}], 8044 [set, public, {write_concurrency, true}, {read_concurrency, true}] 8045 ], 8046 etsmem_fun = fun etsmem/0, 8047 verify_etsmem_fun = fun verify_etsmem/1, 8048 notify_res_fun = 8049 fun(Name, Throughput) -> 8050 SummaryTable = 8051 proplists:get_value(ets_benchmark_result_summary_tab, Config), 8052 AddToSummaryCounter = 8053 case SummaryTable of 8054 undefined -> 8055 fun(_, _) -> 8056 ok 8057 end; 8058 Tab -> 8059 fun(CounterName, ToAdd) -> 8060 OldVal = ets:lookup_element(Tab, CounterName, 2), 8061 NewVal = OldVal + ToAdd, 8062 ets:insert(Tab, {CounterName, NewVal}) 8063 end 8064 end, 8065 Record = 8066 fun(NoOfBenchsCtr, TotThrputCtr) -> 8067 AddToSummaryCounter(NoOfBenchsCtr, 1), 8068 AddToSummaryCounter(TotThrputCtr, Throughput) 8069 end, 8070 Record(nr_of_benchmarks, total_throughput), 8071 case string:find(Name, "ordered_set") of 8072 nomatch -> 8073 Record(nr_of_set_benchmarks, total_throughput_set); 8074 _ -> 8075 Record(nr_of_ordered_set_benchmarks, 8076 total_throughput_ordered_set) 8077 end, 8078 ct_event:notify( 8079 #event{name = benchmark_data, 8080 data = [{suite,"ets_bench"}, 8081 {name, Name}, 8082 {value,Throughput}]}) 8083 end 8084 }). 8085 8086%% This function compares the lookup operation's performance for 8087%% ordered_set ETS tables with and without write_concurrency enabled 8088%% when the data structures have been populated in parallel and 8089%% sequentially. 8090%% 8091%% The main purpose of this function is to check that the 8092%% implementation of ordered_set with write_concurrency (CA tree) 8093%% adapts its structure to contention even when only lookup operations 8094%% are used. 8095lookup_catree_par_vs_seq_init_benchmark() -> 8096 N = erlang:system_info(schedulers), 8097 throughput_benchmark( 8098 #ets_throughput_bench_config{ 8099 benchmark_duration_ms = 600000, 8100 recover_time_ms = 1000, 8101 thread_counts = [1, N div 2, N], 8102 key_ranges = [1000000], 8103 init_functions = [{"seq_init", fun prefill_table/4}, 8104 {"par_init", fun prefill_table_parallel/4}], 8105 nr_of_repeats = 1, 8106 scenarios = 8107 [ 8108 [ 8109 {1.0, lookup} 8110 ] 8111 ], 8112 table_types = 8113 [ 8114 [ordered_set, public, {write_concurrency, true}], 8115 [ordered_set, public] 8116 ], 8117 print_result_paths_fun = fun stdout_notify_res/2 8118 }). 8119 8120add_lists(L1,L2) -> 8121 add_lists(L1,L2,[]). 8122add_lists([],[],Acc) -> 8123 lists:reverse(Acc); 8124add_lists([E1|T1], [E2|T2], Acc) -> 8125 add_lists(T1, T2, [E1+E2 | Acc]). 8126 8127run_smp_workers(InitF,ExecF,FiniF,Laps) -> 8128 run_smp_workers(InitF,ExecF,FiniF,Laps, 0). 8129run_smp_workers(InitF,ExecF,FiniF,Laps, Exclude) -> 8130 case erlang:system_info(schedulers_online) of 8131 N when N > Exclude -> 8132 run_workers_do(InitF,ExecF,FiniF,Laps, N - Exclude); 8133 _ -> 8134 {skipped, "Too few schedulers online"} 8135 end. 8136 8137run_sched_workers(InitF,ExecF,FiniF,Laps) -> 8138 run_workers_do(InitF,ExecF,FiniF,Laps, 8139 erlang:system_info(schedulers)). 8140 8141run_workers_do(InitF,ExecF,FiniF,Laps, NumOfProcs) -> 8142 io:format("starting ~p workers\n",[NumOfProcs]), 8143 Seeds = [{ProcN,rand:uniform(9999)} || ProcN <- lists:seq(1,NumOfProcs)], 8144 Parent = self(), 8145 Pids = [my_spawn_link(fun()-> worker(Seed,InitF,ExecF,FiniF,Laps,Parent,NumOfProcs) end) 8146 || Seed <- Seeds], 8147 case Laps of 8148 infinite -> Pids; 8149 _ -> wait_pids(Pids) 8150 end. 8151 8152worker({ProcN,Seed}, InitF, ExecF, FiniF, Laps, Parent, NumOfProcs) -> 8153 io:format("smp worker ~p, seed=~p~n",[self(),Seed]), 8154 rand:seed(default, {Seed,Seed,Seed}), 8155 State1 = InitF([ProcN, NumOfProcs]), 8156 State2 = worker_loop(Laps, ExecF, State1), 8157 Result = FiniF(State2), 8158 io:format("worker ~p done\n",[self()]), 8159 Parent ! {self(), Result}. 8160 8161worker_loop(0, _, State) -> 8162 State; 8163worker_loop(_, _, [end_of_work|State]) -> 8164 State; 8165worker_loop(infinite, ExecF, State) -> 8166 worker_loop(infinite,ExecF,ExecF(State)); 8167worker_loop(N, ExecF, State) -> 8168 worker_loop(N-1,ExecF,ExecF(State)). 8169 8170wait_pids(Pids) -> 8171 wait_pids(Pids,[]). 8172wait_pids([],Acc) -> 8173 Acc; 8174wait_pids(Pids, Acc) -> 8175 receive 8176 {Pid,Result} -> 8177 true = lists:member(Pid,Pids), 8178 Others = lists:delete(Pid,Pids), 8179 %%io:format("wait_pid got ~p from ~p\n",[Result,Pid]), 8180 wait_pids(Others,[Result | Acc]) 8181 after 60*1000 -> 8182 io:format("Still waiting for workers ~p\n",[Pids]), 8183 wait_pids(Pids, Acc) 8184 end. 8185 8186 8187 8188 8189my_tab_to_list(Ts) -> 8190 Key = ets:first(Ts), 8191 my_tab_to_list(Ts,ets:next(Ts,Key),[ets:lookup(Ts, Key)]). 8192 8193my_tab_to_list(_Ts,'$end_of_table', Acc) -> lists:reverse(Acc); 8194my_tab_to_list(Ts,Key, Acc) -> 8195 my_tab_to_list(Ts,ets:next(Ts,Key),[ets:lookup(Ts, Key)| Acc]). 8196 8197 8198wait_for_memory_deallocations() -> 8199 try 8200 erts_debug:set_internal_state(wait, thread_progress), 8201 erts_debug:set_internal_state(wait, deallocations) 8202 catch 8203 error:undef -> 8204 erts_debug:set_internal_state(available_internal_state, true), 8205 wait_for_memory_deallocations(); 8206 error:badarg -> 8207 %% The emulator we run on does not have the wait internal state 8208 %% so we just sleep some time instead... 8209 timer:sleep(100) 8210 end. 8211 8212etsmem() -> 8213 etsmem(get_etsmem(), 1). 8214 8215etsmem(PrevEtsMem, Try) when Try < 10 -> 8216 case get_etsmem() of 8217 PrevEtsMem -> 8218 PrevEtsMem; 8219 EtsMem -> 8220 io:format("etsmem(): Change in attempt ~p~n~nbefore:~n~p~n~nafter:~n~p~n~n", 8221 [Try, PrevEtsMem, EtsMem]), 8222 etsmem(EtsMem, Try+1) 8223 end; 8224etsmem(_, _) -> 8225 ct:fail("Failed to get a stable/consistent memory snapshot"). 8226 8227get_etsmem() -> 8228 AllTabsExceptions = [logger, code], 8229 %% The logger table is excluded from the AllTabs list 8230 %% below because it uses decentralized counters to keep 8231 %% track of the size and the memory counters. This cause 8232 %% ets:info(T,size) and ets:info(T,memory) to trigger 8233 %% allocations and frees that may change the amount of 8234 %% memory that is allocated for ETS. 8235 %% 8236 %% The code table is excluded from the list below 8237 %% because the amount of memory allocated for it may 8238 %% change if the tested code loads a new module. 8239 AllTabs = 8240 lists:sort( 8241 [begin 8242 try ets:info(T, decentralized_counters) of 8243 true -> 8244 ct:fail("Background ETS table (~p) that " 8245 "uses decentralized counters (Add exception?)", 8246 [ets:info(T,name)]); 8247 _ -> ok 8248 catch _:_ -> 8249 ok 8250 end, 8251 {T, 8252 ets:info(T,name), 8253 ets:info(T,size), 8254 ets:info(T,memory), 8255 ets:info(T,type)} 8256 end 8257 || T <- ets:all(), 8258 not lists:member(ets:info(T, name), AllTabsExceptions)]), 8259 wait_for_memory_deallocations(), 8260 EtsAllocSize = erts_debug:alloc_blocks_size(ets_alloc), 8261 ErlangMemoryEts = try erlang:memory(ets) 8262 catch error:notsup -> notsup end, 8263 FlxCtrMemUsage = try erts_debug:get_internal_state(flxctr_memory_usage) 8264 catch error:badarg -> notsup end, 8265 Mem = {ErlangMemoryEts, EtsAllocSize, FlxCtrMemUsage}, 8266 {Mem, AllTabs}. 8267 8268verify_etsmem(MI) -> 8269 wait_for_test_procs(), 8270 verify_etsmem(MI, 1). 8271 8272verify_etsmem({MemInfo,AllTabs}, Try) -> 8273 case etsmem() of 8274 {MemInfo,_} -> 8275 io:format("Ets mem info: ~p", [MemInfo]), 8276 case {MemInfo, Try} of 8277 {{ErlMem,EtsAlloc},_} when ErlMem == notsup; EtsAlloc == undefined -> 8278 %% Use 'erl +Mea max' to do more complete memory leak testing. 8279 {comment,"Incomplete or no mem leak testing"}; 8280 {_, 1} -> 8281 ok; 8282 _ -> 8283 {comment, "Transient memory discrepancy"} 8284 end; 8285 8286 {MemInfo2, AllTabs2} -> 8287 io:format("#Expected: ~p", [MemInfo]), 8288 io:format("#Actual: ~p", [MemInfo2]), 8289 io:format("#Changed tables before: ~p\n",[AllTabs -- AllTabs2]), 8290 io:format("#Changed tables after: ~p\n", [AllTabs2 -- AllTabs]), 8291 case Try < 2 of 8292 true -> 8293 io:format("\n#This discrepancy could be caused by an " 8294 "inconsistent memory \"snapshot\"" 8295 "\n#Try again...\n", []), 8296 verify_etsmem({MemInfo, AllTabs}, Try+1); 8297 false -> 8298 ct:fail("Failed memory check") 8299 end 8300 end. 8301 8302 8303start_loopers(N, Prio, Fun, State) -> 8304 lists:map(fun (_) -> 8305 my_spawn_opt(fun () -> looper(Fun, State) end, 8306 [{priority, Prio}, link]) 8307 end, 8308 lists:seq(1, N)). 8309 8310stop_loopers(Loopers) -> 8311 lists:foreach(fun (P) -> 8312 unlink(P), 8313 exit(P, bang) 8314 end, 8315 Loopers), 8316 ok. 8317 8318looper(Fun, State) -> 8319 looper(Fun, Fun(State)). 8320 8321spawn_logger(Procs) -> 8322 receive 8323 {new_test_proc, Proc} -> 8324 spawn_logger([Proc|Procs]); 8325 {sync_test_procs, Kill, From} -> 8326 lists:foreach(fun (Proc) when From == Proc -> 8327 ok; 8328 (Proc) -> 8329 Mon = erlang:monitor(process, Proc), 8330 ok = receive 8331 {'DOWN', Mon, _, _, _} -> 8332 ok 8333 after 0 -> 8334 case Kill of 8335 true -> exit(Proc, kill); 8336 _ -> ok 8337 end, 8338 receive 8339 {'DOWN', Mon, _, _, _} -> 8340 ok 8341 after 5000 -> 8342 io:format("Waiting for 'DOWN' from ~w, status=~w\n" 8343 "info = ~p\n", [Proc, 8344 pid_status(Proc), 8345 process_info(Proc)]), 8346 timeout 8347 end 8348 end 8349 end, Procs), 8350 From ! test_procs_synced, 8351 spawn_logger([From]) 8352 end. 8353 8354pid_status(Pid) -> 8355 try 8356 erts_debug:get_internal_state({process_status, Pid}) 8357 catch 8358 error:undef -> 8359 erts_debug:set_internal_state(available_internal_state, true), 8360 pid_status(Pid) 8361 end. 8362 8363start_spawn_logger() -> 8364 case whereis(ets_test_spawn_logger) of 8365 Pid when is_pid(Pid) -> true; 8366 _ -> register(ets_test_spawn_logger, 8367 spawn_opt(fun () -> spawn_logger([]) end, 8368 [{priority, max}])) 8369 end. 8370 8371%% restart_spawn_logger() -> 8372%% stop_spawn_logger(), 8373%% start_spawn_logger(). 8374 8375stop_spawn_logger() -> 8376 Mon = erlang:monitor(process, ets_test_spawn_logger), 8377 (catch exit(whereis(ets_test_spawn_logger), kill)), 8378 receive {'DOWN', Mon, _, _, _} -> ok end. 8379 8380wait_for_test_procs() -> 8381 wait_for_test_procs(false). 8382 8383wait_for_test_procs(Kill) -> 8384 ets_test_spawn_logger ! {sync_test_procs, Kill, self()}, 8385 receive test_procs_synced -> ok end. 8386 8387log_test_proc(Proc) when is_pid(Proc) -> 8388 ets_test_spawn_logger ! {new_test_proc, Proc}, 8389 Proc. 8390 8391my_spawn(Fun) -> log_test_proc(spawn(Fun)). 8392 8393my_spawn_link(Fun) -> log_test_proc(spawn_link(Fun)). 8394 8395my_spawn_opt(Fun,Opts) -> 8396 case spawn_opt(Fun,Opts) of 8397 Pid when is_pid(Pid) -> log_test_proc(Pid); 8398 {Pid, _} = Res when is_pid(Pid) -> log_test_proc(Pid), Res 8399 end. 8400 8401my_spawn_monitor(Fun) -> 8402 Res = spawn_monitor(Fun), 8403 {Pid, _} = Res, 8404 log_test_proc(Pid), 8405 Res. 8406 8407repeat(_Fun, 0) -> 8408 ok; 8409repeat(Fun, N) -> 8410 Fun(), 8411 repeat(Fun, N-1). 8412 8413repeat_while(Fun) -> 8414 case Fun() of 8415 true -> repeat_while(Fun); 8416 false -> false 8417 end. 8418 8419repeat_while(Fun, Arg0) -> 8420 case Fun(Arg0) of 8421 {true,Arg1} -> repeat_while(Fun,Arg1); 8422 {false,Ret} -> Ret 8423 end. 8424 8425%% Some (but not all) permutations of List 8426repeat_for_permutations(Fun, List) -> 8427 repeat_for_permutations(Fun, List, length(List)-1). 8428repeat_for_permutations(Fun, List, 0) -> 8429 Fun(List); 8430repeat_for_permutations(Fun, List, N) -> 8431 {A,B} = lists:split(N, List), 8432 L1 = B++A, 8433 L2 = lists:reverse(L1), 8434 L3 = B++lists:reverse(A), 8435 L4 = lists:reverse(B)++A, 8436 Fun(L1), Fun(L2), Fun(L3), Fun(L4), 8437 repeat_for_permutations(Fun, List, N-1). 8438 8439receive_any() -> 8440 receive M -> 8441 io:format("Process ~p got msg ~p\n", [self(),M]), 8442 M 8443 end. 8444 8445receive_any_spinning() -> 8446 receive_any_spinning(1000000). 8447receive_any_spinning(Loops) -> 8448 receive_any_spinning(Loops,Loops,1). 8449receive_any_spinning(Loops,0,Tries) -> 8450 receive M -> 8451 io:format("Spinning process ~p got msg ~p after ~p tries\n", [self(),M,Tries]), 8452 M 8453 after 0 -> 8454 receive_any_spinning(Loops, Loops, Tries+1) 8455 end; 8456receive_any_spinning(Loops, N, Tries) when N>0 -> 8457 receive_any_spinning(Loops, N-1, Tries). 8458 8459 8460 8461spawn_monitor_with_pid(Pid, Fun) when is_pid(Pid) -> 8462 spawn_monitor_with_pid(Pid, Fun, 10). 8463 8464spawn_monitor_with_pid(_, _, 0) -> 8465 failed; 8466spawn_monitor_with_pid(Pid, Fun, N) -> 8467 case my_spawn(fun()-> case self() of 8468 Pid -> Fun(); 8469 _ -> die 8470 end 8471 end) of 8472 Pid -> 8473 {Pid, erlang:monitor(process, Pid)}; 8474 _Other -> 8475 spawn_monitor_with_pid(Pid,Fun,N-1) 8476 end. 8477 8478 8479only_if_smp(Func) -> 8480 only_if_smp(2, Func). 8481only_if_smp(Schedulers, Func) -> 8482 case erlang:system_info(schedulers_online) of 8483 N when N < Schedulers -> {skip,"Too few schedulers online"}; 8484 _ -> Func() 8485 end. 8486 8487%% Copy-paste from emulator/test/binary_SUITE.erl 8488test_terms(Test_Func, Mode) -> 8489 garbage_collect(), 8490 Pib0 = process_info(self(),binary), 8491 8492 Test_Func(atom), 8493 Test_Func(''), 8494 Test_Func('a'), 8495 Test_Func('ab'), 8496 Test_Func('abc'), 8497 Test_Func('abcd'), 8498 Test_Func('abcde'), 8499 Test_Func('abcdef'), 8500 Test_Func('abcdefg'), 8501 Test_Func('abcdefgh'), 8502 8503 Test_Func(fun() -> ok end), 8504 X = id([a,{b,c},c]), 8505 Y = id({x,y,z}), 8506 Z = id(1 bsl 8*257), 8507 Test_Func(fun() -> X end), 8508 Test_Func(fun() -> {X,Y} end), 8509 Test_Func([fun() -> {X,Y,Z} end, 8510 fun() -> {Z,X,Y} end, 8511 fun() -> {Y,Z,X} end]), 8512 8513 Test_Func({trace_ts,{even_bigger,{some_data,fun() -> ok end}},{1,2,3}}), 8514 Test_Func({trace_ts,{even_bigger,{some_data,<<1,2,3,4,5,6,7,8,9,10>>}}, 8515 {1,2,3}}), 8516 8517 Test_Func(1), 8518 Test_Func(42), 8519 Test_Func(-23), 8520 Test_Func(256), 8521 Test_Func(25555), 8522 Test_Func(-3333), 8523 8524 Test_Func(1.0), 8525 8526 Test_Func(183749783987483978498378478393874), 8527 Test_Func(-37894183749783987483978498378478393874), 8528 Very_Big = very_big_num(), 8529 Test_Func(Very_Big), 8530 Test_Func(-Very_Big+1), 8531 8532 Test_Func([]), 8533 Test_Func("abcdef"), 8534 Test_Func([a, b, 1, 2]), 8535 Test_Func([a|b]), 8536 8537 Test_Func({}), 8538 Test_Func({1}), 8539 Test_Func({a, b}), 8540 Test_Func({a, b, c}), 8541 Test_Func(list_to_tuple(lists:seq(0, 255))), 8542 Test_Func(list_to_tuple(lists:seq(0, 256))), 8543 8544 Test_Func(make_ref()), 8545 Test_Func([make_ref(), make_ref()]), 8546 8547 Test_Func(make_port()), 8548 8549 Test_Func(make_pid()), 8550 Test_Func(make_ext_pid()), 8551 Test_Func(make_ext_port()), 8552 Test_Func(make_ext_ref()), 8553 8554 Bin0 = list_to_binary(lists:seq(0, 14)), 8555 Test_Func(Bin0), 8556 Bin1 = list_to_binary(lists:seq(0, ?heap_binary_size)), 8557 Test_Func(Bin1), 8558 Bin2 = list_to_binary(lists:seq(0, ?heap_binary_size+1)), 8559 Test_Func(Bin2), 8560 Bin3 = list_to_binary(lists:seq(0, 255)), 8561 garbage_collect(), 8562 Pib = process_info(self(),binary), 8563 Test_Func(Bin3), 8564 garbage_collect(), 8565 case Mode of 8566 strict -> Pib = process_info(self(),binary); 8567 skip_refc_check -> ok 8568 end, 8569 8570 Test_Func(make_unaligned_sub_binary(Bin0)), 8571 Test_Func(make_unaligned_sub_binary(Bin1)), 8572 Test_Func(make_unaligned_sub_binary(Bin2)), 8573 Test_Func(make_unaligned_sub_binary(Bin3)), 8574 8575 Test_Func(make_sub_binary(lists:seq(42, 43))), 8576 Test_Func(make_sub_binary([42,43,44])), 8577 Test_Func(make_sub_binary([42,43,44,45])), 8578 Test_Func(make_sub_binary([42,43,44,45,46])), 8579 Test_Func(make_sub_binary([42,43,44,45,46,47])), 8580 Test_Func(make_sub_binary([42,43,44,45,46,47,48])), 8581 Test_Func(make_sub_binary(lists:seq(42, 49))), 8582 Test_Func(make_sub_binary(lists:seq(0, 14))), 8583 Test_Func(make_sub_binary(lists:seq(0, ?heap_binary_size))), 8584 Test_Func(make_sub_binary(lists:seq(0, ?heap_binary_size+1))), 8585 Test_Func(make_sub_binary(lists:seq(0, 255))), 8586 8587 Test_Func(make_unaligned_sub_binary(lists:seq(42, 43))), 8588 Test_Func(make_unaligned_sub_binary([42,43,44])), 8589 Test_Func(make_unaligned_sub_binary([42,43,44,45])), 8590 Test_Func(make_unaligned_sub_binary([42,43,44,45,46])), 8591 Test_Func(make_unaligned_sub_binary([42,43,44,45,46,47])), 8592 Test_Func(make_unaligned_sub_binary([42,43,44,45,46,47,48])), 8593 Test_Func(make_unaligned_sub_binary(lists:seq(42, 49))), 8594 Test_Func(make_unaligned_sub_binary(lists:seq(0, 14))), 8595 Test_Func(make_unaligned_sub_binary(lists:seq(0, ?heap_binary_size))), 8596 Test_Func(make_unaligned_sub_binary(lists:seq(0, ?heap_binary_size+1))), 8597 Test_Func(make_unaligned_sub_binary(lists:seq(0, 255))), 8598 8599 %% Bit level binaries. 8600 Test_Func(<<1:1>>), 8601 Test_Func(<<2:2>>), 8602 Test_Func(<<42:10>>), 8603 Test_Func(list_to_bitstring([<<5:6>>|lists:seq(0, 255)])), 8604 8605 Test_Func(F = fun(A) -> 42*A end), 8606 Test_Func(lists:duplicate(32, F)), 8607 8608 Test_Func(FF = fun binary_SUITE:all/1), 8609 Test_Func(lists:duplicate(32, FF)), 8610 8611 garbage_collect(), 8612 case Mode of 8613 strict -> Pib0 = process_info(self(),binary); 8614 skip_refc_check -> ok 8615 end, 8616 ok. 8617 8618error_info(_Config) -> 8619 Ms = [{{'$1','$2','$3'},[],['$$']}], 8620 BagTab = fun(_Type) -> ets:new(table, [set,bag,private]) end, 8621 OneKeyTab = fun(Type) -> 8622 T = ets:new(table, [Type, private]), 8623 true = ets:insert(T, {one,two,3}), 8624 T 8625 end, 8626 Set = fun(_Type) -> ets:new(table, [set, private]) end, 8627 OrderedSet = fun(_Type) -> ets:new(table, [ordered_set, private]) end, 8628 NamedTable = fun(Type) -> ets:new('$named_table', [Type, named_table, private]) end, 8629 UnownedTable = fun(Type) -> 8630 Parent = self(), 8631 spawn_link(fun() -> 8632 T = ets:new(table, [Type, public]), 8633 Parent ! T, 8634 receive ok -> ok end 8635 end), 8636 receive T -> T end 8637 end, 8638 8639 L = [{delete, ['$Tab']}, 8640 {delete, ['$Tab', no_key], [no_fail]}, 8641 {delete_all_objects, ['$Tab'], [renamed]}, 8642 {delete_object, ['$Tab', bad_object]}, 8643 {delete_object, ['$Tab', {tag,non_existing}], [no_fail]}, 8644 8645 {file2tab, 1}, %Not BIF. 8646 {file2tab, 2}, %Not BIF. 8647 8648 {first, ['$Tab']}, 8649 8650 {foldl, 3}, %Not BIF. 8651 {foldr, 3}, %Not BIF. 8652 8653 {from_dets, 2}, %Not BIF. 8654 8655 {fun2ms, 1}, %Not BIF. 8656 8657 {give_away, ['$Tab', not_a_pid, bad_pid]}, 8658 {give_away, ['$Tab', '$Self', already_owner], [{error_term,owner}]}, 8659 {give_away, ['$Tab', '$Living', living_process], [only_bad_table]}, 8660 {give_away, ['$Tab', '$Dead', dead_process]}, 8661 8662 {give_away, [UnownedTable, '$Living', gift_data], [{error_term,not_owner}]}, 8663 8664 {i, 1}, %Not BIF. 8665 {i, 2}, %Not BIF. 8666 {i, 3}, %Not BIF. 8667 8668 {info, ['$Tab']}, 8669 {info, ['$Tab', invalid_item]}, 8670 8671 {init_table, 2}, %Not BIF. 8672 8673 {insert, ['$Tab', bad_object]}, 8674 {insert, ['$Tab', {}]}, 8675 {insert, ['$Tab', [a,{a,b,c}]]}, 8676 {insert, ['$Tab', [a|b]]}, 8677 {insert, ['$Tab', {a,b,c}], [no_fail]}, 8678 {insert, ['$Tab', [{a,b,c}]], [no_fail]}, 8679 8680 {insert_new, ['$Tab', bad_object]}, 8681 {insert_new, ['$Tab', {a,b,c}], [no_fail]}, 8682 {insert_new, ['$Tab', [a,{a,b,c}]]}, 8683 {insert_new, ['$Tab', [a|b]]}, 8684 8685 {internal_delete_all, 2}, %Internal function. 8686 {internal_select_delete, 2}, %Internal function. 8687 8688 {is_compiled_ms, [bad_ms], [no_fail, no_table]}, 8689 8690 {last, ['$Tab']}, 8691 8692 {lookup, ['$Tab', no_key], [no_fail]}, 8693 8694 {lookup_element, ['$Tab', no_key, 0]}, 8695 {lookup_element, ['$Tab', no_key, 1], [{error_term,badkey}]}, 8696 {lookup_element, ['$Tab', no_key, bad_pos]}, 8697 8698 {lookup_element, [OneKeyTab, one, 4]}, 8699 8700 {match, [bad_continuation], [no_table]}, 8701 8702 {match, ['$Tab', <<1,2,3>>], [no_fail]}, 8703 {match, ['$Tab', <<1,2,3>>, 0]}, 8704 {match, ['$Tab', <<1,2,3>>, bad_limit]}, 8705 {match_delete, ['$Tab', <<1,2,3>>], [no_fail,renamed]}, 8706 8707 {match_object, [bad_continuation], [no_table]}, 8708 8709 {match_object, ['$Tab', <<1,2,3>>], [no_fail]}, 8710 {match_object, ['$Tab', <<1,2,3>>, bad_limit]}, 8711 8712 {match_spec_compile, [bad_match_spec], [no_table]}, 8713 {match_spec_run, 2}, %Not BIF. 8714 {match_spec_run_r, 3}, %Internal BIF. 8715 8716 {member, ['$Tab', no_key], [no_fail]}, 8717 8718 {new, [name, not_list], [no_table]}, 8719 {new, [name, [a|b]], [no_table]}, 8720 {new, [name, [a,b]], [no_table]}, 8721 {new, [{bad,name}, [a,b]], [no_table]}, 8722 8723 %% For a set, ets:next/2 and ets:prev/2 fails if the key does 8724 %% not exist. 8725 {next, [Set, no_key]}, 8726 {prev, [Set, no_key]}, 8727 8728 %% For an ordered set, ets:next/2 and ets:prev/2 succeeds 8729 %% even if the key does not exist. 8730 {next, [OrderedSet, no_key], [no_fail]}, 8731 {prev, [OrderedSet, no_key], [no_fail]}, 8732 8733 {rename, ['$Tab', {bad,name}]}, 8734 {rename, [NamedTable, '$named_table']}, 8735 {rename, [NamedTable, {bad,name}]}, 8736 8737 {repair_continuation, 2}, %Not BIF. 8738 8739 {safe_fixtable, ['$Tab', true], [no_fail]}, 8740 {safe_fixtable, ['$Tab', not_boolean]}, 8741 8742 {select, [bad_continuation], [no_table]}, 8743 8744 {select, ['$Tab', Ms], [no_fail]}, 8745 {select, ['$Tab', bad_match_spec]}, 8746 {select, ['$Tab', Ms, bad_limit]}, 8747 {select, ['$Tab', Ms, 0]}, 8748 {select, ['$Tab', bad_match_spec, bad_limit]}, 8749 {select, ['$Tab', bad_match_spec, 1]}, 8750 8751 {select_count, ['$Tab', Ms], [no_fail]}, 8752 {select_count, ['$Tab', bad_match_spec]}, 8753 8754 {select_delete, ['$Tab', Ms], [no_fail,renamed]}, 8755 {select_delete, ['$Tab', bad_match_spec], [renamed]}, 8756 8757 {select_replace, ['$Tab', [{{'$1','$2','$3'},[],[{{'$1','$3','$2'}}]}]], [no_fail]}, 8758 {select_replace, ['$Tab', [{{'$1','$2','$3'},[],[{{'key_destroyed'}}]}]]}, 8759 {select_replace, ['$Tab', bad_match_spec]}, 8760 8761 {select_replace, [BagTab, [{{'$1','$2','$3'},[],[{{'$1','$3','$2'}}]}]], [{error_term,table_type}]}, 8762 8763 {select_reverse, [bad_continuation], [no_table]}, 8764 8765 {select_reverse, ['$Tab', Ms], [no_fail]}, 8766 {select_reverse, ['$Tab', bad_match_spec]}, 8767 8768 {select_reverse, ['$Tab', Ms, 0]}, 8769 {select_reverse, ['$Tab', Ms, bad_limit]}, 8770 {select_reverse, ['$Tab', bad_match_spec, bad_limit]}, 8771 8772 {setopts, ['$Tab', bad_opts]}, 8773 8774 {slot, ['$Tab', -1]}, 8775 {slot, ['$Tab', not_an_integer]}, 8776 8777 {tab2file, 2}, %Not BIF. 8778 {tab2file, 3}, %Not BIF. 8779 {tab2list, 1}, %Not BIF. 8780 {tabfile_info, 1}, %Not BIF. 8781 {table, 1}, %Not BIF. 8782 {table, 2}, %Not BIF. 8783 8784 {take, ['$Tab', no_key], [no_fail]}, 8785 8786 {test_ms, 2}, %Not BIF. 8787 {to_dets, 2}, %Not BIF. 8788 8789 {update_counter, ['$Tab', no_key, 1], [{error_term,badkey}]}, 8790 {update_counter, ['$Tab', no_key, bad_increment], [{error_term,badkey}]}, 8791 {update_counter, ['$Tab', no_key, {1, 42}], [{error_term,badkey}]}, 8792 {update_counter, ['$Tab', no_key, {1, bad_increment}], [{error_term,badkey}]}, 8793 8794 {update_counter, [OneKeyTab, one, {2, 1}]}, 8795 {update_counter, [OneKeyTab, one, {2, bad_increment}]}, 8796 {update_counter, [OneKeyTab, one, {3, bad_increment}]}, 8797 {update_counter, [OneKeyTab, one, {4, 1}], [{error_term,position}]}, 8798 {update_counter, [OneKeyTab, one, {4, bad_increment}]}, 8799 8800 {update_counter, [BagTab, bag_key, 1], [{error_term,table_type}]}, 8801 {update_counter, [BagTab, bag_key, bad_increment], [{error_term,table_type}]}, 8802 8803 {update_counter, ['$Tab', key, 2, {key,0}], [no_fail]}, 8804 {update_counter, ['$Tab', key, {1,42}, {key,0}], [{error_term,keypos}]}, 8805 {update_counter, ['$Tab', key, 2, {key,not_integer}]}, 8806 {update_counter, ['$Tab', key, 3, {key,whatever}]}, 8807 8808 {update_counter, ['$Tab', no_key, 1, default]}, 8809 {update_counter, ['$Tab', no_key, bad_increment, {tag,0}]}, 8810 {update_counter, ['$Tab', no_key, {1, bad_increment}, {tag,0}]}, 8811 {update_counter, ['$Tab', no_key, {1, 42}, {tag,0}], [{error_term,keypos}]}, 8812 {update_counter, ['$Tab', no_key, {2, 42}, {tag,not_integer}]}, 8813 {update_counter, ['$Tab', no_key, {3, 42}, {tag,not_integer}], [{error_term,position}]}, 8814 8815 {update_counter, [OneKeyTab, one, {2, 1}, {tag,val}]}, 8816 {update_counter, [OneKeyTab, one, {2, bad_increment}, {tag,val}]}, 8817 {update_counter, [OneKeyTab, one, {3, bad_increment}, {tag,val}]}, 8818 {update_counter, [OneKeyTab, one, {4, 1}, {tag,val}], [{error_term,position}]}, 8819 {update_counter, [OneKeyTab, one, {4, bad_increment}, {tag,val}]}, 8820 8821 {update_element, ['$Tab', no_key, {2, new}], [no_fail]}, 8822 {update_element, [BagTab, no_key, {2, bagged}]}, 8823 {update_element, [OneKeyTab, one, not_tuple]}, 8824 {update_element, [OneKeyTab, one, {0, new}]}, 8825 {update_element, [OneKeyTab, one, {1, new}], [{error_term,keypos}]}, 8826 {update_element, [OneKeyTab, one, {4, new}]}, 8827 8828 {whereis, [{bad,name}], [no_table]} 8829 ], 8830 put(errors, []), 8831 eval_ets_bif_errors(L), 8832 io:nl(), 8833 case lists:sort(get(errors)) of 8834 [] -> 8835 ok; 8836 [_|_]=Errors -> 8837 io:format("~p\n", [Errors]), 8838 ct:fail({length(Errors),errors}) 8839 end. 8840 8841eval_ets_bif_errors(L0) -> 8842 L1 = lists:foldl(fun({_,A}, Acc) when is_integer(A) -> Acc; 8843 ({F,A}, Acc) -> [{F,A,[]}|Acc]; 8844 ({F,A,Opts}, Acc) -> [{F,A,Opts}|Acc] 8845 end, [], L0), 8846 Tests = ordsets:from_list([{F,length(A)} || {F,A,_} <- L1] ++ 8847 [{F,A} || {F,A} <- L0, is_integer(A)]), 8848 Bifs0 = [{F,A} || {F,A} <- ets:module_info(exports), 8849 A =/= 0, 8850 F =/= module_info], 8851 Bifs = ordsets:from_list(Bifs0), 8852 NYI = [{F,lists:duplicate(A, '*'),nyi} || {F,A} <- Bifs -- Tests], 8853 L = lists:sort(NYI ++ L1), 8854 8855 spawn(fun() -> 8856 true = register(living, self()), 8857 Ref = make_ref(), 8858 receive 8859 Ref -> 8860 ok 8861 end 8862 end), 8863 8864 do_eval_ets_bif_errors(L). 8865 8866do_eval_ets_bif_errors([H|T]) -> 8867 case H of 8868 {F, Args} -> 8869 eval_ets_bif_errors(F, Args, []); 8870 {_, Args, nyi} -> 8871 case lists:all(fun(A) -> A =:= '*' end, Args) of 8872 true -> 8873 store_error(nyi, H, error); 8874 false -> 8875 store_error(bad_nyi, H, error) 8876 end; 8877 {F, Args, Opts} when is_list(Opts) -> 8878 case lists:member(no_table, Opts) of 8879 true -> 8880 ets_eval_bif_errors_once(F, Args, Opts); 8881 false -> 8882 eval_ets_bif_errors(F, Args, Opts) 8883 end 8884 end, 8885 do_eval_ets_bif_errors(T); 8886do_eval_ets_bif_errors([]) -> 8887 ok. 8888 8889ets_eval_bif_errors_once(F, Args, Opts) -> 8890 MFA = {ets,F,Args}, 8891 io:format("\n\n*** ets:~p/~p", [F,length(Args)]), 8892 8893 NoFail = lists:member(no_fail, Opts), 8894 case ets_apply(F, Args, Opts) of 8895 {error,none} -> 8896 ok; 8897 {error,Info} -> 8898 store_error(wrong_failure_reason, MFA, Info); 8899 ok when NoFail -> 8900 ok; 8901 ok when not NoFail -> 8902 %% This ETS function was supposed to fail. 8903 store_error(expected_failure, MFA, ok) 8904 end. 8905 8906eval_ets_bif_errors(F, Args0, Opts) -> 8907 MFA = {ets,F,Args0}, 8908 io:format("\n\n*** ets:~p/~p", [F,length(Args0)]), 8909 8910 %% Test the ETS function with a valid table argument. 8911 %% Test both for sets and ordered sets. 8912 _ = eval_ets_valid_tid(F, Args0, Opts, set), 8913 Args = eval_ets_valid_tid(F, Args0, Opts, ordered_set), 8914 8915 %% Replace the table id with a plain ref to provoke a type error. 8916 BadArgs = eval_expand_bad_args(Args), 8917 case ets_apply(F, BadArgs, Opts) of 8918 {error,type} -> 8919 ok; 8920 BadIdResult -> 8921 store_error(bad_table_id, MFA, BadIdResult) 8922 end. 8923 8924eval_ets_valid_tid(F, Args0, Opts, Type) -> 8925 MFA = {ets,F,Args0}, 8926 Args = eval_expand_args(Args0, Type), 8927 case should_apply(Args, Opts) of 8928 false -> 8929 %% Applying this function will never fail. 8930 ok; 8931 true -> 8932 NoFail = lists:member(no_fail, Opts), 8933 ErrorTerm = proplists:get_value(error_term, Opts, none), 8934 case ets_apply(F, Args, Opts) of 8935 {error,ErrorTerm} when not NoFail -> 8936 ok; 8937 {error,Info} when not NoFail -> 8938 store_error(wrong_failure_reason, MFA, Info); 8939 {error,Info} when NoFail -> 8940 store_error(expected_success, MFA, Info); 8941 ok when NoFail -> 8942 ok; 8943 ok when not NoFail -> 8944 %% This ETS function was supposed to fail. 8945 store_error(expected_failure, MFA, ok) 8946 end 8947 end, 8948 8949 %% Test the ETS function from another process to provoke an error 8950 %% because of missing access rights. (The table is private.) 8951 {Pid,Ref} = spawn_monitor(fun() -> exit(ets_apply(F, Args, Opts)) end), 8952 receive 8953 {'DOWN',Ref,process,Pid,Result} -> 8954 case Result of 8955 {error,access} -> 8956 ok; 8957 {error,not_owner} when F =:= give_away -> 8958 ok; 8959 {error,none} when F =:= info -> 8960 ok; 8961 ok when F =:= info -> 8962 ok; 8963 Other -> 8964 store_error(access, MFA, Other) 8965 end 8966 end, 8967 8968 %% Delete the ETS table. 8969 eval_delete_tab(Args), 8970 case ets_apply(F, Args, Opts) of 8971 {error,id} -> 8972 ok; 8973 ok when F =:= info -> 8974 %% ets:info/1,2 returns `undefined` instead of failing if the 8975 %% table has been deleted. 8976 ok; 8977 DeadTableResult -> 8978 store_error(dead_table, MFA, DeadTableResult) 8979 end, 8980 8981 Args. 8982 8983should_apply([_], _Opts) -> 8984 %% An ETS function with a single argument can't fail if 8985 %% the argument is valid. 8986 false; 8987should_apply([_,_|_], Opts) -> 8988 %% Applying the function on a valid table would have side effects 8989 %% that would cause problems down the line (e.g. successfully 8990 %% giving away a table). 8991 not lists:member(only_bad_table, Opts). 8992 8993store_error(What, MFA, Wrong) -> 8994 put(errors, [{What,MFA,Wrong}|get(errors)]). 8995 8996eval_expand_args(Args, Type) -> 8997 [expand_arg(A, Type) || A <- Args]. 8998 8999expand_arg('$Tab', Type) -> ets:new(table, [Type, private]); 9000expand_arg('$Self', _Type) -> self(); 9001expand_arg('$Living', _Type) -> whereis(living); 9002expand_arg('$Dead', _Type) -> 9003 {Pid,Ref} = spawn_monitor(fun() -> ok end), 9004 receive 9005 {'DOWN',Ref,process,Pid,normal} -> Pid 9006 end; 9007expand_arg(Fun, Type) when is_function(Fun, 1) -> Fun(Type); 9008expand_arg(Arg, _Type) -> Arg. 9009 9010eval_delete_tab(['$named_table'=H|_]) -> 9011 ets:delete(H); 9012eval_delete_tab([H|_]) when is_reference(H) -> 9013 ets:delete(H); 9014eval_delete_tab([_|T]) -> 9015 eval_delete_tab(T). 9016 9017eval_expand_bad_args(['$named_table'|T]) -> 9018 [make_ref()|T]; 9019eval_expand_bad_args([H|T]) when is_reference(H) -> 9020 [make_ref()|T]; 9021eval_expand_bad_args([H|T]) -> 9022 [H|eval_expand_bad_args(T)]. 9023 9024ets_apply(F, Args, Opts) -> 9025 try 9026 apply(ets, F, Args), 9027 io:format("\nets:~p(~s) succeeded", [F,ets_format_args(Args)]) 9028 catch 9029 C:R:Stk -> 9030 SF = fun(Mod, _, _) -> Mod =:= test_server end, 9031 Str = erl_error:format_exception(C, R, Stk, #{stack_trim_fun => SF}), 9032 BinStr = iolist_to_binary(Str), 9033 io:format("\nets:~p(~s)\n~ts", [F,ets_format_args(Args),BinStr]), 9034 9035 {ets,ActualF,ActualArgs,Info} = hd(Stk), 9036 9037 RE = <<"[*][*][*] argument \\d+:">>, 9038 case re:run(BinStr, RE, [{capture, none}]) of 9039 match -> 9040 ok; 9041 nomatch -> 9042 store_error(no_explanation, {ets,F,Args}, Info) 9043 end, 9044 9045 case {ActualF,ActualArgs} of 9046 {F,Args} -> 9047 ok; 9048 _ -> 9049 case lists:member(renamed, Opts) of 9050 true -> 9051 ok; 9052 false -> 9053 store_error(renamed, {ets,F,length(Args)}, {ActualF,ActualArgs}) 9054 end 9055 end, 9056 [{error_info, ErrorInfoMap}] = Info, 9057 Cause = maps:get(cause, ErrorInfoMap, none), 9058 {error,Cause} 9059 end. 9060 9061ets_format_args(Args) -> 9062 lists:join(", ", [io_lib:format("~p", [A]) || A <- Args]). 9063 9064%%% 9065%%% Common utility functions. 9066%%% 9067 9068id(I) -> I. 9069 9070very_big_num() -> 9071 very_big_num(33, 1). 9072 9073very_big_num(Left, Result) when Left > 0 -> 9074 very_big_num(Left-1, Result*256); 9075very_big_num(0, Result) -> 9076 Result. 9077 9078make_port() -> 9079 hd(erlang:ports()). 9080 9081make_pid() -> 9082 spawn_link(fun sleeper/0). 9083 9084sleeper() -> 9085 receive after infinity -> ok end. 9086 9087make_ext_pid() -> 9088 {Pid, _, _} = get(externals), 9089 Pid. 9090 9091make_ext_port() -> 9092 {_, Port, _} = get(externals), 9093 Port. 9094make_ext_ref() -> 9095 {_, _, Ref} = get(externals), 9096 Ref. 9097 9098init_externals() -> 9099 case get(externals) of 9100 undefined -> 9101 OtherNode = {gurka@sallad, 1}, 9102 Res = {mk_pid(OtherNode, 7645, 8123), 9103 mk_port(OtherNode, 187489773), 9104 mk_ref(OtherNode, [262143, 1293964255, 3291964278])}, 9105 put(externals, Res); 9106 9107 {_,_,_} -> ok 9108 end. 9109 9110%% 9111%% Node container constructor functions 9112%% 9113 9114-define(VERSION_MAGIC, 131). 9115-define(PORT_EXT, 102). 9116-define(PID_EXT, 103). 9117-define(NEW_REFERENCE_EXT, 114). 9118 9119uint32_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 32 -> 9120 [(Uint bsr 24) band 16#ff, 9121 (Uint bsr 16) band 16#ff, 9122 (Uint bsr 8) band 16#ff, 9123 Uint band 16#ff]; 9124uint32_be(Uint) -> 9125 exit({badarg, uint32_be, [Uint]}). 9126 9127uint16_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 16 -> 9128 [(Uint bsr 8) band 16#ff, 9129 Uint band 16#ff]; 9130uint16_be(Uint) -> 9131 exit({badarg, uint16_be, [Uint]}). 9132 9133uint8(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 8 -> 9134 Uint band 16#ff; 9135uint8(Uint) -> 9136 exit({badarg, uint8, [Uint]}). 9137 9138mk_pid({NodeName, Creation}, Number, Serial) when is_atom(NodeName) -> 9139 <<?VERSION_MAGIC, NodeNameExt/binary>> = term_to_binary(NodeName), 9140 mk_pid({NodeNameExt, Creation}, Number, Serial); 9141mk_pid({NodeNameExt, Creation}, Number, Serial) -> 9142 case catch binary_to_term(list_to_binary([?VERSION_MAGIC, 9143 ?PID_EXT, 9144 NodeNameExt, 9145 uint32_be(Number), 9146 uint32_be(Serial), 9147 uint8(Creation)])) of 9148 Pid when is_pid(Pid) -> 9149 Pid; 9150 {'EXIT', {badarg, _}} -> 9151 exit({badarg, mk_pid, [{NodeNameExt, Creation}, Number, Serial]}); 9152 Other -> 9153 exit({unexpected_binary_to_term_result, Other}) 9154 end. 9155 9156mk_port({NodeName, Creation}, Number) when is_atom(NodeName) -> 9157 <<?VERSION_MAGIC, NodeNameExt/binary>> = term_to_binary(NodeName), 9158 mk_port({NodeNameExt, Creation}, Number); 9159mk_port({NodeNameExt, Creation}, Number) -> 9160 case catch binary_to_term(list_to_binary([?VERSION_MAGIC, 9161 ?PORT_EXT, 9162 NodeNameExt, 9163 uint32_be(Number), 9164 uint8(Creation)])) of 9165 Port when is_port(Port) -> 9166 Port; 9167 {'EXIT', {badarg, _}} -> 9168 exit({badarg, mk_port, [{NodeNameExt, Creation}, Number]}); 9169 Other -> 9170 exit({unexpected_binary_to_term_result, Other}) 9171 end. 9172 9173mk_ref({NodeName, Creation}, Numbers) when is_atom(NodeName), 9174 is_integer(Creation), 9175 is_list(Numbers) -> 9176 <<?VERSION_MAGIC, NodeNameExt/binary>> = term_to_binary(NodeName), 9177 mk_ref({NodeNameExt, Creation}, Numbers); 9178mk_ref({NodeNameExt, Creation}, Numbers) when is_binary(NodeNameExt), 9179 is_integer(Creation), 9180 is_list(Numbers) -> 9181 case catch binary_to_term(list_to_binary([?VERSION_MAGIC, 9182 ?NEW_REFERENCE_EXT, 9183 uint16_be(length(Numbers)), 9184 NodeNameExt, 9185 uint8(Creation), 9186 lists:map(fun (N) -> 9187 uint32_be(N) 9188 end, 9189 Numbers)])) of 9190 Ref when is_reference(Ref) -> 9191 Ref; 9192 {'EXIT', {badarg, _}} -> 9193 exit({badarg, mk_ref, [{NodeNameExt, Creation}, Numbers]}); 9194 Other -> 9195 exit({unexpected_binary_to_term_result, Other}) 9196 end. 9197 9198 9199make_sub_binary(Bin) when is_binary(Bin) -> 9200 {_,B} = split_binary(list_to_binary([0,1,3,Bin]), 3), 9201 B; 9202make_sub_binary(List) -> 9203 make_sub_binary(list_to_binary(List)). 9204 9205make_unaligned_sub_binary(Bin0) when is_binary(Bin0) -> 9206 Bin1 = <<0:3,Bin0/binary,31:5>>, 9207 Sz = size(Bin0), 9208 <<0:3,Bin:Sz/binary,31:5>> = id(Bin1), 9209 Bin; 9210make_unaligned_sub_binary(List) -> 9211 make_unaligned_sub_binary(list_to_binary(List)). 9212 9213%% Repeat test function with different combination of table options 9214%% 9215repeat_for_opts(F) -> 9216 repeat_for_opts(F, [write_concurrency, read_concurrency, compressed]). 9217 9218repeat_for_opts_all_table_types(F) -> 9219 repeat_for_opts(F, [all_types, write_concurrency, read_concurrency, compressed]). 9220 9221repeat_for_opts_all_non_stim_table_types(F) -> 9222 repeat_for_opts(F, [all_non_stim_types, write_concurrency, read_concurrency, compressed]). 9223 9224repeat_for_opts_all_set_table_types(F) -> 9225 repeat_for_opts(F, [set_types, write_concurrency, read_concurrency, compressed]). 9226 9227repeat_for_all_set_table_types(F) -> 9228 repeat_for_opts(F, [set_types]). 9229 9230repeat_for_all_ord_set_table_types(F) -> 9231 repeat_for_opts(F, [ord_set_types]). 9232 9233repeat_for_all_non_stim_set_table_types(F) -> 9234 repeat_for_opts(F, [all_non_stim_set_types]). 9235 9236repeat_for_opts_all_ord_set_table_types(F) -> 9237 repeat_for_opts(F, [ord_set_types, write_concurrency, read_concurrency, compressed]). 9238 9239repeat_for_opts(F, OptGenList) when is_function(F, 1) -> 9240 repeat_for_opts(F, OptGenList, []). 9241 9242repeat_for_opts(F, [], Acc) -> 9243 lists:foldl(fun(Opts, RV_Acc) -> 9244 OptList = lists:filter(fun(E) -> E =/= void end, Opts), 9245 case is_redundant_opts_combo(OptList) of 9246 true -> 9247 %%io:format("Ignoring redundant options ~p\n",[OptList]), 9248 ok; 9249 false -> 9250 io:format("Calling with options ~p\n",[OptList]), 9251 RV = F(OptList), 9252 case RV_Acc of 9253 {comment,_} -> RV_Acc; 9254 _ -> case RV of 9255 {comment,_} -> RV; 9256 _ -> [RV | RV_Acc] 9257 end 9258 end 9259 end 9260 end, [], Acc); 9261repeat_for_opts(F, [OptList | Tail], []) when is_list(OptList) -> 9262 repeat_for_opts(F, Tail, [[Opt] || Opt <- OptList]); 9263repeat_for_opts(F, [OptList | Tail], AccList) when is_list(OptList) -> 9264 repeat_for_opts(F, Tail, [[Opt|Acc] || Opt <- OptList, Acc <- AccList]); 9265repeat_for_opts(F, [Atom | Tail], AccList) when is_atom(Atom) -> 9266 repeat_for_opts(F, [repeat_for_opts_atom2list(Atom) | Tail ], AccList). 9267 9268repeat_for_opts_atom2list(set_types) -> [set,ordered_set,stim_cat_ord_set,cat_ord_set]; 9269repeat_for_opts_atom2list(ord_set_types) -> [ordered_set,stim_cat_ord_set,cat_ord_set]; 9270repeat_for_opts_atom2list(all_types) -> [set,ordered_set,stim_cat_ord_set,cat_ord_set,bag,duplicate_bag]; 9271repeat_for_opts_atom2list(all_non_stim_types) -> [set,ordered_set,cat_ord_set,bag,duplicate_bag]; 9272repeat_for_opts_atom2list(all_non_stim_set_types) -> [set,ordered_set,cat_ord_set]; 9273repeat_for_opts_atom2list(write_concurrency) -> [{write_concurrency,false},{write_concurrency,true}]; 9274repeat_for_opts_atom2list(read_concurrency) -> [{read_concurrency,false},{read_concurrency,true}]; 9275repeat_for_opts_atom2list(compressed) -> [void,compressed]. 9276 9277is_redundant_opts_combo(Opts) -> 9278 (lists:member(stim_cat_ord_set, Opts) orelse 9279 lists:member(cat_ord_set, Opts)) 9280 andalso 9281 (lists:member({write_concurrency, false}, Opts) orelse 9282 lists:member(private, Opts) orelse 9283 lists:member(protected, Opts)). 9284 9285%% Add fake table option with info about key range. 9286%% Will be consumed by ets_new and used for stim_cat_ord_set. 9287key_range(Opts, KeyRange) -> 9288 [{key_range, KeyRange} | Opts]. 9289 9290ets_new(Name, Opts0) -> 9291 {KeyRange, Opts1} = case lists:keytake(key_range, 1, Opts0) of 9292 {value, {key_range, KR}, Rest1} -> 9293 {KR, Rest1}; 9294 false -> 9295 {1000*1000, Opts0} 9296 end, 9297 ets_new(Name, Opts1, KeyRange). 9298 9299ets_new(Name, Opts, KeyRange) -> 9300 ets_new(Name, Opts, KeyRange, fun id/1). 9301 9302ets_new(Name, Opts0, KeyRange, KeyFun) -> 9303 {_Smp, CATree, Stimulate, RevOpts} = 9304 lists:foldl(fun(cat_ord_set, {Smp, false, false, Lacc}) -> 9305 {Smp, Smp, false, [ordered_set | Lacc]}; 9306 (stim_cat_ord_set, {Smp, false, false, Lacc}) -> 9307 {Smp, Smp, Smp, [ordered_set | Lacc]}; 9308 (Other, {Smp, CAT, STIM, Lacc}) -> 9309 {Smp, CAT, STIM, [Other | Lacc]} 9310 end, 9311 {erlang:system_info(schedulers) > 1,false, false, []}, 9312 Opts0), 9313 Opts = lists:reverse(RevOpts), 9314 EtsNewHelper = 9315 fun (UseOpts) -> 9316 case get(ets_new_opts) of 9317 UseOpts -> 9318 silence; %% suppress identical table opts spam 9319 _ -> 9320 put(ets_new_opts, UseOpts), 9321 io:format("ets:new(~p, ~p)~n", [Name, UseOpts]) 9322 end, 9323 ets:new(Name, UseOpts) 9324 end, 9325 case CATree andalso 9326 (not lists:member({write_concurrency, false}, Opts)) andalso 9327 (not lists:member(private, Opts)) andalso 9328 (not lists:member(protected, Opts)) of 9329 true -> 9330 NewOpts1 = 9331 case lists:member({write_concurrency, true}, Opts) of 9332 true -> Opts; 9333 false -> [{write_concurrency, true}|Opts] 9334 end, 9335 NewOpts2 = 9336 case lists:member(public, NewOpts1) of 9337 true -> NewOpts1; 9338 false -> [public|NewOpts1] 9339 end, 9340 T = EtsNewHelper(NewOpts2), 9341 case Stimulate of 9342 false -> ok; 9343 true -> stimulate_contention(T, KeyRange, KeyFun) 9344 end, 9345 T; 9346 false -> 9347 EtsNewHelper(Opts) 9348 end. 9349 9350% The purpose of this function is to stimulate fine grained locking in 9351% tables of types ordered_set with the write_concurrency options 9352% turned on. The erts_debug feature 'ets_force_split' is used to easier 9353% generate a routing tree with fine grained locking without having to 9354% provoke lots of actual lock contentions. 9355stimulate_contention(Tid, KeyRange, KeyFun) -> 9356 T = case Tid of 9357 A when is_atom(A) -> ets:whereis(A); 9358 _ -> Tid 9359 end, 9360 erts_debug:set_internal_state(ets_force_split, {T, true}), 9361 Num = case KeyRange > 50 of 9362 true -> 50; 9363 false -> KeyRange 9364 end, 9365 Seed = rand:uniform(KeyRange), 9366 %%io:format("prefill_table: Seed = ~p\n", [Seed]), 9367 RState = unique_rand_start(KeyRange, Seed), 9368 stim_inserter_loop(T, RState, Num, KeyFun), 9369 Num = ets:info(T, size), 9370 ets:match_delete(T, {'$1','$1','$1'}), 9371 0 = ets:info(T, size), 9372 erts_debug:set_internal_state(ets_force_split, {T, false}), 9373 case ets:info(T,stats) of 9374 {0, _, _} -> 9375 io:format("No routing nodes in table?\n" 9376 "Debug feature 'ets_force_split' does not seem to work.\n", []), 9377 ct:fail("No ets_force_split?"); 9378 Stats -> 9379 io:format("stimulated ordered_set: ~p\n", [Stats]) 9380 end. 9381 9382stim_inserter_loop(_, _, 0, _) -> 9383 ok; 9384stim_inserter_loop(T, RS0, N, KeyFun) -> 9385 {K, RS1} = unique_rand_next(RS0), 9386 Key = KeyFun(K), 9387 ets:insert(T, {Key, Key, Key}), 9388 stim_inserter_loop(T, RS1, N-1, KeyFun). 9389 9390do_tc(Do, Report) -> 9391 T1 = erlang:monotonic_time(), 9392 Do(), 9393 T2 = erlang:monotonic_time(), 9394 Elapsed = erlang:convert_time_unit(T2 - T1, native, millisecond), 9395 Report(Elapsed). 9396 9397syrup_factor() -> 9398 case erlang:system_info(build_type) of 9399 valgrind -> 20; 9400 _ -> 1 9401 end. 9402 9403 9404%% 9405%% This is a pseudo random number generator for UNIQUE integers. 9406%% All integers between 1 and Max will be generated before it repeat itself. 9407%% It's a variant of this one using quadratic residues by Jeff Preshing: 9408%% http://preshing.com/20121224/how-to-generate-a-sequence-of-unique-random-integers/ 9409%% 9410unique_rand_start(Max, Seed) -> 9411 L = lists:dropwhile(fun(P) -> P < Max end, 9412 primes_3mod4()), 9413 [P | _] = case L of 9414 [] -> 9415 error("Random range too large"); 9416 _ -> 9417 L 9418 end, 9419 3 = P rem 4, 9420 {0, {Max, P, Seed}}. 9421 9422unique_rand_next({N, {Max, P, Seed}=Const}) -> 9423 case dquad(P, N, Seed) + 1 of 9424 RND when RND > Max -> % Too large, skip 9425 unique_rand_next({N+1, Const}); 9426 RND -> 9427 {RND, {N+1, Const}} 9428 end. 9429 9430%% A one-to-one relation between all integers 0 =< X < Prime 9431%% if Prime rem 4 == 3. 9432quad(Prime, X) -> 9433 Rem = X*X rem Prime, 9434 case 2*X < Prime of 9435 true -> 9436 Rem; 9437 false -> 9438 Prime - Rem 9439 end. 9440 9441dquad(Prime, X, Seed) -> 9442 quad(Prime, (quad(Prime, X) + Seed) rem Prime). 9443 9444%% Primes where P rem 4 == 3. 9445primes_3mod4() -> 9446 [103, 211, 503, 1019, 2003, 5003, 10007, 20011, 50023, 9447 100003, 200003, 500083, 1000003, 2000003, 5000011, 9448 10000019, 20000003, 50000047, 100000007]. 9449