Browse Source

Add hibernate support for websockets.

Return {ok, Req, State, hibernate}
or {reply, Data, Req, State, hibernate} to hibernate the websocket
process and save up memory and CPU. You should hibernate processes
that will receive few messages. So probably most of them.
Loïc Hoguin 14 years ago
parent
commit
648921b8cf
1 changed files with 27 additions and 8 deletions
  1. 27 8
      src/cowboy_http_websocket.erl

+ 27 - 8
src/cowboy_http_websocket.erl

@@ -13,7 +13,8 @@
 %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 
 -module(cowboy_http_websocket).
 -module(cowboy_http_websocket).
--export([upgrade/3]).
+-export([upgrade/3]). %% API.
+-export([handler_loop/4]). %% Internal.
 
 
 -include("include/http.hrl").
 -include("include/http.hrl").
 
 
@@ -24,7 +25,8 @@
 	challenge = undefined :: undefined | binary(),
 	challenge = undefined :: undefined | binary(),
 	timeout = infinity :: timeout(),
 	timeout = infinity :: timeout(),
 	messages = undefined :: undefined | {atom(), atom(), atom()},
 	messages = undefined :: undefined | {atom(), atom(), atom()},
-	eop :: tuple()
+	eop :: tuple(),
+	hibernate = false :: boolean()
 }).
 }).
 
 
 -spec upgrade(module(), any(), #http_req{}) -> ok.
 -spec upgrade(module(), any(), #http_req{}) -> ok.
@@ -95,7 +97,7 @@ websocket_handshake(State=#state{origin=Origin, challenge=Challenge},
 		 {<<"Sec-WebSocket-Location">>, Location},
 		 {<<"Sec-WebSocket-Location">>, Location},
 		 {<<"Sec-WebSocket-Origin">>, Origin}],
 		 {<<"Sec-WebSocket-Origin">>, Origin}],
 		Challenge, Req#http_req{resp_state=waiting}),
 		Challenge, Req#http_req{resp_state=waiting}),
-	handler_loop(State#state{messages=Transport:messages()},
+	handler_before_loop(State#state{messages=Transport:messages()},
 		Req2, HandlerState, <<>>).
 		Req2, HandlerState, <<>>).
 
 
 -spec websocket_location(atom(), binary(), inet:ip_port(), binary())
 -spec websocket_location(atom(), binary(), inet:ip_port(), binary())
@@ -107,11 +109,21 @@ websocket_location(_Any, Host, Port, Path) ->
 	<< "ws://", Host/binary, ":",
 	<< "ws://", Host/binary, ":",
 		(list_to_binary(integer_to_list(Port)))/binary, Path/binary >>.
 		(list_to_binary(integer_to_list(Port)))/binary, Path/binary >>.
 
 
--spec handler_loop(#state{}, #http_req{}, any(), binary()) -> ok.
+-spec handler_before_loop(#state{}, #http_req{}, any(), binary()) -> ok.
-handler_loop(State=#state{messages={OK, Closed, Error}, timeout=Timeout},
+handler_before_loop(State=#state{hibernate=true},
 		Req=#http_req{socket=Socket, transport=Transport},
 		Req=#http_req{socket=Socket, transport=Transport},
 		HandlerState, SoFar) ->
 		HandlerState, SoFar) ->
 	Transport:setopts(Socket, [{active, once}]),
 	Transport:setopts(Socket, [{active, once}]),
+	erlang:hibernate(?MODULE, handler_loop, [State#state{hibernate=false},
+		Req, HandlerState, SoFar]);
+handler_before_loop(State, Req=#http_req{socket=Socket, transport=Transport},
+		HandlerState, SoFar) ->
+	Transport:setopts(Socket, [{active, once}]),
+	handler_loop(State, Req, HandlerState, SoFar).
+
+-spec handler_loop(#state{}, #http_req{}, any(), binary()) -> ok.
+handler_loop(State=#state{messages={OK, Closed, Error}, timeout=Timeout},
+		Req=#http_req{socket=Socket}, HandlerState, SoFar) ->
 	receive
 	receive
 		{OK, Socket, Data} ->
 		{OK, Socket, Data} ->
 			websocket_data(State, Req, HandlerState,
 			websocket_data(State, Req, HandlerState,
@@ -122,7 +134,7 @@ handler_loop(State=#state{messages={OK, Closed, Error}, timeout=Timeout},
 			handler_terminate(State, Req, HandlerState, {error, Reason});
 			handler_terminate(State, Req, HandlerState, {error, Reason});
 		Message ->
 		Message ->
 			handler_call(State, Req, HandlerState,
 			handler_call(State, Req, HandlerState,
-				SoFar, Message, fun handler_loop/4)
+				SoFar, Message, fun handler_before_loop/4)
 	after Timeout ->
 	after Timeout ->
 		websocket_close(State, Req, HandlerState, {normal, timeout})
 		websocket_close(State, Req, HandlerState, {normal, timeout})
 	end.
 	end.
@@ -131,7 +143,7 @@ handler_loop(State=#state{messages={OK, Closed, Error}, timeout=Timeout},
 websocket_data(State, Req, HandlerState, << 255, 0, _Rest/bits >>) ->
 websocket_data(State, Req, HandlerState, << 255, 0, _Rest/bits >>) ->
 	websocket_close(State, Req, HandlerState, {normal, closed});
 	websocket_close(State, Req, HandlerState, {normal, closed});
 websocket_data(State, Req, HandlerState, <<>>) ->
 websocket_data(State, Req, HandlerState, <<>>) ->
-	handler_loop(State, Req, HandlerState, <<>>);
+	handler_before_loop(State, Req, HandlerState, <<>>);
 websocket_data(State, Req, HandlerState, Data) ->
 websocket_data(State, Req, HandlerState, Data) ->
 	websocket_frame(State, Req, HandlerState, Data, binary:first(Data)).
 	websocket_frame(State, Req, HandlerState, Data, binary:first(Data)).
 
 
@@ -146,7 +158,7 @@ websocket_frame(State=#state{eop=EOP}, Req, HandlerState, Data, 0) ->
 				Rest, {websocket, Frame}, fun websocket_data/4);
 				Rest, {websocket, Frame}, fun websocket_data/4);
 		nomatch ->
 		nomatch ->
 			%% @todo We probably should allow limiting frame length.
 			%% @todo We probably should allow limiting frame length.
-			handler_loop(State, Req, HandlerState, Data)
+			handler_before_loop(State, Req, HandlerState, Data)
 	end;
 	end;
 websocket_frame(State, Req, HandlerState, _Data, _FrameType) ->
 websocket_frame(State, Req, HandlerState, _Data, _FrameType) ->
 	websocket_close(State, Req, HandlerState, {error, badframe}).
 	websocket_close(State, Req, HandlerState, {error, badframe}).
@@ -157,9 +169,16 @@ handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState,
 	try Handler:websocket_handle(Message, Req, HandlerState) of
 	try Handler:websocket_handle(Message, Req, HandlerState) of
 		{ok, Req2, HandlerState2} ->
 		{ok, Req2, HandlerState2} ->
 			NextState(State, Req2, HandlerState2, RemainingData);
 			NextState(State, Req2, HandlerState2, RemainingData);
+		{ok, Req2, HandlerState2, hibernate} ->
+			NextState(State#state{hibernate=true},
+				Req2, HandlerState2, RemainingData);
 		{reply, Data, Req2, HandlerState2} ->
 		{reply, Data, Req2, HandlerState2} ->
 			websocket_send(Data, Req2),
 			websocket_send(Data, Req2),
 			NextState(State, Req2, HandlerState2, RemainingData);
 			NextState(State, Req2, HandlerState2, RemainingData);
+		{reply, Data, Req2, HandlerState2, hibernate} ->
+			websocket_send(Data, Req2),
+			NextState(State#state{hibernate=true},
+				Req2, HandlerState2, RemainingData);
 		{shutdown, Req2, HandlerState2} ->
 		{shutdown, Req2, HandlerState2} ->
 			websocket_close(State, Req2, HandlerState2, {normal, shutdown})
 			websocket_close(State, Req2, HandlerState2, {normal, shutdown})
 	catch Class:Reason ->
 	catch Class:Reason ->