aboutsummaryrefslogtreecommitdiffstats
path: root/lib/wx
diff options
context:
space:
mode:
authorDan Gudmundsson <[email protected]>2011-12-02 16:26:45 +0100
committerDan Gudmundsson <[email protected]>2011-12-06 10:13:49 +0100
commit8b6f6db0f5faddc970a7867aecdb03f3cde5fa78 (patch)
tree9a1798efd7150d9f416d2d32180dc4be5cac3732 /lib/wx
parent50b488753358d38348f2d4051bfcea8d860e739b (diff)
downloadotp-8b6f6db0f5faddc970a7867aecdb03f3cde5fa78.tar.gz
otp-8b6f6db0f5faddc970a7867aecdb03f3cde5fa78.tar.bz2
otp-8b6f6db0f5faddc970a7867aecdb03f3cde5fa78.zip
[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.
Diffstat (limited to 'lib/wx')
-rw-r--r--lib/wx/api_gen/wx_extra/wxEvtHandler.erl2
-rw-r--r--lib/wx/src/gen/wxEvtHandler.erl4
-rw-r--r--lib/wx/src/wx_object.erl59
-rw-r--r--lib/wx/src/wxe_server.erl43
-rw-r--r--lib/wx/test/Makefile2
-rw-r--r--lib/wx/test/wx_basic_SUITE.erl76
-rw-r--r--lib/wx/test/wx_obj_test.erl86
-rw-r--r--lib/wx/test/wx_test_lib.hrl7
8 files changed, 219 insertions, 60 deletions
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()).