AtomVM icon indicating copy to clipboard operation
AtomVM copied to clipboard

branch=main - disterl gen_server:call from atomvm fails

Open elsbiet opened this issue 5 months ago • 9 comments

from esp32

 {server, remote_node} ! <<"hello world from atomvm">>

works as expected but

gen_server:call({server, remote_node}, <<"hello world from atomvm">>)

throws an exception

.. AtomVM/libs/estdlib/src/gen_server.erl"},{line,434}]}]

elsbiet avatar Aug 06 '25 10:08 elsbiet

Could you please post the full error log? It probably can easily be reproduced but I'm afk right now.

I guess this is from AtomVM to BEAM? I.e. the gen_server process runs on a BEAM node? Which OTP version?

We might miss a bit of disterl implementation or a gen_server protocol compatibility with BEAM.

pguyot avatar Aug 06 '25 15:08 pguyot

  1. no more info than this:
CRASH 
======
pid: <0.1.0>

Stacktrace:
[{gen_server,call,2,[{file,"../AtomVM/libs/estdlib/src/gen_server.erl"},{line,434}]}]

cp: #CP<module: 9, label: 18, offset: 16>

x[0]: error
x[1]: function_clause
x[2]: {1,1,60,1,[{4,1820}],error}

Stack 
-----

[]
[]
#CP<module: 1, label: 14, offset: 0>


Mailbox
-------


Monitors
--------
link to <0.2.0>
link to <0.8.0>
link to <0.14.0>


**End Of Crash Report**
Return value: error
I (3856) AtomVM: AtomVM application terminated.  Going to sleep forever ...
  1. yes it's from atomvm (running on esp32-s3

  2. i feel (../AtomVM/libs/estdlib/src/gen_server.erl"},{line,434}])

call(Name, Request, TimeoutMs) when is_atom(Name) ->
    case erlang:whereis(Name) of
        undefined ->
            erlang:exit({noproc, {?MODULE, ?FUNCTION_NAME, [Name, Request]}});
        Pid when is_pid(Pid) ->
            call(Pid, Request, TimeoutMs)
    end;
call(Pid, Request, TimeoutMs) when is_pid(Pid) ->
    MonitorRef = monitor(process, Pid),
    Pid ! {'$gen_call', {self(), MonitorRef}, Request},
    receive
        {'DOWN', MonitorRef, process, Pid, {E, []} = _Reason} ->
            erlang:exit({E, {?MODULE, ?FUNCTION_NAME, [Pid, Request]}});
        {'DOWN', MonitorRef, process, Pid, {_E, _L} = Reason} ->
            erlang:exit(Reason);
        {'DOWN', MonitorRef, process, Pid, Atom} when is_atom(Atom) ->
            erlang:exit({Atom, {?MODULE, ?FUNCTION_NAME, [Pid, Request]}});
        {MonitorRef, Reply} ->
            demonitor(MonitorRef, [flush]),
            Reply
    after TimeoutMs ->
        demonitor(MonitorRef, [flush]),
        erlang:exit({timeout, {?MODULE, ?FUNCTION_NAME, [Pid, Request]}})
    end.

cannot handle {server, remote_node}

elsbiet avatar Aug 06 '25 15:08 elsbiet

That seems easy to fix but I cannot test it right now. I can come up with a PR tonight for you to test.

pguyot avatar Aug 06 '25 15:08 pguyot

take your time. i'm not in a run.

elsbiet avatar Aug 06 '25 16:08 elsbiet

The roadmap to fix this is a little longer as I first expected and is as follows:

  1. Fix monitor/2 bif to work with an atom, looking up the registered process.
  2. Factorize gen_server code (and probably gen_statem) to skip the whereis bit and take advantage of the fixed monitor/2. This would have the added benefit of fixing potential race conditions if the process is unregistered and another registered between the whereis/1 call and the monitor/2 call. Also we could remove the guards in most calls as badarg can be raised by monitor/2.
  3. Fix monitor/2 bif to work with {atom(), atom()} tuples by sending the appropriate disterl command.
  4. Change the server_ref/0 type in gen_server.

pguyot avatar Aug 06 '25 18:08 pguyot

i do not know if somebody is still working on this issue.

i found that it's still not possible to

gen_server:call({server, remote_node}, <<"hello world from atomvm">>) because monitor/2 does not except {atom(), atom()}. as long as this holds true i will be using:

call(ServerRef, Request, TimeoutMs) ->
    GetPortOrPid = fun(Object, Is) ->
                       P = whereis(Object),
                       Isa = Is(P),
                       if Isa == true -> P; true -> undefined end
    end,

    TypeAndId = fun({Service, Node}) when is_atom(Service) andalso is_atom(Node) ->
                        {port, GetPortOrPid(network_port, fun is_port/1)}
                        ;
                   (Ref) when is_atom(Ref) ->
                        {process, GetPortOrPid(Ref, fun is_pid/1)}
                        ;
                   (Ref) when is_pid(Ref) ->
                        {process, Ref}
                        ;
                   (_) ->
                        {undefined, undefined}
                end,
    {Type, Id} = TypeAndId(ServerRef),
    MonitorRef = monitor(Type, Id),
    ...

elsbiet avatar Oct 20 '25 08:10 elsbiet

it seems to me that this issue has been resolved (had a look at the code but did not test (yet)). is this assumption correct

elsbiet avatar Dec 09 '25 07:12 elsbiet

sad to write that this issue is still unresolved - the pgm terminates with Return value: error.

maybe i missed something ..

elsbiet avatar Dec 10 '25 13:12 elsbiet

in gen.erl line 52 the call MonitorRef = monitor(process, ServerRef) fails when ServerRef is not a local pid.

applying the same (i know, it's far from beeing perfect) workaround to gen:call/4 as previously applied to gen_server:call/3 results in successful calls gen_server:call({service, 'remote server'}, Request).

elsbiet avatar Dec 10 '25 14:12 elsbiet