diff options
| author | Simon MacMullen <simon@rabbitmq.com> | 2014-05-09 15:27:34 +0100 |
|---|---|---|
| committer | Simon MacMullen <simon@rabbitmq.com> | 2014-05-09 15:27:34 +0100 |
| commit | 6b927367091d058247aa4d0f4c2ec93a5cb8999d (patch) | |
| tree | 1beae751c41b0f32ea056937302930fb31fe895e | |
| parent | a272278f6762be5a96a494b4d228d0b184466df0 (diff) | |
| parent | e787dcfdae7bc055e2ee13c70ed14b3eacba5a9e (diff) | |
| download | rabbitmq-server-git-6b927367091d058247aa4d0f4c2ec93a5cb8999d.tar.gz | |
Merge bug26120
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | include/rabbit.hrl | 2 | ||||
| -rw-r--r-- | packaging/RPMS/Fedora/rabbitmq-server.spec | 3 | ||||
| -rw-r--r-- | packaging/debs/Debian/debian/changelog | 6 | ||||
| -rw-r--r-- | src/gm.erl | 249 | ||||
| -rw-r--r-- | src/rabbit_mirror_queue_misc.erl | 24 | ||||
| -rw-r--r-- | src/rabbit_mirror_queue_slave.erl | 8 | ||||
| -rw-r--r-- | src/rabbit_nodes.erl | 33 | ||||
| -rw-r--r-- | src/rabbit_tests.erl | 10 | ||||
| -rw-r--r-- | src/truncate.erl | 30 |
10 files changed, 182 insertions, 187 deletions
@@ -250,9 +250,6 @@ start-cover: all echo "rabbit_misc:start_cover([\"rabbit\", \"hare\"])." | $(ERL_CALL) echo "rabbit_misc:enable_cover([\"$(COVER_DIR)\"])." | $(ERL_CALL) -start-secondary-cover: all - echo "rabbit_misc:start_cover([\"hare\"])." | $(ERL_CALL) - stop-cover: all echo "rabbit_misc:report_cover(), cover:stop()." | $(ERL_CALL) cat cover/summary.txt @@ -384,3 +381,4 @@ include $(DEPS_FILE) endif .PHONY: run-qc + diff --git a/include/rabbit.hrl b/include/rabbit.hrl index 12b7a07b35..44f0931eef 100644 --- a/include/rabbit.hrl +++ b/include/rabbit.hrl @@ -125,6 +125,6 @@ %% 4) Amount to decrease 2) every time we descend while truncating. %% %% Whole thing feeds into truncate:log_event/2. --define(LOG_TRUNC, {2000, 100, 100, 7}). +-define(LOG_TRUNC, {2000, 100, 50, 5}). -define(store_proc_name(N), rabbit_misc:store_proc_name(?MODULE, N)). diff --git a/packaging/RPMS/Fedora/rabbitmq-server.spec b/packaging/RPMS/Fedora/rabbitmq-server.spec index b85f9a0269..618e3a5662 100644 --- a/packaging/RPMS/Fedora/rabbitmq-server.spec +++ b/packaging/RPMS/Fedora/rabbitmq-server.spec @@ -130,6 +130,9 @@ done rm -rf %{buildroot} %changelog +* Tue Apr 29 2014 simon@rabbitmq.com 3.3.1-1 +- New Upstream Release + * Wed Apr 2 2014 simon@rabbitmq.com 3.3.0-1 - New Upstream Release diff --git a/packaging/debs/Debian/debian/changelog b/packaging/debs/Debian/debian/changelog index c7d911350b..0b3a267d0f 100644 --- a/packaging/debs/Debian/debian/changelog +++ b/packaging/debs/Debian/debian/changelog @@ -1,3 +1,9 @@ +rabbitmq-server (3.3.1-1) unstable; urgency=low + + * New Upstream Release + + -- Simon MacMullen <simon@rabbitmq.com> Tue, 29 Apr 2014 11:49:23 +0100 + rabbitmq-server (3.3.0-1) unstable; urgency=low * New Upstream Release diff --git a/src/gm.erl b/src/gm.erl index 9a51bfc28f..0c0ac349b0 100644 --- a/src/gm.erl +++ b/src/gm.erl @@ -509,9 +509,9 @@ create_tables([]) -> ok; create_tables([{Table, Attributes} | Tables]) -> case mnesia:create_table(Table, Attributes) of - {atomic, ok} -> create_tables(Tables); - {aborted, {already_exists, gm_group}} -> create_tables(Tables); - Err -> Err + {atomic, ok} -> create_tables(Tables); + {aborted, {already_exists, Table}} -> create_tables(Tables); + Err -> Err end. table_definitions() -> @@ -581,7 +581,11 @@ handle_call({confirmed_broadcast, Msg}, _From, ok, State}); handle_call({confirmed_broadcast, Msg}, From, State) -> - internal_broadcast(Msg, From, 0, State); + {Result, State1 = #state { pub_count = PubCount, confirms = Confirms }} = + internal_broadcast(Msg, 0, State), + Confirms1 = queue:in({PubCount, From}, Confirms), + handle_callback_result({Result, flush_broadcast_buffer( + State1 #state { confirms = Confirms1 })}); handle_call(info, _From, State = #state { members_state = undefined }) -> @@ -601,43 +605,27 @@ handle_call({add_on_right, _NewMember}, _From, handle_call({add_on_right, NewMember}, _From, State = #state { self = Self, group_name = GroupName, - view = View, members_state = MembersState, - module = Module, - callback_args = Args, txn_executor = TxnFun }) -> - {MembersState1, Group} = - record_new_member_in_group( - GroupName, Self, NewMember, - fun (Group1) -> - View1 = group_to_view(Group1), - MembersState1 = remove_erased_members(MembersState, View1), - ok = send_right(NewMember, View1, - {catchup, Self, - prepare_members_state(MembersState1)}), - MembersState1 - end, TxnFun), - View2 = group_to_view(Group), - State1 = check_neighbours(State #state { view = View2, - members_state = MembersState1 }), - Result = callback_view_changed(Args, Module, View, View2), + Group = record_new_member_in_group(NewMember, Self, GroupName, TxnFun), + View1 = group_to_view(Group), + MembersState1 = remove_erased_members(MembersState, View1), + ok = send_right(NewMember, View1, + {catchup, Self, prepare_members_state(MembersState1)}), + {Result, State1} = change_view(View1, State #state { + members_state = MembersState1 }), handle_callback_result({Result, {ok, Group}, State1}). - handle_cast({?TAG, ReqVer, Msg}, State = #state { view = View, members_state = MembersState, - group_name = GroupName, - module = Module, - callback_args = Args }) -> + group_name = GroupName }) -> {Result, State1} = case needs_view_update(ReqVer, View) of - true -> View1 = group_to_view(read_group(GroupName)), + true -> View1 = group_to_view(dirty_read_group(GroupName)), MemberState1 = remove_erased_members(MembersState, View1), - {callback_view_changed(Args, Module, View, View1), - check_neighbours( - State #state { view = View1, - members_state = MemberState1 })}; + change_view(View1, State #state { + members_state = MemberState1 }); false -> {ok, State} end, handle_callback_result( @@ -657,7 +645,8 @@ handle_cast({broadcast, Msg, _SizeHint}, State}); handle_cast({broadcast, Msg, SizeHint}, State) -> - internal_broadcast(Msg, none, SizeHint, State); + {Result, State1} = internal_broadcast(Msg, SizeHint, State), + handle_callback_result({Result, maybe_flush_broadcast_buffer(State1)}); handle_cast(join, State = #state { self = Self, group_name = GroupName, @@ -720,13 +709,15 @@ handle_info({'DOWN', MRef, process, _Pid, Reason}, _ -> View1 = group_to_view(record_dead_member_in_group( Member, GroupName, TxnFun)), - State1 = case alive_view_members(View1) of - [Self] -> State #state { - members_state = blank_member_state(), - confirms = purge_confirms(Confirms) }; - _ -> State - end, - handle_callback_result(maybe_erase_aliases(State1, View1)) + handle_callback_result( + case alive_view_members(View1) of + [Self] -> maybe_erase_aliases( + State #state { + members_state = blank_member_state(), + confirms = purge_confirms(Confirms) }, + View1); + _ -> change_view(View1, State) + end) end. @@ -876,30 +867,18 @@ ensure_broadcast_timer(State = #state { broadcast_timer = undefined }) -> ensure_broadcast_timer(State) -> State. -internal_broadcast(Msg, From, SizeHint, +internal_broadcast(Msg, SizeHint, State = #state { self = Self, pub_count = PubCount, module = Module, - confirms = Confirms, callback_args = Args, broadcast_buffer = Buffer, broadcast_buffer_sz = BufferSize }) -> PubCount1 = PubCount + 1, - Result = Module:handle_msg(Args, get_pid(Self), Msg), - Buffer1 = [{PubCount1, Msg} | Buffer], - Confirms1 = case From of - none -> Confirms; - _ -> queue:in({PubCount1, From}, Confirms) - end, - State1 = State #state { pub_count = PubCount1, - confirms = Confirms1, - broadcast_buffer = Buffer1, - broadcast_buffer_sz = BufferSize + SizeHint}, - handle_callback_result( - {Result, case From of - none -> maybe_flush_broadcast_buffer(State1); - _ -> flush_broadcast_buffer(State1) - end}). + {Module:handle_msg(Args, get_pid(Self), Msg), + State #state { pub_count = PubCount1, + broadcast_buffer = [{PubCount1, Msg} | Buffer], + broadcast_buffer_sz = BufferSize + SizeHint}}. %% The Erlang distribution mechanism has an interesting quirk - it %% will kill the VM cold with "Absurdly large distribution output data @@ -1042,7 +1021,7 @@ ensure_alive_suffix1(MembersQ) -> %% --------------------------------------------------------------------------- join_group(Self, GroupName, TxnFun) -> - join_group(Self, GroupName, read_group(GroupName), TxnFun). + join_group(Self, GroupName, dirty_read_group(GroupName), TxnFun). join_group(Self, GroupName, {error, not_found}, TxnFun) -> join_group(Self, GroupName, @@ -1085,93 +1064,82 @@ join_group(Self, GroupName, #gm_group { members = Members } = Group, TxnFun) -> end end. -read_group(GroupName) -> +dirty_read_group(GroupName) -> case mnesia:dirty_read(?GROUP_TABLE, GroupName) of [] -> {error, not_found}; [Group] -> Group end. +read_group(GroupName) -> + case mnesia:read({?GROUP_TABLE, GroupName}) of + [] -> {error, not_found}; + [Group] -> Group + end. + +write_group(Group) -> mnesia:write(?GROUP_TABLE, Group, write), Group. + prune_or_create_group(Self, GroupName, TxnFun) -> - Group = TxnFun( - fun () -> - GroupNew = #gm_group { name = GroupName, - members = [Self], - version = get_version(Self) }, - case mnesia:read({?GROUP_TABLE, GroupName}) of - [] -> - mnesia:write(GroupNew), - GroupNew; - [Group1 = #gm_group { members = Members }] -> - case lists:any(fun is_member_alive/1, Members) of - true -> Group1; - false -> mnesia:write(GroupNew), - GroupNew - end + TxnFun( + fun () -> + GroupNew = #gm_group { name = GroupName, + members = [Self], + version = get_version(Self) }, + case read_group(GroupName) of + {error, not_found} -> + write_group(GroupNew); + Group = #gm_group { members = Members } -> + case lists:any(fun is_member_alive/1, Members) of + true -> Group; + false -> write_group(GroupNew) end - end), - Group. + end + end). record_dead_member_in_group(Member, GroupName, TxnFun) -> - Group = - TxnFun( - fun () -> [Group1 = #gm_group { members = Members, version = Ver }] = - mnesia:read({?GROUP_TABLE, GroupName}), - case lists:splitwith( - fun (Member1) -> Member1 =/= Member end, Members) of - {_Members1, []} -> %% not found - already recorded dead - Group1; - {Members1, [Member | Members2]} -> - Members3 = Members1 ++ [{dead, Member} | Members2], - Group2 = Group1 #gm_group { members = Members3, - version = Ver + 1 }, - mnesia:write(Group2), - Group2 - end - end), - Group. - -record_new_member_in_group(GroupName, Left, NewMember, Fun, TxnFun) -> - {Result, Group} = - TxnFun( - fun () -> - [#gm_group { members = Members, version = Ver } = Group1] = - mnesia:read({?GROUP_TABLE, GroupName}), - {Prefix, [Left | Suffix]} = - lists:splitwith(fun (M) -> M =/= Left end, Members), - Members1 = Prefix ++ [Left, NewMember | Suffix], - Group2 = Group1 #gm_group { members = Members1, - version = Ver + 1 }, - Result = Fun(Group2), - mnesia:write(Group2), - {Result, Group2} - end), - {Result, Group}. + TxnFun( + fun () -> + Group = #gm_group { members = Members, version = Ver } = + read_group(GroupName), + case lists:splitwith( + fun (Member1) -> Member1 =/= Member end, Members) of + {_Members1, []} -> %% not found - already recorded dead + Group; + {Members1, [Member | Members2]} -> + Members3 = Members1 ++ [{dead, Member} | Members2], + write_group(Group #gm_group { members = Members3, + version = Ver + 1 }) + end + end). + +record_new_member_in_group(NewMember, Left, GroupName, TxnFun) -> + TxnFun( + fun () -> + Group = #gm_group { members = Members, version = Ver } = + read_group(GroupName), + {Prefix, [Left | Suffix]} = + lists:splitwith(fun (M) -> M =/= Left end, Members), + write_group(Group #gm_group { + members = Prefix ++ [Left, NewMember | Suffix], + version = Ver + 1 }) + end). erase_members_in_group(Members, GroupName, TxnFun) -> DeadMembers = [{dead, Id} || Id <- Members], - Group = - TxnFun( - fun () -> - [Group1 = #gm_group { members = [_|_] = Members1, - version = Ver }] = - mnesia:read({?GROUP_TABLE, GroupName}), - case Members1 -- DeadMembers of - Members1 -> Group1; - Members2 -> Group2 = - Group1 #gm_group { members = Members2, - version = Ver + 1 }, - mnesia:write(Group2), - Group2 - end - end), - Group. + TxnFun( + fun () -> + Group = #gm_group { members = [_|_] = Members1, version = Ver } = + read_group(GroupName), + case Members1 -- DeadMembers of + Members1 -> Group; + Members2 -> write_group( + Group #gm_group { members = Members2, + version = Ver + 1 }) + end + end). maybe_erase_aliases(State = #state { self = Self, group_name = GroupName, - view = View0, members_state = MembersState, - module = Module, - callback_args = Args, txn_executor = TxnFun }, View) -> #view_member { aliases = Aliases } = fetch_view_member(Self, View), {Erasable, MembersState1} @@ -1190,9 +1158,7 @@ maybe_erase_aliases(State = #state { self = Self, _ -> group_to_view( erase_members_in_group(Erasable, GroupName, TxnFun)) end, - State1 = State #state { members_state = MembersState1, view = View1 }, - {callback_view_changed(Args, Module, View0, View1), - check_neighbours(State1)}. + change_view(View1, State #state { members_state = MembersState1 }). can_erase_view_member(Self, Self, _LA, _LP) -> false; can_erase_view_member(_Self, _Id, N, N) -> true; @@ -1326,7 +1292,7 @@ prepare_members_state(MembersState) -> ?DICT:to_list(MembersState). build_members_state(MembersStateList) -> ?DICT:from_list(MembersStateList). make_member(GroupName) -> - {case read_group(GroupName) of + {case dirty_read_group(GroupName) of #gm_group { version = Version } -> Version; {error, not_found} -> ?VERSION_START end, self()}. @@ -1390,16 +1356,19 @@ callback(Args, Module, Activity) -> {stop, _Reason} = Error -> Error end. -callback_view_changed(Args, Module, OldView, NewView) -> - OldMembers = all_known_members(OldView), - NewMembers = all_known_members(NewView), +change_view(View, State = #state { view = View0, + module = Module, + callback_args = Args }) -> + OldMembers = all_known_members(View0), + NewMembers = all_known_members(View), Births = NewMembers -- OldMembers, Deaths = OldMembers -- NewMembers, - case {Births, Deaths} of - {[], []} -> ok; - _ -> Module:members_changed( - Args, get_pids(Births), get_pids(Deaths)) - end. + Result = case {Births, Deaths} of + {[], []} -> ok; + _ -> Module:members_changed( + Args, get_pids(Births), get_pids(Deaths)) + end, + {Result, check_neighbours(State #state { view = View })}. handle_callback_result({Result, State}) -> if_callback_success( diff --git a/src/rabbit_mirror_queue_misc.erl b/src/rabbit_mirror_queue_misc.erl index 256543de61..b0f092a9a0 100644 --- a/src/rabbit_mirror_queue_misc.erl +++ b/src/rabbit_mirror_queue_misc.erl @@ -21,7 +21,8 @@ report_deaths/4, store_updated_slaves/1, initial_queue_node/2, suggested_queue_nodes/1, is_mirrored/1, update_mirrors/2, validate_policy/1, - maybe_auto_sync/1, log_info/3, log_warning/3]). + maybe_auto_sync/1, maybe_drop_master_after_sync/1, + log_info/3, log_warning/3]). %% for testing only -export([module/1]). @@ -57,6 +58,7 @@ -spec(is_mirrored/1 :: (rabbit_types:amqqueue()) -> boolean()). -spec(update_mirrors/2 :: (rabbit_types:amqqueue(), rabbit_types:amqqueue()) -> 'ok'). +-spec(maybe_drop_master_after_sync/1 :: (rabbit_types:amqqueue()) -> 'ok'). -spec(maybe_auto_sync/1 :: (rabbit_types:amqqueue()) -> 'ok'). -spec(log_info/3 :: (rabbit_amqqueue:name(), string(), [any()]) -> 'ok'). -spec(log_warning/3 :: (rabbit_amqqueue:name(), string(), [any()]) -> 'ok'). @@ -346,6 +348,26 @@ update_mirrors0(OldQ = #amqqueue{name = QName}, maybe_auto_sync(NewQ), ok. +%% The arrival of a newly synced slave may cause the master to die if +%% the policy does not want the master but it has been kept alive +%% because there were no synced slaves. +%% +%% We don't just call update_mirrors/2 here since that could decide to +%% start a slave for some other reason, and since we are the slave ATM +%% that allows complicated deadlocks. +maybe_drop_master_after_sync(Q = #amqqueue{name = QName, + pid = MPid}) -> + {DesiredMNode, DesiredSNodes} = suggested_queue_nodes(Q), + case node(MPid) of + DesiredMNode -> ok; + OldMNode -> false = lists:member(OldMNode, DesiredSNodes), %% [0] + drop_mirror(QName, OldMNode) + end, + ok. +%% [0] ASSERTION - if the policy wants the master to change, it has +%% not just shuffled it into the slaves. All our modes ensure this +%% does not happen, but we should guard against a misbehaving plugin. + %%---------------------------------------------------------------------------- validate_policy(KeyList) -> diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index f6acd91aa2..ee889f8442 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -922,8 +922,6 @@ update_ram_duration(BQ, BQS) -> rabbit_memory_monitor:report_ram_duration(self(), RamDuration), BQ:set_ram_duration_target(DesiredDuration, BQS1). -%% [1] - the arrival of this newly synced slave may cause the master to die if -%% the admin has requested a migration-type change to policy. record_synchronised(#amqqueue { name = QName }) -> Self = self(), case rabbit_misc:execute_mnesia_transaction( @@ -934,9 +932,9 @@ record_synchronised(#amqqueue { name = QName }) -> [Q1 = #amqqueue { sync_slave_pids = SSPids }] -> Q2 = Q1#amqqueue{sync_slave_pids = [Self | SSPids]}, rabbit_mirror_queue_misc:store_updated_slaves(Q2), - {ok, Q1, Q2} + {ok, Q2} end end) of - ok -> ok; - {ok, Q1, Q2} -> rabbit_mirror_queue_misc:update_mirrors(Q1, Q2) %% [1] + ok -> ok; + {ok, Q} -> rabbit_mirror_queue_misc:maybe_drop_master_after_sync(Q) end. diff --git a/src/rabbit_nodes.erl b/src/rabbit_nodes.erl index 9d8606be83..7f7fcc3126 100644 --- a/src/rabbit_nodes.erl +++ b/src/rabbit_nodes.erl @@ -81,10 +81,12 @@ diagnostics_node(Node) -> [{" * unable to connect to epmd (port ~s) on ~s: ~s~n", [epmd_port(), Host, rabbit_misc:format_inet_error(Reason)]}]; {ok, NamePorts} -> - case net_adm:ping(Node) of - pong -> dist_working_diagnostics(Node); - pang -> dist_broken_diagnostics(Name, Host, NamePorts) - end + [{" * connected to epmd (port ~s) on ~s", + [epmd_port(), Host]}] ++ + case net_adm:ping(Node) of + pong -> dist_working_diagnostics(Node); + pang -> dist_broken_diagnostics(Name, Host, NamePorts) + end end]. epmd_port() -> @@ -95,11 +97,11 @@ epmd_port() -> dist_working_diagnostics(Node) -> case rabbit:is_running(Node) of - true -> [{" * node up, rabbit application running~n", []}]; - false -> [{" * node up, rabbit application not running~n" + true -> [{" * node ~s up, 'rabbit' application running", [Node]}]; + false -> [{" * node ~s up, 'rabbit' application not running~n" " * running applications on ~s: ~p~n" - " * suggestion: start_app on ~s~n", - [Node, remote_apps(Node), Node]}] + " * suggestion: start_app on ~s", + [Node, Node, remote_apps(Node), Node]}] end. remote_apps(Node) -> @@ -119,13 +121,16 @@ dist_broken_diagnostics(Name, Host, NamePorts) -> Host -> SelfName; _ -> never_matches end], - [{" * ~s seems not to be running at all", [Name]} | - case Others of - [] -> [{" * no other nodes on ~s", [Host]}]; - _ -> [{" * other nodes on ~s: ~p", [Host, Others]}] - end]; + OthersDiag = case Others of + [] -> [{" no other nodes on ~s", + [Host]}]; + _ -> [{" other nodes on ~s: ~p", + [Host, Others]}] + end, + [{" * epmd reports: node '~s' not running at all", [Name]}, + OthersDiag, {" * suggestion: start the node", []}]; [{Name, Port}] -> - [{" * found ~s (port ~b)", [Name, Port]} | + [{" * epmd reports node '~s' running on port ~b", [Name, Port]} | case diagnose_connect(Host, Port) of ok -> [{" * TCP connection succeeded but Erlang distribution " diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 9e5cf2c030..ab95196d96 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -90,19 +90,9 @@ do_if_secondary_node(Up, Down) -> setup_cluster() -> do_if_secondary_node( fun (SecondaryNode) -> - cover:stop(SecondaryNode), ok = control_action(stop_app, []), - %% 'cover' does not cope at all well with nodes disconnecting, - %% which happens as part of reset. So we turn it off - %% temporarily. That is ok even if we're not in general using - %% cover, it just turns the engine on / off and doesn't log - %% anything. Note that this way cover won't be on when joining - %% the cluster, but this is OK since we're testing the clustering - %% interface elsewere anyway. - cover:stop(nodes()), ok = control_action(join_cluster, [atom_to_list(SecondaryNode)]), - cover:start(nodes()), ok = control_action(start_app, []), ok = control_action(start_app, SecondaryNode, [], []) end, diff --git a/src/truncate.erl b/src/truncate.erl index 7113cfa4e3..02dba2e36a 100644 --- a/src/truncate.erl +++ b/src/truncate.erl @@ -44,18 +44,19 @@ report(List, Params) -> [case Item of end || Item <- List]. term(Thing, {Content, Struct, ContentDec, StructDec}) -> - term(Thing, #params{content = Content, - struct = Struct, - content_dec = ContentDec, - struct_dec = StructDec}); + term(Thing, true, #params{content = Content, + struct = Struct, + content_dec = ContentDec, + struct_dec = StructDec}). -term(Bin, #params{content = N}) when (is_binary(Bin) orelse is_bitstring(Bin)) - andalso size(Bin) > N - ?ELLIPSIS_LENGTH -> +term(Bin, _AllowPrintable, #params{content = N}) + when (is_binary(Bin) orelse is_bitstring(Bin)) + andalso size(Bin) > N - ?ELLIPSIS_LENGTH -> Suffix = without_ellipsis(N), <<Head:Suffix/binary, _/bitstring>> = Bin, <<Head/binary, <<"...">>/binary>>; -term(L, #params{struct = N} = Params) when is_list(L) -> - case io_lib:printable_list(L) of +term(L, AllowPrintable, #params{struct = N} = Params) when is_list(L) -> + case AllowPrintable andalso io_lib:printable_list(L) of true -> N2 = without_ellipsis(N), case length(L) > N2 of true -> string:left(L, N2) ++ "..."; @@ -63,9 +64,9 @@ term(L, #params{struct = N} = Params) when is_list(L) -> end; false -> shrink_list(L, Params) end; -term(T, Params) when is_tuple(T) -> +term(T, _AllowPrintable, Params) when is_tuple(T) -> list_to_tuple(shrink_list(tuple_to_list(T), Params)); -term(T, _) -> +term(T, _, _) -> T. without_ellipsis(N) -> erlang:max(N - ?ELLIPSIS_LENGTH, 0). @@ -78,9 +79,9 @@ shrink_list([H|T], #params{content = Content, struct = Struct, content_dec = ContentDec, struct_dec = StructDec} = Params) -> - [term(H, Params#params{content = Content - ContentDec, - struct = Struct - StructDec}) - | term(T, Params#params{struct = Struct - 1})]. + [term(H, true, Params#params{content = Content - ContentDec, + struct = Struct - StructDec}) + | term(T, false, Params#params{struct = Struct - 1})]. %%---------------------------------------------------------------------------- @@ -91,6 +92,7 @@ test() -> test_short_examples_exactly() -> F = fun (Term, Exp) -> Exp = term(Term, {10, 10, 5, 5}) end, + FSmall = fun (Term, Exp) -> Exp = term(Term, {2, 2, 2, 2}) end, F([], []), F("h", "h"), F("hello world", "hello w..."), @@ -101,6 +103,8 @@ test_short_examples_exactly() -> F(<<1:1>>, <<1:1>>), F(<<1:81>>, <<0:56, "...">>), F({{{{a}}},{b},c,d,e,f,g,h,i,j,k}, {{{'...'}},{b},c,d,e,f,g,h,i,j,'...'}), + FSmall({a,30,40,40,40,40}, {a,30,'...'}), + FSmall([a,30,40,40,40,40], [a,30,'...']), P = spawn(fun() -> receive die -> ok end end), F([0, 0.0, <<1:1>>, F, P], [0, 0.0, <<1:1>>, F, P]), P ! die, |
