I have created a digraph term on process A and I want to pass this digraph to a process on another node. Whenever I use this digraph on the other process I am getting errors such as:
** {badarg,
[{ets,insert,[598105,{"EPqzYxiM9UV0pplPTRg8vX28h",[]}],[]},
{digraph,do_add_vertex,2,[{file,"digraph.erl"},{line,377}]},
Becasuse a digraph is based on ETS, it appears that this is quite more complicated, making a digraph pretty much standalone on the process it was created. I have found this entry that reveals a similar problem : ETS on a different process
I know I can create the digraph in a server an then connect to it through otp messages, but I cannot do that in my architecture. All nodes can communicate using a specific approach designed to pass the state along as Terms.
It appears to me that having digraphs sent accross different nodes that cannot directly communicate with each other is not possible. Overall, it appears that a digraph cannot be directly serialized. I am thinking that I can "unwind" the digraph as a list of vertices and edges and then transmit and recreate it on the other process (not efficient, performing or elegant). Any thoughts on a better way to serialize it ? Is there a way to serialize the digraph state out of the ETS store ?
Any thoughts ?
You can serialize/deserialize a digraph like this;
serialize({digraph, V, E, N, B}) ->
{ets:tab2list(V),
ets:tab2list(E),
ets:tab2list(N),
B}.
deserialize({VL, EL, NL, B}) ->
DG = {digraph, V, E, N, B} = case B of
true -> digraph:new();
false -> digraph:new([acyclic])
end,
ets:delete_all_objects(V)
ets:delete_all_objects(L)
ets:delete_all_objects(N)
ets:insert(V, VL)
ets:insert(E, EL)
ets:insert(N, NL)
DG.
And this is the code I used to test this;
passer() ->
G = digraph:new(),
V1 = digraph:add_vertex(G),
V2 = digraph:add_vertex(G),
V3 = digraph:add_vertex(G),
digraph:add_edge(G, V1, V2, "edge1"),
digraph:add_edge(G, V1, V3, "edge2"),
Pid = spawn(fun receiver/0),
Pid ! serialize(G).
receiver() ->
receive
SG = {_VL, _EL, _NL, _B} ->
G = deserialize(SG),
io:format("Edges: ~p~n", [digraph:edges(G)]),
io:format("Edges: ~p~n", [digraph:vertices(G)])
end.
Pretty ugly solution but works. I think this is the only way pass digraph between nodes since ets tables cannot be shared between nodes.
Edit: remove unnecessary loops
I have a solution, but it relies on the structure of the variable returned by digrapgh:new(), so I am not sure that it will be compatible with future version.
D = digraph:new(),
...
%some code modifying D
...
{digraph,Vertices,Edges,Neighbours,Cyclic} = D, % get the table Id of the 3 tables containing D values
% It should be preferable to use the record definition of the digraph module
%-record(digraph, {vtab = notable :: ets:tab(),
% etab = notable :: ets:tab(),
% ntab = notable :: ets:tab(),
% cyclic = true :: boolean()}).
LV = tab2list(Vertices),
LE = tab2list(Edges),
LN = tab2list(Neighbours),
...
% then serialize and send all this variables to the target node, ideally in one single tuple like
% {my_digraph_data,LV,LE,LN,Cyclic} or using refs to avoid the mix of messages,
% and on reception on the remote node:
receive
{my_digraph_data,LV,LE,LN,Cyclic} ->
Dcopy = digrapgh:new(Cyclic),
{digraph,Vertices,Edges,Neighbours,_Cyclic} = Dcopy,
ets:insert(Vertices,LV),
ets:insert(Edges,LE),
ets:insert(Neighbours,LN),
Dcopy;
...
and that's it.