Related
I have a ws_handler which receives a websocket connection.
This process waits for input starting with <<"h to login.
It then saves the default Websocket state, a Player process ID (which is spawned with a reference to the current PID), and an outbox message, `<<"turn1">>
websocket_handle({text, <<"h", Name/binary>>}, State) ->
{ok, PID} = player:go(self(), <<"profiledata", Name/binary>>),
erlang:start_timer(1000, self(), <<"Hello!">>),
{reply, {text, <<"You joined, ", Name/binary>>}, {State, PID, <<"turn1">>}};
I want to control data flow from this seperate player process, then have my websocket handler retrieve messages and pass them on to its client via the Outbox element.
So I add this in to manually trigger a message:
websocket_handle({text, <<"myscreen">>}, S = {_, P, _}) ->
gen_server:call(P, myscreen),
{ok, S};
and in player.erl,
handle_call(myscreen, _, {WS, Profile, Cab}) ->
gen_server:cast(WS, myscreenupdate),
{reply, ok, {WS, Profile, Cab}};
Back in ws_handler I expect this to get called:
websocket_info(myscreenupdate, State = {St,P, _}) ->
{reply, {text, <<"My screen update">>}, {St, P, <<"turn2">>}};
But the websocket output in my browser continuously prints turn1, instead of turn2.
I tried gen_server:call in player.erl and I get a timeout crash. I think this is because the {reply tuple of websocket_handle in ws_handler is supposed to be replying to the websocket.. but if that were true, then I'd expect the data to be updated:
websocket_info(myscreenupdate, State = {St,P, _}) ->
{reply, {text, <<"My screen update">>}, {St, P, <<"turn2">>}};
So I'm uncertain what's happening here.
How do I update state from a Player process then have my websocket handler retrieve that State and send it out to its connection?
ws_handler.erl:
-module(ws_handler).
-export([init/2]).
-export([websocket_init/1]).
-export([websocket_handle/2]).
-export([websocket_info/2]).
init(Req, Opts) ->
{cowboy_websocket, Req, Opts}.
websocket_init(State) ->
{ok, State}.
websocket_handle({text, <<"h", Name/binary>>}, State) ->
{ok, PID} = player:go(self(), <<"profiledata", Name/binary>>),
erlang:start_timer(1000, self(), <<"Hello!">>),
{reply, {text, <<"You joined, ", Name/binary>>}, {State, PID, <<"turn1">>}};
websocket_handle({text, <<"myscreen">>}, S = {_, P, _}) ->
gen_server:call(P, myscreen),
{ok, S};
websocket_handle({text, <<"auth", Auth/binary>>}, S = {_St, P, _}) ->
case s:s(P, Auth) of
{ok, Valid} -> {reply, {text, << "Authorized">>}, S};
_ -> {reply, {text, <<"Error">>}, S}
end;
websocket_handle({text, Msg}, S = {_St, P, Outbox}) ->
{reply, {text, Outbox}, S};
websocket_handle(_Data, State) ->
{ok, State}.
websocket_info(myscreenupdate, State = {St,P, _}) ->
{reply, {text, <<"My screen update">>}, {St, P, <<"turn2">>}};
websocket_info({timeout, _Ref, _Ignored}, State = {_, P, Outbox}) ->
erlang:start_timer(1000, self(), <<"This is ignored">>),
Msg = Outbox,
{reply, {text, Msg}, State};
websocket_info(_Info, State) ->
{ok, State}.
player.erl:
-module(player).
-compile(export_all).
handle_call(myscreen, _, {WS, Profile, Cab}) ->
gen_server:cast(WS, myscreenupdate),
{reply, ok, {WS, Profile, Cab}};
handle_call(get_profile, _, State = {_WSPID, Profile, _}) ->
{reply, Profile, State}.
init([WSPID, Profile]) ->
{ok, {WSPID, Profile, null}};
init([WSPID, Profile, CabinetPID]) ->
{ok, {WSPID, Profile, CabinetPID}}.
go(WSPID, Profile, CabinetPID) ->
gen_server:start_link(?MODULE, [WSPID, Profile, CabinetPID], []).
go(WSPID, Profile) ->
gen_server:start_link(?MODULE, [WSPID, Profile], []).
The problem is that cowboy's websocket_info/2 handler will only receive messages sent to the websocket process by using the erlang built-in message operator ! (or, equivalently, the erlang:send/{2,3} functions).
So you should do:
WS ! myscreenupdate
instead of
gen_server:cast(WS, myscreenupdate)
When you use gen_server:cast the message is probably discarded by the cowboy message loop since it's not a recognized message. And when you use gen_server:call you get a deadlock.
I have a crude Erlang-to-Golang port example, passing data from Erlang to Golang and echoing the response.
Problem is the amount of data I can transfer seems to be limited to 2^8 bytes (see below). I thought the problem was probably on the Golang side (not creating a big enough buffer) but replacing bufio.NewReader with bufio.NewReaderSize didn't work. So am now thinking the problem is maybe on the Erlang side.
What do I need to do to increase the buffer size / be able to echo a message larger than 2^8 bytes ?
TIA
justin#justin-ThinkPad-X240:~/work/erlang_golang_port$ erl -pa ebin
Erlang/OTP 17 [erts-6.4.1] [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false]
Eshell V6.4.1 (abort with ^G)
1> port:start("./echo").
<0.35.0>
2> port:ping(65000).
65000
3> port:ping(66000).
** exception error: bad argument
in function port:call_port/1 (port.erl, line 20)
4> port:start("./echo").
<0.40.0>
5> port:ping(66000).
65536
Go
package main
import (
"bufio"
"os"
)
const Delimiter = '\n'
func main() {
// reader := bufio:NewReader(os.Stdin)
reader := bufio.NewReaderSize(os.Stdin, 1677216) // 2**24;
bytes, _ := reader.ReadBytes(Delimiter)
os.Stdout.Write(bytes[:len(bytes)-1])
}
Erlang
-module(port).
-export([start/1, stop/0, init/1]).
-export([ping/1]).
-define(DELIMITER, [10]).
start(ExtPrg) ->
spawn(?MODULE, init, [ExtPrg]).
stop() ->
myname ! stop.
ping(N) ->
Msg=[round(65+26*random:uniform()) || _ <- lists:seq(1, N)],
call_port(Msg).
call_port(Msg) ->
myname ! {call, self(), Msg},
receive
{myname, Result} ->
length(Result)
end.
init(ExtPrg) ->
register(myname, self()),
process_flag(trap_exit, true),
Port = open_port({spawn, ExtPrg}, []),
loop(Port).
loop(Port) ->
receive
{call, Caller, Msg} ->
Port ! {self(), {command, Msg++?DELIMITER}},
receive
{Port, {data, Data}} ->
Caller ! {myname, Data}
end,
loop(Port);
stop ->
Port ! {self(), close},
receive
{Port, closed} ->
exit(normal)
end;
{'EXIT', Port, _Reason} ->
exit(port_terminated)
end.
If you use start_link instead, you'll see that the port crashes after the first command:
1> port:start('go run port.go').
<0.118.0>
2> port:ping(65000).
65000
** exception error: port_terminated
If you change the Go code to run in a loop, this crash can be avoided:
func main() {
for {
// reader := bufio:NewReader(os.Stdin)
reader := bufio.NewReaderSize(os.Stdin, 1677216) // 2**24;
bytes, _ := reader.ReadBytes(Delimiter)
os.Stdout.Write(bytes[:len(bytes)-1])
}
}
Now we can see another interesting result:
33> c(port).
{ok,port}
40> port:ping(66000).
65536
41> port:ping(66000).
464
42> port:ping(66000).
65536
43> port:ping(66000).
464
Now we can see that no data is actually lost, it's just buffered in the port. Since you have not specified a framing protocol (using {packet, N} or {line, N} you are responsible yourself for collecting the data. It also seems that the internal buffer size of an Erlang port is 64K (although I found no documentation of this and no way to change it).
If you change your receive to get all data before returning, you'll every byte each time:
loop(Port) ->
receive
{call, Caller, Msg} ->
Port ! {self(), {command, Msg++?DELIMITER}},
Caller ! {myname, receive_all(Port, 10)},
loop(Port);
stop ->
Port ! {self(), close},
receive
{Port, closed} ->
exit(normal)
end;
{'EXIT', Port, _Reason} ->
exit(port_terminated)
end.
receive_all(Port, Timeout) -> receive_all(Port, Timeout, []).
receive_all(Port, Timeout, Data) ->
receive
{Port, {data, New}} ->
receive_all(Port, Timeout, [New|Data])
after Timeout ->
lists:flatten(lists:reverse(Data))
end.
Running this, we get:
1> c(port).
{ok,port}
2>
3> port:start('go run port.go').
<0.311.0>
4> port:ping(66000).
66000
5> port:ping(66000).
66000
6> port:ping(66000).
66000
2^8 is 256, not 65536 which is 2^16 (or 2 bytes).
For excluding golang program you can simply replace your echo with GNU cat
Default message max size for port communication is 64k, so when your port receives messages, the first one is leading 64k of the string. You can read port again to gain remaining data but you just drop them in your code.
If you really want to communicate on line-based protocol you should configure your port accordingly:
{line, L}
Messages are delivered on a per line basis. Each line
(delimited by the OS-dependent newline sequence) is delivered in one
single message. The message data format is {Flag, Line}, where Flag is
either eol or noeol and Line is the actual data delivered (without the
newline sequence).
L specifies the maximum line length in bytes. Lines longer than this
will be delivered in more than one message, with the Flag set to noeol
for all but the last message. If end of file is encountered anywhere
else than immediately following a newline sequence, the last line will
also be delivered with the Flag set to noeol. In all other cases,
lines are delivered with Flag set to eol.
The {packet, N} and {line, L} settings are mutually exclusive.
So your code would be
Port = open_port({spawn, ExtPrg}, [{line, ?PACKET_SIZE]),
%%...
{call, Caller, Msg} ->
Port ! {self(), {command, Msg++?DELIMITER}},
D = read_data(Port, []),
Caller ! {myname, D},
loop(Port);
%%...
read_data(Port, Prefix) ->
receive
{Port, {data, {noeol, Data}}} ->
read_data(Port, Prefix ++ Data);
{Port, {data, {eol, Data}}} ->
Prefix ++ Data
end.
I have been struggling with the similar problem.
Here the complete code of pipe module.
It allows sent text data to port and read all replies.
-module(apr_pipe).
-export([open_pipe/2,send/2,close/1]).
-export([loop/1,status/1,init/1]).
-include_lib("kernel/include/logger.hrl").
-define(MAX_LINE_LEN,4096).
open_pipe(Path,Cmd) ->
State = #{path => Path, cmd => Cmd},
Pid = spawn(?MODULE,init,[State]),
Pid.
init(State) ->
#{path := Path,cmd := Cmd} = State,
FullFn = filename:join(Path,Cmd),
Settings = [{line,?MAX_LINE_LEN},use_stdio,stderr_to_stdout,hide,binary,exit_status],
Port = erlang:open_port({spawn_executable,FullFn},Settings),
State2 = State#{port => Port, data => #{}},
loop(State2).
send(Pid,Data) -> Pid!{self(),send,Data}.
close(Pid) -> Pid!{self(),send,close}.
status(Pid) -> Pid!{self(),status}.
get_eol() -> <<"\n">>.
loop(State) ->
receive
{_Pid,send,close} ->
?LOG(notice,"got cmd: Close",[]),
Port = maps:get(port,State),
port_close(Port),
exit(normal);
{Pid,send,Data} ->
?LOG(notice,"Send Data ...",[]),
Port = maps:get(port,State),
port_command(Port,Data),
port_command(Port,get_eol()),
State2 = State#{status => data_sent, client => Pid},
loop(State2);
{Pid,status} ->
Port = maps:get(port,State),
?LOG(notice,"Status: Port: ~p State: ~p",[Port,State]),
Pid!{status,Port,State},
loop(State);
% port messages.
{Port, {data,{noeol,Data}}} ->
?LOG(notice,"Port: ~p Data: ~p",[Port,Data]),
CurData = maps:get(cur_data,State,[]),
State2 = State#{cur_data => [Data | CurData]},
loop(State2);
{Port, {data, {eol,Data}}} ->
?LOG(notice,"Port: ~p Data: ~p",[Port,Data]),
CurData = [Data | maps:get(cur_data,State,[])],
CurData2 = lists:reverse(CurData),
Reply = list_to_binary(CurData2),
Client = maps:get(client,State,undefined),
State2 = State#{cur_data => [], client => undefined},
case Client of
undefined -> ?LOG(error,"can not sent reply. Client: ~p Reply: ~p", [Client,Reply]),
loop(State2);
_ -> Client!{reply,Reply},
loop(State2)
end;
{_Port, closed} ->
?LOG(warning, "Port: ~p closed",[]),
exit(normal);
{'EXIT', Port, Reason} ->
?LOG(notice,"Port: ~p exit. Reason: ~p",[Port,Reason]),
exit(Reason);
_Other -> ?LOG(error,"unexpected message: ~p",[_Other]),
exit({error,{unexpected_message,_Other}})
end.
I am trying to return data I get using boss_db over a websocket connection. In this example I want to return the questions I fetch, you can see the logs print out the question, however there is some error which is causing terminated with reason: bad return value: ok.
Below is my code and error:
websocket/fan_games_game_websocket.erl
-module(fan_games_game_websocket, [Req, SessionId]).
-behaviour(boss_service_handler).
-record(state,{users}).
%% API
-export([
init/0,
handle_incoming/4,
handle_join/3,
handle_broadcast/2,
handle_close/4,
handle_info/2,
terminate/2
]).
init() ->
io:format("~p (~p) starting...~n", [?MODULE, self()]),
%timer:send_interval(1000, ping),
{ok, #state{users=dict:new()}}.
handle_join(ServiceName, WebSocketId, State) ->
error_logger:info_msg("~p ~p ~p", [ServiceName, WebSocketId, SessionId]),
#state{users=Users} = State,
{noreply, #state{users=dict:store(WebSocketId, SessionId, Users)}}.
handle_close(Reason, ServiceName, WebSocketId, State) ->
#state{users=Users} = State,
io:format("ServiceName ~p, WebSocketId ~p, SessiondId ~p, close for Reason ~p~n",
[ServiceName, WebSocketId, SessionId, Reason]),
{noreply, #state{users=dict:erase(WebSocketId, Users)}}.
handle_broadcast(Message, State) ->
io:format("Broadcast Message ~p~n",[Message]),
{noreply, State}.
handle_incoming(_ServiceName, WebSocketId, Message, State) ->
error_logger:info_msg(Message),
Questions = boss_db:find(question, []),
error_logger:info_msg("~p~n", [Questions]),
WebSocketId ! {text, list_to_binary(Questions)},
{noreply, State}.
handle_info(state, State) ->
#state{users=Users} = State,
error_logger:info_msg("state:~p~n", [Users]),
{noreply, State};
handle_info(ping, State) ->
error_logger:info_msg("pong:~p~n", [now()]),
{noreply, State};
handle_info(tic_tac, State) ->
#state{users=Users} = State,
Fun = fun(X) when is_pid(X)-> X ! {text, "tic tac"} end,
All = dict:fetch_keys(Users),
[Fun(E) || E <- All],
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
question.erl
-module(question, [Id, GameId, Text]).
-has({answers, many}).
-belongs_to(game).
Update
Here are my updated logs with your suggestions:
Here are the logs from a sample request submitting "a"
11:14:25.401 [info] a
fan_games_game_websocket (<0.299.0>) starting...
11:14:25.401 [info] [{question,"question-2","game-2","Who will have the most rushing yards in the first quarter?"}]
11:14:25.402 [error] ** Boss Service Handler fan_games_game_websocket terminating in handle_incoming/4
for the reason error:badarg
ServiceUrl: "/websocket/game"
WebSocketId: <0.285.0>
SessionId : undefined
Message : <<"a">>
State : {state,{dict,0,16,16,8,80,48,{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},{{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}}
** Stacktrace: [{erlang,list_to_binary,[[{question,"question-2","game-2","Who will have the most rushing yards in the first quarter?"}]],[]},{fan_games_game_websocket,handle_incoming,5,[{file,"/Users/blanecordes/Documents/Code/erlang/fan_game/fan_games/src/websocket/fan_games_game_websocket.erl"},{line,42}]},{boss_service_worker,handle_cast,2,[{file,"src/boss/boss_service_worker.erl"},{line,173}]},{gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,604}]},{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,239}]}]
11:14:25.402 [error] gen_server fan_games_game_websocket terminated with reason: bad return value: ok
11:14:25.402 [error] CRASH REPORT Process <0.297.0> with 0 neighbours exited with reason: bad return value: ok in gen_server:terminate/6 line 744
11:14:25.402 [error] Supervisor {global,boss_service_sup} had child fan_games_game_websocket started with boss_service_worker:start_link(fan_games_game_websocket, <<"/websocket/game">>) at <0.297.0> exit with reason bad return value: ok in context child_terminated
I believe the problem comes from the line
WebSocketId ! {text, <<Questions>>},
in handle_incoming/4, because <<Questions>> is not a proper binary. Try changing it to this:
WebSocketId ! {text, list_to_binary(Questions)},
instead.
I'm trying to parallelize a function but I seem to be having problems (in Erlang).
I have code similar to this:
-module(examples).
-import(lists, [sublist/2, sublist/3]).
motherFunc(Ls) -> childFunc(Ls, false).
childFunc(Ls, false) ->
Pid1 = spawn(example, mergesortP, [lists:sublist(Ls, length(Ls) div 2, self()]),
Pid2 = spawn(example, mergesortP, [lists:sublist(Ls, length(Ls) div 2, self()]),
FirstHalf = [],
SecondHalf = [],
receive
{Pid1, Msg} -> FirstHalf = Msg;
{Pid2, Msg} -> SecondHalf = Msg
end,
SecondHalf ++ FirstHalf;
childFunc([], Pid) -> Pid ! {self(), []};
childFunc([L], Pid) -> Pid ! {self(), [L]};
childFunc(Ls, Pid) ->
Pid1 = spawn(examples, childFunc, [lists:sublist(Ls, length(Ls) div 2, self()]),
Pid2 = spawn(examples, childFunc, [lists:sublist(Ls, length(Ls) div 2 + 1, length(Ls) div 2 + 1), self()]),
FirstHalf = [],
SecondHalf = [],
receive
{Pid1, Msg} -> FirstHalf = Msg;
{Pid2, Msg} -> SecondHalf = Msg
end,
Pid ! {self(), SecondHalf ++ FirstHalf}.
When I run this I'm getting this error message for both threads, and then it does nothing else: Error in process <0.31.0> with exit value: {undef,[{examples,childFunc,[[1,2,3,4],<0.2.0>],[]}]}
The function spawn/3 with the module and the function name as the first and second argument requires that the function is exported.
In fact, any explicit qualified call:
module:function(...)
or implicit qualified call:
apply(module, function, ...)
requires that the function be exported. Whether it's supposedly from the same module or not is irrelevant (1).
In your code, you should either use spawn/1 or export the function:
-export([childFunc/2, mergesortP/2]).
% ...
spawn(?MODULE, mergesortP, [lists:sublist(Ls, length(Ls) div 2), self()])
or
Self = self(),
spawn(fun() -> mergesortP(lists:sublist(Ls, length(Ls) div 2), Self) end.
Note the difference when passing self(). With spawn/3 invocation, self() is evaluated by the spawning process and therefore is its pid. If you call self() within the anonymous function passed to spawn/1, it will evaluate to the pid of the spawned process. So you need to call it before and pass it in a variable.
Additionally, your code is unlikely to work. Indeed, the following section does not do what you mean:
FirstHalf = [],
SecondHalf = [],
receive
{Pid1, Msg} -> FirstHalf = Msg;
{Pid2, Msg} -> SecondHalf = Msg
end,
This will first assign [] to FirstHalf and SecondHalf variables, and then try to match this with the first message that arrived, as variables in Erlang cannot be re-assigned. The second message is ignored as received is only used once. The first result probably isn't an empty list and therefore it will fail with a badmatch error. Instead, it seems you mean to perform parallel execution of the merge an collect the results afterwards. You could write:
FirstHalf = receive {Pid1, Msg1} -> Msg1 end,
SecondHalf = receive {Pid2, Msg2} -> Msg2 end,
This will collect the first half and then the second half.
(1) Actually, a qualified call can in fact be executed by another module, or more precisely a newer version of the module. Qualifying calls to (a loop) function within the same module is a common method to continue execution in a newer version during code upgrades.
The server running in the shell hangs and cannot accept any input. Is there any way to make the program allow input while still running the server loop to accept incoming connections?
-module(cp1).
-export([start/0,accept/1,enter_loop/1,loop/1]).
start() ->
io:format("Started Server:~n"),
{ok, Socket} = gen_tcp:listen(4001, [binary, {packet, 0},{reuseaddr, true},{active, false}]),
accept(Socket).
accept(ListenSocket) ->
case gen_tcp:accept(ListenSocket) of
{ok, Socket} ->
Pid = spawn(fun() ->
io:format("Connection accepted ~n", []),
enter_loop(Socket)
end),
io:format("Pid ~p~n",[Pid]),
gen_tcp:controlling_process(Socket, Pid),
Pid ! ack,
accept(ListenSocket);
Error ->
exit(Error)
end.
enter_loop(Sock) ->
%% make sure to acknowledge owner rights transmission finished
receive ack -> ok end,
loop(Sock).
loop(Sock) ->
%% set soscket options to receive messages directly into itself
inet:setopts(Sock, [{active, once}]),
receive
{tcp, Socket, Data} ->
io:format("Got packet: ~p~n", [Data]),
io:format("Send packet: ~p~n",[Data]),
gen_tcp:send(Socket, Data),
loop(Socket);
{tcp_closed, Socket} ->
io:format("Socket ~p closed~n", [Socket]);
{tcp_error, Socket, Reason} ->
io:format("Error on socket ~p reason: ~p~n", [Socket, Reason])
end.
Spawn a process for server loop instead of calling the start function. Pid = spawn(fun()-> cp1:start() end).