From 8b6f6db0f5faddc970a7867aecdb03f3cde5fa78 Mon Sep 17 00:00:00 2001
From: Dan Gudmundsson <dgud@erlang.org>
Date: Fri, 2 Dec 2011 16:26:45 +0100
Subject: [wx] Avoid deadlock in handle_sync_event

Avoid sending cb messages to the wx_object, since it may deadlock.
Instead send it to the wxe_server which reads the state from the wx_object's
process_dictionary.

Ugly but it's the only way I can avoid the deadlock.
---
 lib/wx/api_gen/wx_extra/wxEvtHandler.erl |  2 +-
 lib/wx/src/gen/wxEvtHandler.erl          |  4 +-
 lib/wx/src/wx_object.erl                 | 59 ++++------------------
 lib/wx/src/wxe_server.erl                | 43 +++++++++++++++-
 lib/wx/test/Makefile                     |  2 +-
 lib/wx/test/wx_basic_SUITE.erl           | 76 +++++++++++++++++++++++++++-
 lib/wx/test/wx_obj_test.erl              | 86 ++++++++++++++++++++++++++++++++
 lib/wx/test/wx_test_lib.hrl              |  7 ++-
 8 files changed, 219 insertions(+), 60 deletions(-)
 create mode 100644 lib/wx/test/wx_obj_test.erl

(limited to 'lib')

diff --git a/lib/wx/api_gen/wx_extra/wxEvtHandler.erl b/lib/wx/api_gen/wx_extra/wxEvtHandler.erl
index c6810eb32c..080ebfa49f 100644
--- a/lib/wx/api_gen/wx_extra/wxEvtHandler.erl
+++ b/lib/wx/api_gen/wx_extra/wxEvtHandler.erl
@@ -76,7 +76,7 @@ parse_opts([{callback,Fun}|R], Opts) when is_function(Fun) ->
     %% Check Fun Arity?
     parse_opts(R, Opts#evh{cb=Fun});
 parse_opts([callback|R], Opts) ->
-    parse_opts(R, Opts#evh{cb=1});
+    parse_opts(R, Opts#evh{cb=self()});
 parse_opts([{userData, UserData}|R],Opts) ->
     parse_opts(R, Opts#evh{userdata=UserData});
 parse_opts([{skip, Skip}|R],Opts) when is_boolean(Skip) ->
diff --git a/lib/wx/src/gen/wxEvtHandler.erl b/lib/wx/src/gen/wxEvtHandler.erl
index f155351b66..820c2b7a58 100644
--- a/lib/wx/src/gen/wxEvtHandler.erl
+++ b/lib/wx/src/gen/wxEvtHandler.erl
@@ -1,7 +1,7 @@
 %%
 %% %CopyrightBegin%
 %%
-%% Copyright Ericsson AB 2008-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2011. All Rights Reserved.
 %%
 %% The contents of this file are subject to the Erlang Public License,
 %% Version 1.1, (the "License"); you may not use this file except in
@@ -95,7 +95,7 @@ parse_opts([{callback,Fun}|R], Opts) when is_function(Fun) ->
     %% Check Fun Arity?
     parse_opts(R, Opts#evh{cb=Fun});
 parse_opts([callback|R], Opts) ->
-    parse_opts(R, Opts#evh{cb=1});
+    parse_opts(R, Opts#evh{cb=self()});
 parse_opts([{userData, UserData}|R],Opts) ->
     parse_opts(R, Opts#evh{userdata=UserData});
 parse_opts([{skip, Skip}|R],Opts) when is_boolean(Skip) ->
diff --git a/lib/wx/src/wx_object.erl b/lib/wx/src/wx_object.erl
index 82c4cfbad5..bc85cd93d4 100644
--- a/lib/wx/src/wx_object.erl
+++ b/lib/wx/src/wx_object.erl
@@ -1,7 +1,7 @@
 %%
 %% %CopyrightBegin%
 %%
-%% Copyright Ericsson AB 2008-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2011. All Rights Reserved.
 %%
 %% The contents of this file are subject to the Erlang Public License,
 %% Version 1.1, (the "License"); you may not use this file except in
@@ -226,9 +226,11 @@ call(Name, Request, Timeout) when is_atom(Name) orelse is_pid(Name) ->
 %% Invokes handle_cast(Request, State) in the server
 
 cast(#wx_ref{state=Pid}, Request) when is_pid(Pid) ->
-    Pid ! {'$gen_cast',Request};
+    Pid ! {'$gen_cast',Request},
+    ok;
 cast(Name, Request) when is_atom(Name) orelse is_pid(Name) ->
-    Name ! {'$gen_cast',Request}.
+    Name ! {'$gen_cast',Request},
+    ok.
 
 %% @spec (Ref::wxObject()) -> pid()
 %% @doc Get the pid of the object handle.
@@ -258,9 +260,10 @@ init_it(Starter, self, Name, Mod, Args, Options) ->
     init_it(Starter, self(), Name, Mod, Args, Options);
 init_it(Starter, Parent, Name, Mod, Args, [WxEnv|Options]) ->
     case WxEnv of
-	undefined -> ok;	
+	undefined -> ok;
 	_ -> wx:set_env(WxEnv)
     end,
+    put('_wx_object_', {Mod,'_wx_init_'}),
     Debug = debug_options(Name, Options),
     case catch Mod:init(Args) of
 	{#wx_ref{} = Ref, State} ->
@@ -350,57 +353,16 @@ handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod) ->
 	{noreply, NState, Time1} ->
 	    loop(Parent, Name, NState, Mod, Time1, []);
 	{stop, Reason, Reply, NState} ->
-	    {'EXIT', R} = 
+	    {'EXIT', R} =
 		(catch terminate(Reason, Name, Msg, Mod, NState, [])),
 	    reply(From, Reply),
 	    exit(R);
 	Other -> handle_common_reply(Other, Name, Msg, Mod, State, [])
     end;
-
-handle_msg(Msg = {_,_,'_wx_invoke_cb_'}, Parent, Name, State, Mod) ->
-    Reply = dispatch_cb(Msg, Mod, State),
-    handle_no_reply(Reply, Parent, Name, Msg, Mod, State, []);
 handle_msg(Msg, Parent, Name, State, Mod) ->
     Reply = (catch dispatch(Msg, Mod, State)),
     handle_no_reply(Reply, Parent, Name, Msg, Mod, State, []).
-%% @hidden
-dispatch_cb({{Msg=#wx{}, Obj=#wx_ref{}}, _, '_wx_invoke_cb_'}, Mod, State) ->
-    Callback = fun() ->
-		       wxe_util:cast(?WXE_CB_START, <<>>),
-		       case Mod:handle_sync_event(Msg, Obj, State) of
-			   ok -> <<>>;
-			   noreply -> <<>>;
-			   Other ->
-			       Args = [Msg, Obj, State],
-			       MFA = {Mod, handle_sync_event, Args},
-			       exit({bad_return, Other, MFA})
-		       end
-	       end,
-    wxe_server:invoke_callback(Callback),
-    {noreply, State};
-dispatch_cb({Func, ArgList, '_wx_invoke_cb_'}, Mod, State) ->
-    try  %% This don't work yet....
-	[#wx_ref{type=ThisClass}] = ArgList,
-	case Mod:handle_overloaded(Func, ArgList, State) of
-	    {reply, CBReply, NState} -> 
-		ThisClass:send_return_value(Func, CBReply),
-		{noreply, NState};
-	    {reply, CBReply, NState, Time1} -> 
-		ThisClass:send_return_value(Func, CBReply),
-		{noreply, NState, Time1};
-	    {noreply, NState} ->
-		ThisClass:send_return_value(Func, <<>>),
-		{noreply, NState};
-	    {noreply, NState, Time1} ->
-		ThisClass:send_return_value(Func, <<>>),
-		{noreply, NState, Time1};
-	    Other -> Other
-	end
-    catch _Err:Reason ->
-	    %% Hopefully we can release the wx-thread with this
-	    wxe_util:cast(?WXE_CB_RETURN, <<>>),
-	    {'EXIT', {Reason, erlang:get_stacktrace()}}
-    end.
+
 %% @hidden
 handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, Debug) ->
     case catch Mod:handle_call(Msg, From, State) of
@@ -426,9 +388,6 @@ handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, Debug) ->
 	Other ->
 	    handle_common_reply(Other, Name, Msg, Mod, State, Debug)
     end;
-handle_msg(Msg = {_,_,'_wx_invoke_cb_'}, Parent, Name, State, Mod, Debug) ->
-    Reply = dispatch_cb(Msg, Mod, State),
-    handle_no_reply(Reply, Parent, Name, Msg, Mod, State, Debug);
 handle_msg(Msg, Parent, Name, State, Mod, Debug) ->
     Reply = (catch dispatch(Msg, Mod, State)),
     handle_no_reply(Reply, Parent, Name, Msg, Mod, State, Debug).
diff --git a/lib/wx/src/wxe_server.erl b/lib/wx/src/wxe_server.erl
index 69e2189fac..6e982c97f6 100644
--- a/lib/wx/src/wxe_server.erl
+++ b/lib/wx/src/wxe_server.erl
@@ -221,7 +221,7 @@ handle_connect(Object, EvData, From, State0 = #state{users=Users}) ->
     Evs = [#event{object=Object,callback=Callback, cb_handler=CBHandler}|Evs0],
     User = User0#user{events=Evs, evt_handler=Handler},
     State1 = State0#state{users=gb_trees:update(From, User, Users)},
-    if is_function(Callback) ->
+    if is_function(Callback) orelse is_pid(Callback) ->
 	    {FunId, State} = attach_fun(Callback,State1),
 	    Res = wxEvtHandler:connect_impl(CBHandler,Object,
 					    wxEvtHandler:replace_fun_with_id(EvData,FunId)),
@@ -229,6 +229,7 @@ handle_connect(Object, EvData, From, State0 = #state{users=Users}) ->
 		ok     -> {reply,Res,State};
 		_Error -> {reply,Res,State0}
 	    end;
+
        true ->
 	    Res = {call_impl, connect_cb, CBHandler},
 	    {reply, Res, State1}
@@ -239,6 +240,8 @@ invoke_cb({{Ev=#wx{}, Ref=#wx_ref{}}, FunId,_}, _S) ->
     case get(FunId) of
 	Fun when is_function(Fun) ->
 	    invoke_callback(fun() -> Fun(Ev, Ref), <<>> end);
+	Pid when is_pid(Pid) -> %% wx_object sync event
+	    invoke_callback(Pid, Ev, Ref);
 	Err ->
 	    ?log("Internal Error ~p~n",[Err])
     end;
@@ -270,6 +273,44 @@ invoke_callback(Fun) ->
     spawn(CB),
     ok.
 
+invoke_callback(Pid, Ev, Ref) ->
+    Env = get(?WXE_IDENTIFIER),
+    CB = fun() ->
+		 wx:set_env(Env),
+		 wxe_util:cast(?WXE_CB_START, <<>>),
+		 try
+		     case get_wx_object_state(Pid) of
+			 ignore ->
+			     %% Ignore early events
+			     wxEvent:skip(Ref);
+			 {Mod, State} ->
+			     case Mod:handle_sync_event(Ev, Ref, State) of
+				 ok -> ok;
+				 noreply -> ok;
+				 Return -> exit({bad_return, Return})
+			     end
+		     end
+		 catch _:Reason ->
+			 wxEvent:skip(Ref),
+			 ?log("Callback fun crashed with {'EXIT, ~p, ~p}~n",
+			      [Reason, erlang:get_stacktrace()])
+		 end,
+		 wxe_util:cast(?WXE_CB_RETURN, <<>>)
+	 end,
+    spawn(CB),
+    ok.
+
+get_wx_object_state(Pid) ->
+    case process_info(Pid, dictionary) of
+	{dictionary, Dict} ->
+	    case lists:keysearch('_wx_object_',1,Dict) of
+		{value, {'_wx_object_', {_Mod, '_wx_init_'}}} -> ignore;
+		{value, {'_wx_object_', Value}} -> Value;
+		_ -> ignore
+	    end;
+	_ -> ignore
+    end.
+
 new_evt_listener(State) ->
     #wx_env{port=Port} = wx:get_env(),
     _ = erlang:port_control(Port,98,<<>>),
diff --git a/lib/wx/test/Makefile b/lib/wx/test/Makefile
index cf51d7918f..333711789f 100644
--- a/lib/wx/test/Makefile
+++ b/lib/wx/test/Makefile
@@ -27,7 +27,7 @@ PWD = $(shell pwd)
 APPDIR = $(shell dirname $(PWD))
 ERL_COMPILE_FLAGS = -pa $(APPDIR)/ebin
 
-Mods =  wxt wx_test_lib \
+Mods =  wxt wx_test_lib wx_obj_test \
 	wx_app_SUITE \
 	wx_basic_SUITE \
 	wx_event_SUITE \
diff --git a/lib/wx/test/wx_basic_SUITE.erl b/lib/wx/test/wx_basic_SUITE.erl
index 9ad34248a9..46c72bb453 100644
--- a/lib/wx/test/wx_basic_SUITE.erl
+++ b/lib/wx/test/wx_basic_SUITE.erl
@@ -48,7 +48,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
 
 all() -> 
     [create_window, several_apps, wx_api, wx_misc,
-     data_types].
+     data_types, wx_object].
 
 groups() -> 
     [].
@@ -298,3 +298,77 @@ data_types(_Config) ->
     wxClientDC:destroy(CDC),
     %%wx_test_lib:wx_destroy(Frame,Config).
     wx:destroy().
+
+wx_object(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
+wx_object(Config) ->
+    wx:new(),
+    Frame = ?mt(wxFrame, wx_obj_test:start([])),
+    timer:sleep(500),
+    ?m(ok, check_events(flush())),
+
+    Me = self(),
+    ?m({call, foobar, {Me, _}}, wx_object:call(Frame, foobar)),
+    ?m(ok, wx_object:cast(Frame, foobar2)),
+    ?m([{cast, foobar2}], flush()),
+    FramePid = wx_object:get_pid(Frame),
+    io:format("wx_object pid ~p~n",[FramePid]),
+    FramePid ! foo3,
+    ?m([{info, foo3}], flush()),
+
+    ?m(ok, wx_object:cast(Frame, fun(_) -> hehe end)),
+    ?m([{cast, hehe}], flush()),
+    wxWindow:refresh(Frame),
+    ?m([{sync_event, #wx{event=#wxPaint{}}, _}], flush()),
+    ?m(ok, wx_object:cast(Frame, fun(_) -> timer:sleep(200), slept end)),
+    %% The sleep above should not hinder the Paint event below
+    %% Which it did in my buggy handling of the sync_callback
+    wxWindow:refresh(Frame),
+    ?m([{sync_event, #wx{event=#wxPaint{}}, _}], flush()),
+    ?m([{cast, slept}], flush()),
+
+    Monitor = erlang:monitor(process, FramePid),
+    case proplists:get_value(user, Config, false) of
+	false ->
+	    timer:sleep(100),
+	    wxFrame:destroy(Frame);
+	true ->
+	    timer:sleep(500),
+	    ?m(ok, wxFrame:destroy(Frame));
+	_ ->
+	    ?m(ok, wxEvtHandler:connect(Frame, close_window, [{skip,true}])),
+	    wx_test_lib:wait_for_close()
+    end,
+    ?m(ok, receive
+	       {'DOWN', Monitor, _, _, _} ->
+		   ?m([{terminate, wx_deleted}], flush()),
+		   ok
+	   after 1000 ->
+		   Msgs = flush(),
+		   io:format("Error ~p Alive ~p~n",[Msgs, is_process_alive(FramePid)])
+	   end),
+    catch wx:destroy(),
+    ok.
+
+check_events(Msgs) ->
+    check_events(Msgs, 0,0).
+
+check_events([{event, #wx{event=#wxSize{}}}|Rest], Async, Sync) ->
+    check_events(Rest, Async+1, Sync);
+check_events([{sync_event, #wx{event=#wxPaint{}}, Obj}|Rest], Async, Sync) ->
+    ?mt(wxPaintEvent, Obj),
+    check_events(Rest, Async, Sync+1);
+check_events([], Async, Sync) ->
+    case Async > 0 of  %% Test sync explictly
+	true -> ok;
+	false -> {Async, Sync}
+    end.
+
+flush() ->
+    flush([], 500).
+
+flush(Acc, Wait) ->
+    receive
+	Msg -> flush([Msg|Acc], Wait div 10)
+    after Wait ->
+	    lists:reverse(Acc)
+    end.
diff --git a/lib/wx/test/wx_obj_test.erl b/lib/wx/test/wx_obj_test.erl
new file mode 100644
index 0000000000..b4d7640c7e
--- /dev/null
+++ b/lib/wx/test/wx_obj_test.erl
@@ -0,0 +1,86 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(wx_obj_test).
+-include_lib("wx/include/wx.hrl").
+
+-export([start/1]).
+
+%% wx_object callbacks
+-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
+	 handle_sync_event/3, handle_event/2, handle_cast/2]).
+
+-record(state, {frame, panel, opts}).
+
+start(Opts) ->
+    wx_object:start_link(?MODULE, [{parent, self()}, Opts], []).
+
+init(Opts) ->
+    put(parent_pid, proplists:get_value(parent, Opts)),
+    Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Test wx_object", [{size, {500, 400}}]),
+    Sz = wxBoxSizer:new(?wxHORIZONTAL),
+    Panel = wxPanel:new(Frame),
+    wxSizer:add(Sz, Panel, [{flag, ?wxEXPAND}, {proportion, 1}]),
+    wxPanel:connect(Panel, size, [{skip, true}]),
+    wxPanel:connect(Panel, paint, [callback, {userData, proplists:get_value(parent, Opts)}]),
+    wxWindow:show(Frame),
+    {Frame, #state{frame=Frame, panel=Panel, opts=Opts}}.
+
+handle_sync_event(Event = #wx{obj=Panel}, WxEvent, #state{opts=Opts}) ->
+    DC=wxPaintDC:new(Panel),  %% We must create & destroy paintDC, or call wxEvent:skip(WxEvent))
+    wxPaintDC:destroy(DC),    %% in sync_event. Otherwise wx on windows keeps sending the events.
+    Pid = proplists:get_value(parent, Opts),
+    true = is_pid(Pid),
+    Pid ! {sync_event, Event, WxEvent},
+    ok.
+
+handle_event(Event, State = #state{opts=Opts}) ->
+    Pid = proplists:get_value(parent, Opts),
+    Pid ! {event, Event},
+    {noreply, State}.
+
+handle_call(What, From, State) when is_function(What) ->
+    Result = What(State),
+    {reply, {call, Result, From}, State};
+handle_call(What, From, State) ->
+    {reply, {call, What, From}, State}.
+
+handle_cast(What, State = #state{opts=Opts})  when is_function(What) ->
+    Result = What(State),
+    Pid = proplists:get_value(parent, Opts),
+    Pid ! {cast, Result},
+    {noreply, State};
+
+handle_cast(What, State = #state{opts=Opts}) ->
+    Pid = proplists:get_value(parent, Opts),
+    Pid ! {cast, What},
+    {noreply, State}.
+
+handle_info(What, State = #state{opts=Opts}) ->
+    Pid = proplists:get_value(parent, Opts),
+    Pid ! {info, What},
+    {noreply, State}.
+
+terminate(What, #state{opts=Opts}) ->
+    Pid = proplists:get_value(parent, Opts),
+    Pid ! {terminate, What},
+    ok.
+
+code_change(Ver1, Ver2, State = #state{opts=Opts}) ->
+    Pid = proplists:get_value(parent, Opts),
+    Pid ! {code_change, Ver1, Ver2},
+    State.
diff --git a/lib/wx/test/wx_test_lib.hrl b/lib/wx/test/wx_test_lib.hrl
index 34e1e9c6b8..820e8f0050 100644
--- a/lib/wx/test/wx_test_lib.hrl
+++ b/lib/wx/test/wx_test_lib.hrl
@@ -1,7 +1,7 @@
 %%
 %% %CopyrightBegin%
 %% 
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2011. All Rights Reserved.
 %% 
 %% The contents of this file are subject to the Erlang Public License,
 %% Version 1.1, (the "License"); you may not use this file except in
@@ -40,7 +40,6 @@
 
 -define(m(ExpectedRes, Expr),
 	fun() ->
-		{TeStFILe, TeSTLiNe} = {?FILE, ?LINE},
 		AcTuAlReS = (catch (Expr)),
 		case AcTuAlReS of
 		    ExpectedRes ->
@@ -48,8 +47,8 @@
 			AcTuAlReS;
 		    _ ->
 			wx_test_lib:error("Not Matching Actual result was:~n ~p ~n Expected ~s~n",
-					  [AcTuAlReS, ??ExpectedRes], 
-					  TeStFILe,TeSTLiNe),
+					  [AcTuAlReS, ??ExpectedRes],
+					  ?FILE,?LINE),
 			AcTuAlReS
 		end
 	end()).
-- 
cgit v1.2.3