From 9a4f7576f8b85e87686705d0b6cf8e778a5cb7a8 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Thu, 21 Sep 2017 13:43:52 +0200 Subject: stdlib: Add 'send_request' and 'wait_response' to generic behaviors Simplify and encourage users to do more async work. The usage pattern is already available in the 'rpc' module and similar usages are available in other languages and standards. Currently async calls can be implemented via cast or regular messages, but the user needs to implement it in both client and server. In the implementation in this commit the server does not need to know that the client are making async calls. `wait_response(Promise)` returns `{reply, Reply} | {error, Reason}`. `wait_response(Promise, Timeout)` returns `{reply, Reply}` | {error, Reason} or `timeout` if there is a client-side timeout. The reason for the `reply` tuple is that `Reply` may be positive or negative answer. That is, `{reply, {ok, Value}}` and `{reply, {error, Reason}}` looks better than `{ok, {error, Reason}}` or `{ok, {ok, Value}}`. We also need to encapsulate the return value to differentiate between client timeouts and the server response that may be the atom `timeout`. We don't want do `exit(timeout)` since in that case is is not possible to do non-blocking wait_response without catching the call to `wait_response(Promise, 0)`. `check_response(Message, ReqId)` returns `{reply, Reply}` if the `Message` is a reply from the server associated with the handle `ReqId`. Otherwise it returns `no_reply`. `check_response/2` is introduced to be able to handle concurrent async_calls in a receive loop or in a handle_info callback in another gen_server. ``` Promise = gen:send_request(..), . . . dispatch(State) -> receive Msg -> case gen:check_response(Msg, Promise) of {reply, Reply} -> {Reply, State}; no_reply -> State0 = handle_msg(Msg, State0), dispatch(State0) end end. ``` Both wait_response and check_response leaves the monitor until a proper response is received or the moniter fires. To be able to poll for response. The functions in rpc are named async_call and yield but OTB decided to use the names 'send_request', 'wait_response' and 'check_response' here instead. Returns error tuples in case of server (or network) errors in 'check_response' and 'wait_response'. For some cases, for example named process not existing, the 'send_request' part in 'gen:call' exited, this is now catched and a 'DOWN' message is faked so that all error handling can be done when handling the response. This enables to extend the functionality with check_response_m(Msg, MapWithRequestIds) --- lib/wx/test/wx_basic_SUITE.erl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'lib/wx/test') diff --git a/lib/wx/test/wx_basic_SUITE.erl b/lib/wx/test/wx_basic_SUITE.erl index ad03a378de..16b531be6c 100644 --- a/lib/wx/test/wx_basic_SUITE.erl +++ b/lib/wx/test/wx_basic_SUITE.erl @@ -394,10 +394,19 @@ wx_object(Config) -> {call, {Frame,Panel}, _} = wx_object:call(Frame, fun(US) -> US end), ?m(false, wxWindow:getParent(Panel) =:= Frame), ?m(true, wx:equal(wxWindow:getParent(Panel),Frame)), + flush(), + ReqId = wx_object:send_request(Frame, fun(_US) -> timer:sleep(10), yes end), + timeout = wx_object:wait_response(ReqId, 0), + {reply, {call, yes, {Me,ReqId}}} = wx_object:wait_response(ReqId, 1000), + ReqId2 = wx_object:send_request(Frame, yes), + [Msg] = flush(), + no_reply = wx_object:check_response(Msg, ReqId), + {reply, {call, yes, {Me,ReqId2}}} = wx_object:check_response(Msg, ReqId2), + FramePid = wx_object:get_pid(Frame), io:format("wx_object pid ~p~n",[FramePid]), FramePid ! foo3, - ?m([{info, foo3}|_], flush()), + ?m([{info, foo3}], flush()), ?m(ok, wx_object:cast(Frame, fun(_) -> hehe end)), ?m([{cast, hehe}|_], flush()), -- cgit v1.2.1