Skip to content

Commit

Permalink
Merge pull request #8765 from garazdawi/lukas/kernel/refactor-group-g…
Browse files Browse the repository at this point in the history
…en_statem

Refactor group to use gen_statem
  • Loading branch information
garazdawi authored Sep 9, 2024
2 parents 5a63096 + 0169a29 commit 2ff9590
Show file tree
Hide file tree
Showing 8 changed files with 860 additions and 794 deletions.
1,465 changes: 754 additions & 711 deletions lib/kernel/src/group.erl

Large diffs are not rendered by default.

86 changes: 49 additions & 37 deletions lib/kernel/src/user_drv.erl
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ init_remote_shell(State, Node, {M, F, A}) ->
end,

Group = group:start(self(), RShell,
[{echo,State#state.shell_started =:= new}] ++
[{dumb, State#state.shell_started =/= new}] ++
group_opts(RemoteNode)),

Gr = gr_add_cur(State#state.groups, Group, RShell),
Expand All @@ -329,7 +329,7 @@ init_local_shell(State, InitialShell) ->

Gr = gr_add_cur(State#state.groups,
group:start(self(), InitialShell,
group_opts() ++ [{echo,State#state.shell_started =:= new}]),
group_opts() ++ [{dumb,State#state.shell_started =/= new}]),
InitialShell),

init_shell(State#state{ groups = Gr }, [Slogan,$\n]).
Expand All @@ -351,10 +351,7 @@ init_shell(State, Slogan) ->
start_user() ->
case whereis(user) of
undefined ->
User = group:start(self(), {}, [{echo,false},
{noshell,true}]),
register(user, User),
User;
group:start(self(), noshell, [{name, user}]);
User ->
User
end.
Expand Down Expand Up @@ -625,7 +622,7 @@ switch_loop(internal, line, State) ->
switch_loop(internal, {line, Line}, State) ->
case erl_scan:string(Line) of
{ok, Tokens, _} ->
case switch_cmd(Tokens, State#state.groups) of
case switch_cmd(Tokens, State#state.groups, State#state.shell_started =/= new) of
{ok, Groups} ->
Curr = gr_cur_pid(Groups),
put(current_group, Curr),
Expand Down Expand Up @@ -676,38 +673,48 @@ switch_loop(info, {Requester, get_terminal_state}, _State) ->
stdout => prim_tty:isatty(stdout),
stderr => prim_tty:isatty(stderr) } },
keep_state_and_data;
switch_loop(info, {Requester, tty_geometry}, {_Cont, #state{ tty = TTYState }}) ->
case prim_tty:window_size(TTYState) of
{ok, Geometry} ->
Requester ! {self(), tty_geometry, Geometry},
ok;
Error ->
Requester ! {self(), tty_geometry, Error},
ok
end,
keep_state_and_data;
switch_loop(timeout, _, {_Cont, State}) ->
{keep_state_and_data,
{next_event, info, {State#state.read,{data,[]}}}};
switch_loop(info, _Unknown, _State) ->
{keep_state_and_data, postpone}.

switch_cmd([{atom,_,Key},{Type,_,Value}], Gr)
switch_cmd([{atom,_,Key},{Type,_,Value}], Gr, Dumb)
when Type =:= atom; Type =:= integer ->
switch_cmd({Key, Value}, Gr);
switch_cmd([{atom,_,Key},{atom,_,V1},{atom,_,V2}], Gr) ->
switch_cmd({Key, V1, V2}, Gr);
switch_cmd([{atom,_,Key}], Gr) ->
switch_cmd(Key, Gr);
switch_cmd([{'?',_}], Gr) ->
switch_cmd(h, Gr);

switch_cmd(Cmd, Gr) when Cmd =:= c; Cmd =:= i; Cmd =:= k ->
switch_cmd({Cmd, gr_cur_index(Gr)}, Gr);
switch_cmd({c, I}, Gr0) ->
switch_cmd({Key, Value}, Gr, Dumb);
switch_cmd([{atom,_,Key},{atom,_,V1},{atom,_,V2}], Gr, Dumb) ->
switch_cmd({Key, V1, V2}, Gr, Dumb);
switch_cmd([{atom,_,Key}], Gr, Dumb) ->
switch_cmd(Key, Gr, Dumb);
switch_cmd([{'?',_}], Gr, Dumb) ->
switch_cmd(h, Gr, Dumb);

switch_cmd(Cmd, Gr, Dumb) when Cmd =:= c; Cmd =:= i; Cmd =:= k ->
switch_cmd({Cmd, gr_cur_index(Gr)}, Gr, Dumb);
switch_cmd({c, I}, Gr0, _Dumb) ->
case gr_set_cur(Gr0, I) of
{ok,Gr} -> {ok, Gr};
undefined -> unknown_group()
end;
switch_cmd({i, I}, Gr) ->
switch_cmd({i, I}, Gr, _Dumb) ->
case gr_get_num(Gr, I) of
{pid,Pid} ->
exit(Pid, interrupt),
{retry, []};
undefined ->
unknown_group()
end;
switch_cmd({k, I}, Gr) ->
switch_cmd({k, I}, Gr, _Dumb) ->
case gr_get_num(Gr, I) of
{pid,Pid} ->
exit(Pid, die),
Expand All @@ -724,15 +731,15 @@ switch_cmd({k, I}, Gr) ->
undefined ->
unknown_group()
end;
switch_cmd(j, Gr) ->
switch_cmd(j, Gr, _Dumb) ->
{retry, gr_list(Gr)};
switch_cmd({s, Shell}, Gr0) when is_atom(Shell) ->
Pid = group:start(self(), {Shell,start,[]}),
switch_cmd({s, Shell}, Gr0, Dumb) when is_atom(Shell) ->
Pid = group:start(self(), {Shell,start,[]}, [{dumb, Dumb} | group_opts()]),
Gr = gr_add_cur(Gr0, Pid, {Shell,start,[]}),
{retry, [], Gr};
switch_cmd(s, Gr) ->
switch_cmd({s, shell}, Gr);
switch_cmd(r, Gr0) ->
switch_cmd(s, Gr, Dumb) ->
switch_cmd({s, shell}, Gr, Dumb);
switch_cmd(r, Gr0, _Dumb) ->
case is_alive() of
true ->
Node = pool:get_node(),
Expand All @@ -742,30 +749,35 @@ switch_cmd(r, Gr0) ->
false ->
{retry, [{put_chars,unicode,<<"Node is not alive\n">>}]}
end;
switch_cmd({r, Node}, Gr) when is_atom(Node)->
switch_cmd({r, Node, shell}, Gr);
switch_cmd({r,Node,Shell}, Gr0) when is_atom(Node), is_atom(Shell) ->
switch_cmd({r, Node}, Gr, Dumb) when is_atom(Node)->
switch_cmd({r, Node, shell}, Gr, Dumb);
switch_cmd({r,Node,Shell}, Gr0, Dumb) when is_atom(Node), is_atom(Shell) ->
case is_alive() of
true ->
Pid = group:start(self(), {Node,Shell,start,[]}, group_opts(Node)),
Gr = gr_add_cur(Gr0, Pid, {Node,Shell,start,[]}),
{retry, [], Gr};
case net_kernel:connect_node(Node) of
true ->
Pid = group:start(self(), {Node,Shell,start,[]}, [{dumb, Dumb} | group_opts(Node)]),
Gr = gr_add_cur(Gr0, Pid, {Node,Shell,start,[]}),
{retry, [], Gr};
false ->
{retry, [{put_chars,unicode,<<"Could not connect to node\n">>}]}
end;
false ->
{retry, [{put_chars,unicode,"Node is not alive\n"}]}
end;

switch_cmd(q, _Gr) ->
switch_cmd(q, _Gr, _Dumb) ->
case erlang:system_info(break_ignored) of
true -> % noop
{retry, [{put_chars,unicode,<<"Unknown command\n">>}]};
false ->
halt()
end;
switch_cmd(h, _Gr) ->
switch_cmd(h, _Gr, _Dumb) ->
{retry, list_commands()};
switch_cmd([], _Gr) ->
switch_cmd([], _Gr, _Dumb) ->
{retry,[]};
switch_cmd(_Ts, _Gr) ->
switch_cmd(_Ts, _Gr, _Dumb) ->
{retry, [{put_chars,unicode,<<"Unknown command\n">>}]}.

unknown_group() ->
Expand Down
50 changes: 26 additions & 24 deletions lib/kernel/test/interactive_shell_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
shell_update_window_unicode_wrap/1,
shell_receive_standard_out/1,
shell_standard_error_nlcr/1, shell_clear/1,
shell_format/1,
shell_format/1, shell_help/1,
remsh_basic/1, remsh_error/1, remsh_longnames/1, remsh_no_epmd/1,
remsh_expand_compatibility_25/1, remsh_expand_compatibility_later_version/1,
external_editor/1, external_editor_visual/1,
Expand Down Expand Up @@ -117,7 +117,8 @@ groups() ->
shell_full_queue,
external_editor,
external_editor_visual,
shell_ignore_pager_commands
shell_ignore_pager_commands,
shell_help
]},
{tty_unicode,[parallel],
[{group,tty_tests},
Expand Down Expand Up @@ -1094,7 +1095,7 @@ shell_support_ansi_input(Config) ->
ClearText = "\e[0m",

try
send_stdin(Term,["{",BoldText,"a😀b",ClearText,"}"]),
send_tty(Term,["{",BoldText,"a😀b",ClearText,"}"]),
timer:sleep(1000),
try check_location(Term, {0, width("{1ma😀bm}")}) of
_ ->
Expand Down Expand Up @@ -1149,15 +1150,15 @@ shell_expand_location_below(Config) ->
Cols = 80,

%% First check that basic completion works
send_stdin(Term, "escript:"),
send_stdin(Term, "\t"),
send_tty(Term, "escript:"),
send_tty(Term, "\t"),
%% Cursor at correct place
check_location(Term, {-3, width("escript:")}),
%% Nothing after the start( completion
check_content(Term, "start\\($"),

%% Check that completion is cleared when we type
send_stdin(Term, "s"),
send_tty(Term, "s"),
check_location(Term, {-3, width("escript:s")}),
check_content(Term, "escript:s$"),

Expand All @@ -1167,19 +1168,19 @@ shell_expand_location_below(Config) ->
send_tty(Term, "End"),
send_tty(Term, ", test_after]"),
[send_tty(Term, "Left") || _ <- ", test_after]"],
send_stdin(Term, "\t"),
send_tty(Term, "\t"),
check_location(Term, {-3, width("[escript:s")}),
check_content(Term, "script_name\\([ ]+start\\($"),
send_tty(Term, "C-K"),

%% Check that completion works when in the middle of a long term
send_tty(Term, ", "++ lists:duplicate(80*2, $a)++"]"),
[send_tty(Term, "Left") || _ <- ", "++ lists:duplicate(80*2, $a)++"]"],
send_stdin(Term, "\t"),
send_tty(Term, "\t"),
check_location(Term, {-4, width("[escript:s")}),
check_content(Term, "script_name\\([ ]+start\\($"),
send_tty(Term, "End"),
send_stdin(Term, ".\n"),
send_tty(Term, ".\n"),

%% Check that we behave as we should with very long completions
rpc(Term, fun() ->
Expand All @@ -1190,14 +1191,14 @@ shell_expand_location_below(Config) ->
timer:sleep(1000), %% Sleep to make sure window has resized
Result = 61,
Rows1 = 48,
send_stdin(Term, "long_module:" ++ FunctionName),
send_stdin(Term, "\t"),
send_tty(Term, "long_module:" ++ FunctionName),
send_tty(Term, "\t"),
check_content(Term, "3> long_module:" ++ FunctionName ++ "\nfunctions(\n|.)*a_long_function_name0\\("),

%% Check that correct text is printed below expansion
check_content(Term, io_lib:format("rows ~w to ~w of ~w",
[1, 7, Result])),
send_stdin(Term, "\t"),
send_tty(Term, "\t"),
check_content(Term, io_lib:format("rows ~w to ~w of ~w",
[1, Rows1, Result])),
send_tty(Term, "Down"),
Expand Down Expand Up @@ -1273,13 +1274,13 @@ shell_expand_location_above(Config) ->

try
tmux(["resize-window -t ",tty_name(Term)," -x 80"]),
send_stdin(Term, "escript:"),
send_stdin(Term, "\t"),
send_tty(Term, "escript:"),
send_tty(Term, "\t"),
check_location(Term, {0, width("escript:")}),
check_content(Term, "start\\(\n"),
check_content(Term, "escript:$"),
send_stdin(Term, "s"),
send_stdin(Term, "\t"),
send_tty(Term, "s"),
send_tty(Term, "\t"),
check_location(Term, {0, width("escript:s")}),
check_content(Term, "\nscript_name\\([ ]+start\\(\n"),
check_content(Term, "escript:s$"),
Expand All @@ -1292,12 +1293,13 @@ shell_expand_location_above(Config) ->
shell_help(Config) ->
Term = start_tty(Config),
try
send_stdin(Term, "lists"),
send_stdin(Term, "\^[h"),
send_tty(Term, "application:put_env(kernel, shell_docs_ansi, false).\n"),
send_tty(Term, "lists"),
send_tty(Term, "\^[h"),
check_content(Term, "List processing functions."),
send_stdin(Term, ":all"),
send_stdin(Term, "\^[h"),
check_content(Term, "-spec all(Pred, List) -> boolean()"),
send_tty(Term, ":all"),
send_tty(Term, "\^[h"),
check_content(Term, ~S"all\(Pred, List\)"),
ok
after
stop_tty(Term),
Expand Down Expand Up @@ -1337,7 +1339,7 @@ shell_get_password(_Config) ->
rtnode:run(
[{putline,"io:get_password()."},
{putline,"secret\r"},
{expect, "\r\n\r\n\"secret\""}]),
{expect, "\r\n\"secret\""}]),

%% io:get_password only works when run in "newshell"
rtnode:run(
Expand Down Expand Up @@ -1548,7 +1550,7 @@ shell_suspend(Config) ->
%% We test that suspending of `erl` and then resuming restores the shell
shell_full_queue(Config) ->

[throw({skip,"Need unbuffered to run"}) || os:find_executable("unbuffered") =:= false],
[throw({skip,"Need unbuffer (apt install expect) to run"}) || os:find_executable("unbuffer") =:= false],

%% In order to fill the read buffer of the terminal we need to get a
%% bit creative. We first need to start erl in bash in order to be
Expand Down Expand Up @@ -1615,7 +1617,7 @@ shell_full_queue(Config) ->
send_tty(Term, "fg"),
send_tty(Term, "Enter"),
Pid ! stop,
check_content(Term,"b$"),
check_content(Term,"b\\([^)]*\\)2>$"),

send_tty(Term, "."),
send_tty(Term, "Enter"),
Expand Down
7 changes: 4 additions & 3 deletions lib/ssh/src/ssh_cli.erl
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,8 @@ start_shell(ConnectionHandler, State) ->
Shell
end,
State#state{group = group:start(self(), ShellSpawner,
[{dumb, get_dumb(State#state.pty)},{expand_below, false},
[{dumb, get_dumb(State#state.pty)},
{expand_below, false},
{echo, get_echo(State#state.pty)}]),
buf = empty_buf()}.

Expand All @@ -763,7 +764,7 @@ start_exec_shell(ConnectionHandler, Cmd, State) ->
{M, F, A++[Cmd]}
end,
State#state{group = group:start(self(), ExecShellSpawner, [{expand_below, false},
{echo,false}]),
{dumb, true}]),
buf = empty_buf()}.

%%--------------------------------------------------------------------
Expand Down Expand Up @@ -848,7 +849,7 @@ exec_in_self_group(ConnectionHandler, ChannelId, WantReply, State, Fun) ->
end)
end,
{ok, State#state{group = group:start(self(), Exec, [{expand_below, false},
{echo,false}]),
{dumb, true}]),
buf = empty_buf()}}.


Expand Down
2 changes: 0 additions & 2 deletions lib/ssh/src/ssh_client_channel.erl
Original file line number Diff line number Diff line change
Expand Up @@ -363,8 +363,6 @@ enter_loop(State) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
-doc """
init(Options) -> {ok, State} | {ok, State, Timeout} | {stop, Reason}
The following options must be present:
- **`{channel_cb, atom()}`** - The module that implements the channel behaviour.
Expand Down
6 changes: 3 additions & 3 deletions lib/ssh/test/ssh_connection_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ start_shell_exec(Config) when is_list(Config) ->
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
{user_dir, UserDir},
{password, "morot"},
{exec, {?MODULE,ssh_exec_echo,[]}} ]),
{exec, {?MODULE,ssh_exec_echo,["foo"]}} ]),

ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user, "foo"},
Expand Down Expand Up @@ -1153,8 +1153,8 @@ start_exec_direct_fun1_read_write_advanced(Config) ->
after 5000 -> go_on
end,
receive
X -> ct:fail("remaining messages"),
ct:log("remaining message: ~p",[X])
X -> ct:log("remaining message: ~p",[X]),
ct:fail("remaining messages")
after 0 -> go_on
end,
ssh:stop_daemon(Pid).
Expand Down
Loading

0 comments on commit 2ff9590

Please sign in to comment.