Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:26
erlang
3047-Implement-exit_on_close.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 3047-Implement-exit_on_close.patch of Package erlang
From 5d131574a639d9c336d65a3c1613fd075713f4d7 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen <raimo@erlang.org> Date: Thu, 7 Mar 2024 19:08:12 +0100 Subject: [PATCH 17/22] Implement exit_on_close --- lib/kernel/src/gen_tcp_socket.erl | 569 +++++++++++++++--------------- 1 file changed, 292 insertions(+), 277 deletions(-) diff --git a/lib/kernel/src/gen_tcp_socket.erl b/lib/kernel/src/gen_tcp_socket.erl index 60546e36f2..36f3b7993b 100644 --- a/lib/kernel/src/gen_tcp_socket.erl +++ b/lib/kernel/src/gen_tcp_socket.erl @@ -744,6 +744,8 @@ info(?MODULE_socket(Server, _Socket)) -> case call(Server, {info, inet:stats()}) of {error, closed} -> ?CLOSED_SOCKET; + {error, einval} -> + ?CLOSED_SOCKET; Other -> Other end. @@ -834,28 +836,35 @@ fdopen(Fd, Opts) when is_integer(Fd), 0 =< Fd, is_list(Opts) -> %%% Socket glue code %%% --compile({inline, [socket_send/3]}). +-compile({inline, [socket_send/3, socket_send_error/1]}). socket_send(Socket, Data, Timeout) -> Result = socket:send(Socket, Data, [], Timeout), case Result of - %% Translate 'epipe' to 'econnreset' - {error, {epipe, RestData}} when is_binary(RestData) -> - {error, {econnreset, RestData}}; - {error, epipe} -> - {error, econnreset}; - #{info := Reason} = _EEI -> + {error, {Reason, RestData}} when is_binary(RestData) -> + {error, NewReason} = socket_send_error({error, Reason}), + {error, {NewReason, RestData}}; + {error, _} -> + socket_send_error(Result); + _ -> + Result + end. + +socket_send_error(Result) -> + case Result of + {error, epipe} -> {error, econnreset}; + {error, Reason} when is_atom(Reason) -> Result; + {error, #{info := Reason}} -> case Reason of netname_deleted -> {error, econnreset}; too_many_cmds -> {error, closed}; _ -> - {error, Reason} - end; - _ -> - Result + Result + end end. + -compile({inline, [socket_recv/2]}). socket_recv(Socket, Length) -> Result = socket:recv(Socket, Length, [], nowait), @@ -1433,7 +1442,7 @@ callback_mode() -> handle_event_function. -record(wrap_counters, {ref :: reference(), - call :: info | {getstat, list()}, + call :: {info | getstat, [inet:stat_option()]}, state :: term()}). %% 'connect' % A listen socket stays here @@ -1704,7 +1713,7 @@ handle_event( owner => Owner, active => Active}; getstat -> - GetstatCounters + {ok, GetstatCounters} end, {next_state, NewState, P_D, [{reply, From, Reply}]}; @@ -1756,7 +1765,7 @@ handle_event( handle_event({call, From}, {getopts, Opts}, State, {P, D}) -> %% ?DBG([{opts, Opts}, {state, State}, {d, D}]), - Result = case state_getopts(P, D, State, Opts) of + Result = case call_getopts(P, D, State, Opts) of {ok, OptVals} -> %% ?DBG([{opt_vals, OptVals}]), {ok, externalize_getopts(OptVals)}; @@ -1767,7 +1776,7 @@ handle_event({call, From}, {getopts, Opts}, State, {P, D}) -> {keep_state_and_data, [{reply, From, Result}]}; -handle_event({call, From}, {setopts, Opts}, State, {P_0, D_0}) -> +handle_event({call, From}, {setopts, Opts}, State, {P, D_0}) -> %% To produce less garbage - work on a diminished D map %% and last merge all changes, also to see when there are %% metadata changes and only update metadata if so @@ -1776,29 +1785,23 @@ handle_event({call, From}, {setopts, Opts}, State, {P_0, D_0}) -> %% affects the option handling %% MinKeys = [active, recv_httph], - HandleEventResult = - handle_setopts(From, Opts, State, P_0, maps:with(MinKeys, D_0)), - %% Extract the resulting D map - case HandleEventResult of - {next_state, _, {P, D_1}, _} -> ok; - {stop_and_reply, _, _, {P, D_1}} -> ok - end, + {Result, D_1, ActionsR} = + call_setopts(P, maps:with(MinKeys, D_0), State, Opts), %% Merge the changes D = maps:merge(maps:without(MinKeys, D_0), D_1), is_map_keys(meta_opts(), D_1) andalso begin % Metadata option change - update by overwrite ok = socket:setopt(P#params.socket, {otp,meta}, meta(D)) end, - %% Replace the D map in HandleEventResult - case HandleEventResult of - {next_state, NextState, _, Actions} -> - {next_state, NextState, {P, D}, Actions}; - {stop_and_reply, Reason, Replies, _} -> - {stop_and_reply, Reason, Replies, {P, D}} - end; + handle_active(P, D, State, reverse(ActionsR, [{reply, From, Result}])); -handle_event({call, From}, {setopt_active, Active}, State, {P, D}) -> - handle_setopts_active(From, [], State, P, D, Active); +handle_event({call, From}, {setopt_active, Active}, State, {P, D_0}) -> + %% The Active option doesn't affect any metadata option, + %% and will only cause one D map update, so there is no need + %% for the diminished D map dance above + %% + {Result, D, ActionsR} = call_setopts_active(P, D_0, State, [], Active), + handle_active(P, D, State, reverse(ActionsR, [{reply, From, Result}])); handle_event({call, From}, {close, Caller}, State, {P, D}) -> handle_close(P, D, State, Caller, [{reply, From, ok}]); @@ -1810,7 +1813,7 @@ handle_event(internal, exit, 'closed', _P_D) -> %% corresponds to driver_exit() in inet_drv {stop, {shutdown, closed}}; handle_event(Type, Content, 'closed' = State, P_D) -> - handle_unexpected(Type, Content, State, P_D); + handle_closed(Type, Content, State, P_D); %% State: 'closed' %% ------- @@ -1819,20 +1822,8 @@ handle_event({call, From}, {send_error, Reason}, State, {P, D}) -> %% ------- %% State: 'closed_read' -handle_event(Type, Content, 'closed_read' = State, {P, _D}) -> - case Type of - {call, From} -> - {keep_state_and_data, - [{reply, From, {error, closed}}]}; - _ -> - warning_msg("Received unexpected event:" - "~n Socket: ~p" - "~n State: ~p" - "~n Event Type: ~p" - "~n Content: ~p", - [P#params.socket, State, Type, Content]), - keep_state_and_data - end; +handle_event(Type, Content, 'closed_read' = State, P_D) -> + handle_closed(Type, Content, State, P_D); %% State: 'closed_read' %% ------- @@ -2029,6 +2020,21 @@ completion_status_reason(Reason) -> _ -> Reason end. +handle_closed(Type, Content, State, {P, _D}) -> + case Type of + {call, From} -> + {keep_state_and_data, + [{reply, From, {error, closed}}]}; + _ -> + warning_msg("Received unexpected event:" + "~n Socket: ~p" + "~n State: ~p" + "~n Event Type: ~p" + "~n Content: ~p", + [P#params.socket, State, Type, Content]), + keep_state_and_data + end. + handle_unexpected(Type, Content, State, {P, _D}) -> warning_msg("Received unexpected event:" "~n Socket: ~p" @@ -2403,45 +2409,53 @@ handle_recv_error( end, handle_recv_error_reply(P, D_1, ActionsR, Reason, 'connected'); true -> + ShowReason = + if + Reason =:= econnreset; + Reason =:= econnaborted -> + case maps:get(show_econnreset, D) of + true -> econnreset; + false -> closed + end; + true -> + Reason + end, case Active of false -> - case maps:get(exit_on_close, D) of - true -> - socket_close(P#params.socket), - handle_recv_error_reply( - P, D, ActionsR, Reason, 'closed'); - false -> - handle_recv_error_reply( - P, D, ActionsR, Reason, 'closed_read') - end; + handle_recv_error_reply( + P, D, ActionsR, ShowReason, 'closed_read'); _ -> - Reason =/= closed andalso + ShowReason =/= closed andalso begin - Owner ! {tcp_error, ModuleSocket, Reason} + Owner ! {tcp_error, ModuleSocket, ShowReason} end, Caller =/= Owner andalso begin Owner ! {tcp_closed, ModuleSocket} end, D_1 = D#{active := false, tcp_closed := true}, - case maps:get(exit_on_close, D) of - true -> - socket_close(P#params.socket), - handle_recv_error_reply( - P, D_1, - [{next_event, internal, exit} | ActionsR], - Reason, 'closed'); - false -> - handle_recv_error_reply( - P, D_1, ActionsR, Reason, 'closed_read') - end + handle_recv_error_reply( + P, D_1, ActionsR, ShowReason, 'closed_read') end end. %% -handle_recv_error_reply(P, D, ActionsR, Reason, NextState) -> +handle_recv_error_reply(P, D, ActionsR_0, Reason, NextState_0) -> %% %% Create state machine actions; reply and cancel timeout %% + case + (NextState_0 =:= 'closed_read') + andalso + maps:get(exit_on_close, D) + of + true -> + socket_close(P#params.socket), + ActionsR = [{next_event, internal, exit} | ActionsR_0], + NextState = 'closed'; + false -> + ActionsR = ActionsR_0, + NextState = NextState_0 + end, case D of #{recv_from := From} -> {next_state, NextState, {P, recv_stop(D)}, @@ -2455,14 +2469,15 @@ handle_recv_error_reply(P, D, ActionsR, Reason, NextState) -> handle_send_error(#params{socket = Socket} = P, D, State, From, Reason) -> ReplyReason = case - (Reason =:= econnreset) andalso maps:get(show_econnreset, D) + (Reason =:= econnreset orelse + Reason =:= econnaborted) andalso maps:get(show_econnreset, D) of true -> - Reason; + econnreset; false -> closed end, - Reply = {reply, From, ReplyReason}, + Reply = {reply, From, {error, ReplyReason}}, case State of #recv{info = Info} -> socket_cancel(Socket, Info), @@ -2483,204 +2498,13 @@ handle_send_error(#params{socket = Socket} = P, D, State, From, Reason) -> end. -%% ------- -%% {call, From}, {setopts, Opts} -%% - -handle_setopts_result(From, Result_0, State, P, D) -> - Result = - case Result_0 of - {error, enoprotoopt} -> - %% If we get this error, the options is not valid for - %% this (tcp) protocol. - {error, einval}; - - {error, {invalid, _}} -> - %% If we get this error, the options where crap. - {error, einval}; - {error, einval} -> - %% If we get this error, either the options where crap or - %% the socket is in a "bad state" (maybe it's closed). - %% So, if that is the case we accept that we may not be - %% able to update the meta data. - Result_0; - {error, _} -> - %% We should really handle this better. stop_and_reply? - Result_0; - ok -> - ok - end, +handle_active(P, D, State, ActionsR) -> case State of 'connected' -> - handle_connected(P, D, [{reply, From, Result}]); - _ -> - {next_state, State, {P, D}, [{reply, From, Result}]} - end. - -handle_setopts(From, [], State, P, D) -> - handle_setopts_result(From, ok, State, P, D); -handle_setopts(From, [{Tag, Val} | Opts], State, P, D) -> - case socket_opts() of - #{Tag := SocketOpt} -> - handle_setopts_socket(From, Opts, State, P, D, SocketOpt, Val); - #{} -> - case maps:is_key(Tag, server_write_opts()) of - %% server options for socket send hence - %% duplicated in {opt,meta} - %% - true when State =:= 'closed' -> - %% ?DBG('server write when state closed'), - handle_setopts_result( - From, {error, einval}, State, P, D); - true -> - %% ?DBG('server write side'), - handle_setopts_server( - From, Opts, State, P, D, Tag, Val); - false -> - case maps:is_key(Tag, server_read_opts()) of - %% server options for receive - %% - true - when State =:= 'closed' -> - %% ?DBG('server read when state closed*'), - handle_setopts_result( - From, {error, einval}, State, P, D); - true -> - %% ?DBG('server read side'), - handle_setopts_server( - From, Opts, State, P, D, Tag, Val); - false -> - %% ignored and invalid options - %% - case ignore_optname(Tag) of - true -> - %% ?DBG(ignore), - handle_setopts( - From, Opts, State, P, D); - false -> - %% ?DBG({extra, Tag}), - handle_setopts_result( - From, {error, einval}, State, P, D) - end - end - end - end. - -%% Options for the 'socket' module -%% -handle_setopts_socket(From, Opts, State, P, D, SocketOpt, Val) -> - case P#params.socket of - undefined -> - handle_setopts_result(From, {error, closed}, State, P, D); - Socket -> - case socket_setopt(Socket, SocketOpt, Val) of - ok when SocketOpt =:= {otp,rcvbuf} -> - Size = - case Val of - {Count, Sz} -> Count * Sz; - Sz when is_integer(Sz) -> Sz - end, - handle_setopts( - From, Opts, State, P, D#{SocketOpt => Size}); - ok when SocketOpt =:= {socket,rcvbuf} -> - %% Mimic inet_drv.c for SOCK_STREAM: - %% when setting 'recbuf', if 'buffer' hasn't been set; - %% set 'buffer' to the same size - %% - OtpOpt = {otp,rcvbuf}, - case D of - #{OtpOpt := _} -> - case socket_setopt(Socket, OtpOpt, Val) of - ok -> - handle_setopts( - From, Opts, State, P, D); - {error, _} = Error -> - handle_setopts_result( - From, Error, State, P, D) - end; - #{} -> - handle_setopts(From, Opts, State, P, D) - end; - ok -> - handle_setopts(From, Opts, State, P, D); - {error, _} = Error -> - handle_setopts_result(From, Error, State, P, D) - end - end. - -%% Options in the server process D variable -%% -handle_setopts_server(From, Opts, State, P, D, Tag, Val) -> - case Tag of - packet -> - case is_packet_option_value(Val) of - true -> - handle_setopts( - From, Opts, State, P, - maps:remove(recv_httph, D#{packet => Val})); - false -> - handle_setopts_result( - From, {error, einval}, State, P, D) - end; - active -> - handle_setopts_active(From, Opts, State, P, D, Val); + handle_connected(P, D, reverse(ActionsR)); _ -> - %% ?DBG([{tag, Tag}, {value, Value}]), - handle_setopts(From, Opts, State, P, D#{Tag => Val}) - end. - -handle_setopts_active(From, Opts, State, P, D, Active) - when State =:= 'closed_read'; - State =:= 'closed' -> - if - Active =:= false -> - handle_setopts(From, Opts, State, P, D); - true -> % not false; socket is active - case D of - #{tcp_closed := true} -> - %% tcp_closed already sent - handle_setopts(From, Opts, State, P, D); - #{tcp_closed := false} -> - P#params.owner ! {tcp_closed, module_socket(P)}, - handle_setopts( - From, Opts, State, P, D#{tcp_closed := true}) - end - end; -handle_setopts_active(From, Opts, State, P, D, Active) -> - %% ?DBG([{active, Active}]), - if - Active =:= once; - Active =:= true -> - handle_setopts(From, Opts, State, P, D#{active := Active}); - Active =:= false -> - OldActive = maps:get(active, D), - is_integer(OldActive) andalso - begin - P#params.owner ! {tcp_passive, module_socket(P)} - end, - handle_setopts(From, Opts, State, P, D#{active := Active}); - is_integer(Active), -32768 =< Active, Active =< 32767 -> - OldActive = maps:get(active, D), - N = - if - is_integer(OldActive) -> OldActive + Active; - true -> Active - end, - if - 32767 < N -> - handle_setopts_result( - From, {error, einval}, State, P, D); - N =< 0 -> - P#params.owner ! {tcp_passive, module_socket(P)}, - handle_setopts( - From, Opts, State, P, D#{active := false}); - true -> - handle_setopts( - From, Opts, State, P, D#{active := N}) - end; - true -> - handle_setopts_result(From, {error, einval}, State, P, D) + {next_state, State, {P, D}, reverse(ActionsR)} end. %% ------------------------------------------------------------------------- @@ -2703,7 +2527,7 @@ recv_data_deliver( %% %% ?DBG([{owner, Owner}, %% {mode, Mode}, - %% {header, Header}, {deliver, Deliver}, {packet, Packet}]), + %% {header, Header}, {deliver, Deliver}, {packet, Packet}]), DeliverData = deliver_data(Data, Mode, Header, Packet), case D of #{recv_from := From} -> % Explicit recv/2 call @@ -2837,6 +2661,189 @@ tag(Packet) -> tcp end. +%% ------- +%% {call, From}, {setopts, Opts} +%% +%% -> {Result, D, ActionsR} +%% + +call_setopts_result(Result_0, D) -> + call_setopts_result(Result_0, D, []). +%% +call_setopts_result(Result_0, D, ActionsR) -> + Result = + case Result_0 of + {error, enoprotoopt} -> + %% If we get this error, the options is not valid for + %% this (tcp) protocol. + {error, einval}; + + {error, {invalid, _}} -> + %% If we get this error, the options where crap. + {error, einval}; + + {error, einval} -> + %% If we get this error, either the options where crap or + %% the socket is in a "bad state" (maybe it's closed). + %% So, if that is the case we accept that we may not be + %% able to update the meta data. + Result_0; + {error, _} -> + %% We should really handle this better. stop_and_reply? + Result_0; + ok -> + ok + end, + {Result, D, ActionsR}. + +call_setopts(_P, D, _State, []) -> + call_setopts_result(ok, D); +call_setopts(P, D, State, [{Tag, Val} | Opts]) -> + case socket_opts() of + #{Tag := SocketOpt} -> + call_setopts_socket(P, D, State, Opts, SocketOpt, Val); + #{} -> + case maps:is_key(Tag, server_write_opts()) of + %% server options for socket send hence + %% duplicated in {opt,meta} + %% + true when State =:= 'closed' -> + %% ?DBG('server write when state closed'), + call_setopts_result({error, einval}, D); + true -> + %% ?DBG('server write side'), + call_setopts_server(P, D, State, Opts, Tag, Val); + false -> + case maps:is_key(Tag, server_read_opts()) of + %% server options for receive + %% + true + when State =:= 'closed' -> + %% ?DBG('server read when state closed*'), + call_setopts_result({error, einval}, D); + true -> + %% ?DBG('server read side'), + call_setopts_server(P, D, State, Opts, Tag, Val); + false -> + %% ignored and invalid options + %% + case ignore_optname(Tag) of + true -> + %% ?DBG(ignore), + call_setopts(P, D, State, Opts); + false -> + %% ?DBG({extra, Tag}), + call_setopts_result({error, einval}, D) + end + end + end + end. + +%% Options for the 'socket' module +%% +call_setopts_socket(P, D, State, Opts, SocketOpt, Val) -> + case P#params.socket of + undefined -> + call_setopts_result({error, closed}, D); + Socket -> + case socket_setopt(Socket, SocketOpt, Val) of + ok when SocketOpt =:= {otp,rcvbuf} -> + Size = + case Val of + {Count, Sz} -> Count * Sz; + Sz when is_integer(Sz) -> Sz + end, + call_setopts(P, D#{SocketOpt => Size}, State, Opts); + ok when SocketOpt =:= {socket,rcvbuf} -> + %% Mimic inet_drv.c for SOCK_STREAM: + %% when setting 'recbuf', if 'buffer' hasn't been set; + %% set 'buffer' to the same size + %% + OtpOpt = {otp,rcvbuf}, + case D of + #{OtpOpt := _} -> + case socket_setopt(Socket, OtpOpt, Val) of + ok -> + call_setopts(P, D, State, Opts); + {error, _} = Error -> + call_setopts_result(Error, D) + end; + #{} -> + call_setopts(P, D, State, Opts) + end; + ok -> + call_setopts(P, D, State, Opts); + {error, _} = Error -> + call_setopts_result(Error, D) + end + end. + +%% Options in the server process D variable +%% +call_setopts_server(P, D, State, Opts, Tag, Val) -> + case Tag of + packet -> + case is_packet_option_value(Val) of + true -> + call_setopts( + P, maps:remove(recv_httph, D#{packet => Val}), + State, Opts); + false -> + call_setopts_result({error, einval}, D) + end; + active -> + call_setopts_active(P, D, State, Opts, Val); + _ -> + %% ?DBG([{tag, Tag}, {value, Value}]), + call_setopts(P, D#{Tag => Val}, State, Opts) + end. + +call_setopts_active(P, D, State, Opts, _Active) + when State =:= 'closed_read' -> + call_setopts(P, D, State, Opts); +call_setopts_active(P, D, State, Opts, Active) + when State =:= 'closed' -> + if + Active =:= false -> + call_setopts(P, D, State, Opts); + true -> % not false; socket is active + P#params.owner ! {tcp_closed, module_socket(P)}, + socket_close(P#params.socket), + {ok, D, [{next_event, internal, exit}]} + end; +call_setopts_active(P, D, State, Opts, Active) -> + %% ?DBG([{active, Active}]), + if + Active =:= once; + Active =:= true -> + call_setopts(P, D#{active := Active}, State, Opts); + Active =:= false -> + OldActive = maps:get(active, D), + is_integer(OldActive) andalso + begin + P#params.owner ! {tcp_passive, module_socket(P)} + end, + call_setopts(P, D#{active := Active}, State, Opts); + is_integer(Active), -32768 =< Active, Active =< 32767 -> + OldActive = maps:get(active, D), + N = + if + is_integer(OldActive) -> OldActive + Active; + true -> Active + end, + if + 32767 < N -> + call_setopts_result({error, einval}, D); + N =< 0 -> + P#params.owner ! {tcp_passive, module_socket(P)}, + call_setopts(P, D#{active := false}, State, Opts); + true -> + call_setopts(P, D#{active := N}, State, Opts) + end; + true -> + call_setopts_result({error, einval}, D) + end. + %% ------- %% Exported socket option translation %% @@ -2873,13 +2880,15 @@ socket_setopts(Socket, [{Tag,Val} | Opts], SocketOpts) -> %% ------- %% getopts in server %% - +%% {call, From}, {getopts, Opts} +%% %% -> {ok, [Options]} | {error, einval} -state_getopts(P, D, State, Opts) -> - state_getopts(P, D, State, Opts, []). -state_getopts(_P, _D, _State, [], Acc) -> +%% +call_getopts(P, D, State, Opts) -> + call_getopts(P, D, State, Opts, []). +call_getopts(_P, _D, _State, [], Acc) -> {ok, reverse(Acc)}; -state_getopts(P, D, State, [Tag | Tags], Acc) -> +call_getopts(P, D, State, [Tag | Tags], Acc) -> %% ?DBG([{tag, Tag}]), SocketOpts = socket_opts(), {Key, Val} = @@ -2902,13 +2911,13 @@ state_getopts(P, D, State, [Tag | Tags], Acc) -> of {ok, Value} -> %% ?DBG({'socket getopt', ok, Value}), - state_getopts( + call_getopts( P, D, State, Tags, [{Key, Value} | Acc]); {error, einval} = ERROR -> ERROR; {error, _Reason} -> %% ?DBG([{reason, _Reason}]), - state_getopts(P, D, State, Tags, Acc) + call_getopts(P, D, State, Tags, Acc) end end; false -> @@ -2922,7 +2931,7 @@ state_getopts(P, D, State, [Tag | Tags], Acc) -> true -> %% ?DBG('server write'), Value = maps:get(Key, D), - state_getopts(P, D, State, Tags, [{Key, Value} | Acc]); + call_getopts(P, D, State, Tags, [{Key, Value} | Acc]); false -> case maps:is_key(Key, server_read_opts()) of %% server options for receive @@ -2934,7 +2943,7 @@ state_getopts(P, D, State, [Tag | Tags], Acc) -> true -> %% ?DBG('server read'), Value = maps:get(Key, D), - state_getopts( + call_getopts( P, D, State, Tags, [{Key, Value} | Acc]); false -> %% ignored and invalid options @@ -2942,7 +2951,7 @@ state_getopts(P, D, State, [Tag | Tags], Acc) -> case ignore_optname(Key) of true -> %% ?DBG({ignore, Tag}), - state_getopts(P, D, State, Tags, Acc); + call_getopts(P, D, State, Tags, Acc); false -> %% ?DBG({extra, Tag}), {error, einval} @@ -2953,6 +2962,8 @@ state_getopts(P, D, State, [Tag | Tags], Acc) -> %% %% ------- +%% {call, From}, {info|getstat, What} +%% counter_keys(What) -> lists:usort(counter_keys_1(What)). @@ -3035,6 +3046,10 @@ counter_value(W, C) -> (W bsl ?COUNTER_BITS) + C. +%% ------- +%% General helpers +%% + -compile({inline, [reverse/1]}). reverse([]) -> []; reverse([_] = L) -> L; -- 2.35.3
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor