From 8b6f6db0f5faddc970a7867aecdb03f3cde5fa78 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson 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