Current section
Files
Jump to
Current section
Files
src/edb_server_eval.erl
%% Copyright (c) Meta Platforms, Inc. and affiliates.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% % @format
-module(edb_server_eval).
%% erlfmt:ignore
% @fb-only
-compile(warn_missing_spec_all).
-moduledoc false.
-export([eval/5]).
-export([stash_object_code/3, get_object_code/1]).
% ---------------------------------------------------------------------------
% Public functions
% ---------------------------------------------------------------------------
-spec eval(F, X, SourceNode, Timeout, Deps) ->
{ok, Result} | {eval_error, edb:eval_error()} | {failed_to_load_module, module(), term()}
when
F :: fun((X) -> Result),
SourceNode :: node(),
Timeout :: timeout(),
Deps :: [module()].
eval(F, X, SourceNode, Timeout, Deps) ->
{Mod, _, _} = erlang:fun_info_mfa(F),
case load_modules_if_necessary([Mod | Deps], SourceNode) of
{error, FailedMod, LoadFailureReason} ->
{failed_to_load_module, FailedMod, LoadFailureReason};
ok ->
ResultRef = erlang:make_ref(),
Parent = self(),
{Pid, MonitorRef} = erlang:spawn_monitor(fun() ->
try F(X) of
Result -> Parent ! {ResultRef, {ok, Result}}
catch
Class:Reason:ST ->
Exc = {exception, #{class => Class, reason => Reason, stacktrace => ST}},
Parent ! {ResultRef, Exc}
end
end),
{Final, PurgeNeeded} =
receive
{ResultRef, Result = {ok, _}} ->
{Result, true};
{ResultRef, Exc = {exception, _}} ->
{{eval_error, Exc}, true};
{'DOWNER', MonitorRef, process, Pid, ExitReason} ->
{{eval_error, {killed, ExitReason}}, false}
after Timeout ->
erlang:exit(Pid, {timeout, MonitorRef}),
receive
{'DOWN', MonitorRef, process, Pid, ExitReason} ->
case ExitReason of
{timeout, MonitorRef} ->
{{eval_error, timeout}, false};
_ ->
{{eval_error, {killed, ExitReason}}, false}
end;
{ResultRef, Result = {ok, _}} ->
{Result, true};
{ResultRef, Exc = {exception, _}} ->
{{eval_error, Exc}, true}
end
end,
case PurgeNeeded of
false ->
ok;
true ->
receive
{'DOWN', MonitorRef, process, Pid, _} -> ok
end
end,
Final
end.
-spec stash_object_code(Module, BeamFilename, Code) -> ok when
Module :: module(),
BeamFilename :: file:filename(),
Code :: binary().
stash_object_code(Module, BeamFilename, Code) ->
persistent_term:put(stash_key(Module), {ok, BeamFilename, Code}).
-spec get_object_code(Module) -> {ok, BeamFilename, Code} | not_found when
Module :: module(),
BeamFilename :: file:filename(),
Code :: binary().
get_object_code(Module) ->
case persistent_term:get(stash_key(Module), not_found) of
Stashed = {ok, _, _} ->
Stashed;
not_found ->
case code:get_object_code(Module) of
error -> not_found;
{Module, Code, BeamFilename} -> {ok, BeamFilename, Code}
end
end.
% ---------------------------------------------------------------------------
% Helpers
% ---------------------------------------------------------------------------
-spec load_modules_if_necessary(Modules, SourceNode) -> ok | {error, BadModule, Reason} when
Modules :: [module()],
BadModule :: module(),
SourceNode :: node(),
Reason :: not_found | badarg | code:load_error_rsn() | {rpc_error, term()}.
load_modules_if_necessary(Modules, SourceNode) ->
lists:foldl(
fun
(Module, ok) ->
case load_module_if_necessary(Module, SourceNode) of
ok -> ok;
{error, Reason} -> {error, Module, Reason}
end;
(_, Error) ->
Error
end,
ok,
Modules
).
-spec load_module_if_necessary(Module, SourceNode) -> ok | {error, Reason} when
Module :: module(),
SourceNode :: node(),
Reason :: not_found | badarg | code:load_error_rsn() | {rpc_error, term()}.
load_module_if_necessary(Module, SourceNode) ->
case code:is_loaded(Module) of
{file, _} ->
ok;
false ->
% elp:ignore W0014 (cross_node_eval) -- allowed on debugger
try erpc:call(SourceNode, ?MODULE, get_object_code, [Module]) of
not_found ->
{error, not_found};
{ok, BeamFilename, BeamBinary} ->
case code:load_binary(Module, BeamFilename, BeamBinary) of
{module, Module} -> ok;
Error = {error, _} -> Error
end
catch
Class:Reason -> {error, {rpc_error, {Class, Reason}}}
end
end.
-spec stash_key(Module) -> term() when
Module :: module().
stash_key(Module) ->
{?MODULE, Module}.