Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
2891-Improve-response-handling-for-asynchronous...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 2891-Improve-response-handling-for-asynchronous-gen-reque.patch of Package erlang
From 4975e1ecccb64c480857dbd498f16279a8138321 Mon Sep 17 00:00:00 2001 From: Rickard Green <rickard@erlang.org> Date: Tue, 23 Nov 2021 22:05:04 +0100 Subject: [PATCH 1/2] Improve response handling for asynchronous 'gen' requests receive_response/3, wait_response/3, and check_response/3 in 'gen_server', 'gen_statem', and 'gen_event' can now take a collection of request identifiers as argument and handle any responses corresponding a request identifier in the collection. --- lib/stdlib/doc/src/gen_event.xml | 559 ++++++++++++++++++++++----- lib/stdlib/doc/src/gen_server.xml | 534 +++++++++++++++++++++---- lib/stdlib/doc/src/gen_statem.xml | 488 +++++++++++++++++++++-- lib/stdlib/src/gen.erl | 251 ++++++++++-- lib/stdlib/src/gen_event.erl | 193 ++++++++- lib/stdlib/src/gen_server.erl | 218 +++++++++-- lib/stdlib/src/gen_statem.erl | 225 +++++++++-- lib/stdlib/test/dummy_h.erl | 3 + lib/stdlib/test/gen_event_SUITE.erl | 329 +++++++++++++++- lib/stdlib/test/gen_server_SUITE.erl | 327 +++++++++++++++- lib/stdlib/test/gen_statem_SUITE.erl | 309 ++++++++++++++- 11 files changed, 3103 insertions(+), 333 deletions(-) diff --git a/lib/stdlib/doc/src/gen_event.xml b/lib/stdlib/doc/src/gen_event.xml index bb891fce56..676d643072 100644 --- a/lib/stdlib/doc/src/gen_event.xml +++ b/lib/stdlib/doc/src/gen_event.xml @@ -119,24 +119,89 @@ gen_event:stop -----> Module:terminate/2 <datatype> <name name="handler"/> </datatype> + <datatype> <name name="handler_args"/> </datatype> + <datatype> <name name="add_handler_ret"/> </datatype> + <datatype> <name name="del_handler_ret"/> </datatype> + + <datatype> + <name name="emgr_ref"/> + </datatype> + <datatype> <name name="request_id"/> <desc> <p> - A request handle, see <seemfa marker="#send_request/3"> <c>send_request/3</c> </seemfa> + An opaque request identifier. See + <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> for details. </p> </desc> </datatype> + + <datatype> + <name name="request_id_collection"/> + <desc> + <p> + An opaque collection of request identifiers + (<seetype marker="#request_id"><c>request_id()</c></seetype>) + where each request identifier can be associated with a label + chosen by the user. For more information see + <seemfa marker="#reqids_new/0"><c>reqids_new/0</c></seemfa>. + </p> + </desc> + </datatype> + + <datatype> + <name name="response_timeout"/> + <desc> + <p> + Used to set a time limit on how long to wait for a response using + either + <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa>, + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + <seemfa marker="#wait_response/2"><c>wait_response/2</c></seemfa>, + or + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>. + The time unit used is <c>millisecond</c>. Currently valid values: + </p> + <taglist> + <tag><c>0..4294967295</c></tag> + <item><p> + Timeout relative to current time in milliseconds. + </p></item> + <tag><c>infinity</c></tag> + <item><p> + Infinite timeout. That is, the operation will never time out. + </p></item> + <tag><c>{abs, Timeout}</c></tag> + <item><p> + An absolute + <seemfa marker="erts:erlang#monotonic_time/1">Erlang monotonic time</seemfa> + timeout in milliseconds. That is, the operation will time out when + <seemfa marker="erts:erlang#monotonic_time/1"><c>erlang:monotonic_time(millisecond)</c></seemfa> + returns a value larger than or equal to <c>Timeout</c>. <c>Timeout</c> + is not allowed to identify a time further into the future than <c>4294967295</c> + milliseconds. Identifying the timeout using an absolute timeout value + is especially handy when you have a deadline for responses corresponding + to a complete collection of requests + (<seetype marker="#request_id_collection"><c>request_id_collection()</c></seetype>) +, + since you do not have to recalculate the relative time until the deadline + over and over again. + </p></item> + </taglist> + </desc> + </datatype> + </datatypes> <funcs> @@ -312,28 +377,24 @@ gen_event:stop -----> Module:terminate/2 </desc> </func> - <func> - <name since="OTP 23.0">check_response(Msg, RequestId) -> Result</name> - <fsummary>Check if a message is a reply from a server.</fsummary> - <type> - <v>Msg = term()</v> - <v>RequestId = request_id()</v> - <v>Result = {reply, Reply} | no_reply | {error, Error}</v> - <v>Reply = Error = term()</v> - </type> + <name name="check_response" arity="2" since="OTP 23.0"/> + <fsummary>Check if a message is a response to an asynchronous call request + to a generic event manager.</fsummary> <desc> <p> - This function is used to check if a previously received - message, for example by <c>receive</c> or - <c>handle_info/2</c>, is a result of a request made with + Check if <c><anno>Msg</anno></c> is a response corresponding + to the request identifier <c><anno>ReqId</anno></c>. The request + must have been made by <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa>. - If <c>Msg</c> is a reply to the handle <c>RequestId</c> - the result of the request is returned in <c>Reply</c>. - Otherwise returns <c>no_reply</c> and no cleanup is done, and - thus the function shall be invoked repeatedly until a reply - is returned. </p> + <p> + If <c><anno>Msg</anno></c> is a response corresponding to + <c><anno>ReqId</anno></c> the response is returned; otherwise, + <c>no_reply</c> is returned and no cleanup is done, and + thus the function must be invoked repeatedly until a response + is returned. + </p> <p> If the specified event handler is not installed, the function returns <c>{error,bad_module}</c>. If @@ -346,6 +407,74 @@ gen_event:stop -----> Module:terminate/2 </desc> </func> + <func> + <name name="check_response" arity="3" since="OTP 25.0"/> + <fsummary>Check if a message is a response to an asynchronous call request + to a generic event manager.</fsummary> + <desc> + <p> + Check if <c><anno>Msg</anno></c> is a response corresponding + to a request identifier saved in <c><anno>ReqIdCollection</anno></c>. + All request identifiers of <c><anno>ReqIdCollection</anno></c> + must correspond to requests that have been made using + <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> or + <seemfa marker="#send_request/5"><c>send_request/5</c></seemfa>, + and all request must have been made by the process calling this + function. + </p> + <p> + The <c><anno>Label</anno></c> in the response equals the + <c><anno>Label</anno></c> associated with the request identifier + that the response corresponds to. The <c><anno>Label</anno></c> + of a request identifier is associated when + <seemfa marker="#reqids_add/3">saving the request id</seemfa> + in a request identifier collection, or when sending the request using + <seemfa marker="#send_request/5"><c>send_request/5</c></seemfa>. + </p> + <p> + Compared to + <seemfa marker="#check_response/2"><c>check_response/2</c></seemfa>, + the returned result associated with a specific request identifier + or an exception associated with a specific request identifier will + be wrapped in a 3-tuple. The first element of this tuple equals the + value that would have been produced by <c>check_response/2</c>, + the second element equals the <c><anno>Label</anno></c> associated + with the specific request identifier, and the third element + <c><anno>NewReqIdCollection</anno></c> is a possibly modified + request identifier collection. + </p> + <p> + If <c><anno>ReqIdCollection</anno></c> is empty, the atom + <c>no_request</c> will be returned. If <c><anno>Msg</anno></c> + does not correspond to any of the request identifiers in + <c><anno>ReqIdCollection</anno></c>, the atom + <c>no_reply</c> is returned. + </p> + <p> + If <c><anno>Delete</anno></c> equals <c>true</c>, the association + with <c><anno>Label</anno></c> will have been deleted from + <c><anno>ReqIdCollection</anno></c> in the resulting + <c><anno>NewReqIdCollection</anno></c>. If + <c><anno>Delete</anno></c> equals <c>false</c>, + <c><anno>NewReqIdCollection</anno></c> will equal + <c><anno>ReqIdCollection</anno></c>. Note that deleting an + association is not for free and that a collection containing + already handled requests can still be used by subsequent calls to + <c>check_response/3</c>, + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + and + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>. + However, without deleting handled associations, the above calls will + not be able to detect when there are no more outstanding requests to + handle, so you will have to keep track of this some other way than + relying on a <c>no_request</c> return. Note that if you pass a + collection only containing associations of already handled or + abandoned requests to <c>check_response/3</c>, it will always + return <c>no_reply</c>. + </p> + </desc> + </func> + <func> <name since="">delete_handler(EventMgrRef, Handler, Args) -> Result</name> <fsummary>Delete an event handler from a generic event manager.</fsummary> @@ -409,34 +538,29 @@ gen_event:stop -----> Module:terminate/2 does not exist, unless it is specified as <c>Name</c>.</p> </desc> </func> - + <func> - <name since="OTP 24.0">receive_response(RequestId, Timeout) -> Result</name> - <fsummary>Receive for a reply from a server.</fsummary> - <type> - <v>RequestId = request_id()</v> - <v>Reply = term()</v> - <v>Timeout = timeout()</v> - <v>Result = {reply, Reply} | timeout | {error, Error}</v> - <v>Reply = Error = term()</v> - </type> + <name name="receive_response" arity="2" clause_i="1" since="OTP 24.0"/> + <fsummary>Receive a response to an asynchronous call request + to a generic event manager.</fsummary> <desc> <p> - This function is used to receive for a reply of a request made with - <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> - to the event manager. This function must be called from the same - process from which <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> - was made. + Receive a response corresponding to the request identifier + <c><anno>ReqId</anno></c>- The request must have been made by + <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> + to the <c>gen_statem</c> process. This function must be called + from the same process from which + <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> + was made. </p> <p> - <c>Timeout</c> is an integer greater then or equal to zero - that specifies how many milliseconds to wait for an reply, or - the atom <c>infinity</c> to wait indefinitely. - If no reply is received within the specified - time, the function returns <c>timeout</c>. Assuming that the + <c><anno>Timeout</anno></c> specifies how long to wait for + a response. If no response is received within the specified time, + the function returns <c>timeout</c>. Assuming that the server executes on a node supporting aliases (introduced in - OTP 24) no response will be received after a timeout. Otherwise, - a garbage response might be received at a later time. + OTP 24) the request will also be abandoned. That is, no + response will be received after a timeout. Otherwise, a + stray response might be received at a later time. </p> <p> The return value <c>Reply</c> is defined in the return value @@ -453,44 +577,197 @@ gen_event:stop -----> Module:terminate/2 </p> <p> The difference between - <seemfa marker="#wait_response/2"><c>wait_response()</c></seemfa> - and <c>receive_response()</c> is that <c>receive_response()</c> + <seemfa marker="#wait_response/2"><c>wait_response/2</c></seemfa> + and <c>receive_response/2</c> is that <c>receive_response/2</c> abandons the request at timeout so that a potential future - response is ignored, while <c>wait_response()</c> does not. + response is ignored, while <c>wait_response/2</c> does not. </p> </desc> </func> <func> - <name since="OTP 23.0">send_request(EventMgrRef, Handler, Request) -> RequestId</name> - <fsummary>Send a request to a generic event manager.</fsummary> - <type> - <v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}</v> - <v> | {via,Module,ViaName} | pid()</v> - <v> Node = atom()</v> - <v> GlobalName = ViaName = term()</v> - <v>Handler = Module | {Module,Id}</v> - <v> Module = atom()</v> - <v> Id = term()</v> - <v>Request = term()</v> - <v>RequestId = request_id()</v> - </type> + <name name="receive_response" arity="3" since="OTP 25.0"/> + <fsummary>Receive a response to an asynchronous call request + to a generic event manager.</fsummary> + <desc> + <p> + Receive a response corresponding to a request identifier saved + in <c><anno>ReqIdCollection</anno></c>. All request identifiers + of <c><anno>ReqIdCollection</anno></c> must correspond to requests + that have been made using + <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> or + <seemfa marker="#send_request/5"><c>send_request/5</c></seemfa>, + and all request must have been made by the process calling this + function. + </p> + <p> + The <c><anno>Label</anno></c> in the response equals the + <c><anno>Label</anno></c> associated with the request identifier + that the response corresponds to. The <c><anno>Label</anno></c> + of a request identifier is associated when + <seemfa marker="#reqids_add/3">adding the request id</seemfa> + in a request identifier collection, or when sending the request using + <seemfa marker="#send_request/5"><c>send_request/5</c></seemfa>. + </p> + <p> + Compared to + <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa>, + the returned result associated with a specific request identifier + will be wrapped in a 3-tuple. The first element of this tuple equals + the value that would have been produced by <c>receive_response/2</c>, + the second element equals the <c><anno>Label</anno></c> associated + with the specific request identifier, and the third element + <c><anno>NewReqIdCollection</anno></c> is a possibly modified + request identifier collection. + </p> + <p> + If <c><anno>ReqIdCollection</anno></c> is empty, the atom + <c>no_request</c> will be returned. + </p> + <p> + <c><anno>Timeout</anno></c> specifies how long to wait for + a response. If no response is received within the specified time, + the function returns <c>timeout</c>. Assuming that the + server executes on a node supporting aliases (introduced in + OTP 24) all requests identified by <c><anno>ReqIdCollection</anno></c> + will also be abandoned. That is, no responses will be received + after a timeout. Otherwise, stray responses might be received + at a later time. + </p> + <p> + The difference between <c>receive_response/3</c> and + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa> + is that <c>receive_response/3</c> abandons the requests at timeout + so that potential future responses are ignored, while + <c>wait_response/3</c> does not. + </p> + <p> + If <c><anno>Delete</anno></c> equals <c>true</c>, the association + with <c><anno>Label</anno></c> will have been deleted from + <c><anno>ReqIdCollection</anno></c> in the resulting + <c><anno>NewReqIdCollection</anno></c>. If + <c><anno>Delete</anno></c> equals <c>false</c>, + <c><anno>NewReqIdCollection</anno></c> will equal + <c><anno>ReqIdCollection</anno></c>. Note that deleting an + association is not for free and that a collection containing + already handled requests can still be used by subsequent calls to + <c>receive_response/3</c>, + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>, + and + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>. + However, without deleting handled associations, the above calls will + not be able to detect when there are no more outstanding requests to + handle, so you will have to keep track of this some other way than + relying on a <c>no_request</c> return. Note that if you pass a + collection only containing associations of already handled or + abandoned requests to <c>receive_response/3</c>, it will always block + until a timeout determined by <c><anno>Timeout</anno></c> is + triggered. + </p> + </desc> + </func> + + <func> + <name name="reqids_add" arity="3" since="OTP 25.0"/> + <fsummary>Save a request identifier.</fsummary> + <desc> + <p> + Saves <c><anno>ReqId</anno></c> and associates a <c><anno>Label</anno></c> + with the request identifier by adding this information to + <c><anno>ReqIdCollection</anno></c> and returning the + resulting request identifier collection. + </p> + </desc> + </func> + + <func> + <name name="reqids_new" arity="0" since="OTP 25.0"/> + <fsummary>Create a new empty request identifier collection.</fsummary> <desc> + <p> + Returns a new empty request identifier collection. A + request identifier collection can be utilized in order + the handle multiple outstanding requests. + </p> + <p> + Request identifiers of requests made by + <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> + can be saved in a request identifier collection using + <seemfa marker="#reqids_add/3"><c>reqids_add/3</c></seemfa>. + Such a collection of request identifiers can later be used in + order to get one response corresponding to a request in the + collection by passing the collection as argument to + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>, + or, + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>. + </p> <p> - Sends a request to event handler <c>Handler</c> installed in - event manager <c>EventMgrRef</c> and returns a handle - <c>RequestId</c>. The return value <c>RequestId</c> shall - later be used with <seemfa marker="#receive_response/2"> - <c>receive_response/2</c></seemfa>, <seemfa marker="#wait_response/2"> - <c>wait_response/2</c></seemfa>, or <seemfa - marker="#check_response/2"> - <c>check_response/2</c></seemfa> in the same process to - fetch the actual result of the request. + <seemfa marker="#reqids_size/1"><c>reqids_size/1</c></seemfa> + can be used to determine the amount of request identifiers in a + request identifier collection. + </p> + </desc> + </func> + + <func> + <name name="reqids_size" arity="1" since="OTP 25.0"/> + <fsummary>Get size of a request identifier collection.</fsummary> + <desc> + <p> + Returns the amount of request identifiers saved in + <c><anno>ReqIdCollection</anno></c>. + </p> + </desc> + </func> + + <func> + <name name="reqids_to_list" arity="1" since="OTP 25.0"/> + <fsummary>List a request identifiers.</fsummary> + <desc> + <p> + Returns a list of <c>{<anno>ReqId</anno>, <anno>Label</anno>}</c> + tuples which corresponds to all request identifiers with their + associated labels present in the <c><anno>ReqIdCollection</anno></c> + collection. + </p> + </desc> + </func> + + <func> + <name name="send_request" arity="3" since="OTP 23.0"/> + <fsummary>Send an asyncronous call request to a generic event manager.</fsummary> + <desc> + <p> + Sends an asynchronous <c>call</c> request <c><anno>Request</anno></c> to + event handler <c><anno>Handler</anno></c> installed in the event manager + identified by <c><anno>EventMgrRef</anno></c> and returns a request + identifier <c>ReqId</c>. The return value <c><anno>ReqId</anno></c> + shall later be used with + <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa>, + <seemfa marker="#wait_response/2"><c>wait_response/2</c></seemfa>, or + <seemfa marker="#check_response/2"><c>check_response/2</c></seemfa> + to fetch the actual result of the request. Besides passing + the request identifier directly to these functions, it can also be + saved in a request identifier collection using + <seemfa marker="#reqids_add/3"><c>reqids_add/3</c></seemfa>. + Such a collection of request identifiers can later be used in + order to get one response corresponding to a request in the + collection by passing the collection as argument to + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>, or + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>. + If you are about to save the request identifier in a request identifier + collection, you may want to consider using + <seemfa marker="#send_request/5"><c>send_request/5</c></seemfa> + instead. </p> <p> - The call <c>gen_event:wait_response(gen_event:send_request(EventMgrRef,Handler,Request), Timeout)</c> + The call <c>gen_event:receive_response(gen_event:send_request(<anno>EventMgrRef</anno>, + <anno>Handler</anno>, <anno>Request</anno>), Timeout)</c> can be seen as equivalent to - <seemfa marker="#call/3"><c>gen_event:call(EventMgrRef,Handler,Request,Timeout)</c></seemfa>, + <seemfa marker="#call/3"><c>gen_event:call(<anno>EventMgrRef</anno>, + <anno>Handler</anno>, <anno>Request</anno>, Timeout)</c></seemfa>, ignoring the error handling. </p> <p> @@ -504,6 +781,38 @@ gen_event:stop -----> Module:terminate/2 </desc> </func> + + <func> + <name name="send_request" arity="5" since="OTP 25.0"/> + <fsummary>Sends a request to a generic server.</fsummary> + <desc> + <p> + Sends an asynchronous <c>call</c> request <c><anno>Request</anno></c> to + event handler <c><anno>Handler</anno></c> installed in the event manager + identified by <c><anno>EventMgrRef</anno></c>. + The <c><anno>Label</anno></c> will be associated with the request + identifier of the operation and added to the returned request + identifier collection <c><anno>NewReqIdCollection</anno></c>. + The collection can later be used in order to get one response + corresponding to a request in the collection by passing the + collection as argument to + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>, + or, + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>. + </p> + + <p> + The same as calling + <seemfa marker="#reqids_add/3"><c>gen_event:reqids_add</c></seemfa>(<seemfa + marker="#send_request/3"><c>gen_event:send_request</c></seemfa><c>(<anno>EventMgrRef</anno>, + <anno>Handler</anno>, <anno>Request</anno>), <anno>Label</anno>, + <anno>ReqIdCollection</anno>)</c>, but calling <c>send_request/5</c> + is slightly more efficient. + </p> + </desc> + </func> + <func> <name since="">start() -> Result</name> <name since="">start(EventMgrName | Options) -> Result</name> @@ -742,30 +1051,24 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name since="OTP 23.0">wait_response(RequestId, Timeout) -> Result</name> - <fsummary>Wait for a reply from a server.</fsummary> - <type> - <v>RequestId = request_id()</v> - <v>Reply = term()</v> - <v>Timeout = timeout()</v> - <v>Result = {reply, Reply} | timeout | {error, Error}</v> - <v>Reply = Error = term()</v> - </type> + <name name="wait_response" arity="2" since="OTP 23.0"/> + <fsummary>Wait or poll for a response to an asynchronous call request + to a generic event manager.</fsummary> <desc> <p> - This function is used to wait for a reply of a request made with - <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> - to the event manager. This function must be called from the same - process from which <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> - was made. + Wait for a response corresponding to the request identifier + <c><anno>ReqId</anno></c>. The request must have been made by + <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> + to the <c>gen_statem</c> process. This function must be called + from the same process from which + <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> + was made. </p> <p> - <c>Timeout</c> is an integer greater then or equal to zero - that specifies how many milliseconds to wait for an reply, or - the atom <c>infinity</c> to wait indefinitely. - If no reply is received within the specified + <c><anno>WaitTime</anno></c> specifies how long to wait for + a response. If no response is received within the specified time, the function returns <c>timeout</c> and no cleanup is - done, and thus the function must be invoked repeatedly until a + done, and thus the function can be invoked repeatedly until a reply is returned. </p> <p> @@ -783,11 +1086,89 @@ gen_event:stop -----> Module:terminate/2 </p> <p> The difference between - <seemfa marker="#receive_response/2"><c>receive_response()</c></seemfa> - and <c>wait_response()</c> is that <c>receive_response()</c> + <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa> + and <c>wait_response/2</c> is that <c>receive_response/2</c> abandons the request at timeout so that a potential future - response is ignored, while <c>wait_response()</c> does not. + response is ignored, while <c>wait_response/2</c> does not. + </p> + </desc> + </func> + + <func> + <name name="wait_response" arity="3" since="OTP 25.0"/> + <fsummary>Wait or poll for a response to an asynchronous call request + to a generic event manager.</fsummary> + <desc> + <p> + Wait for a response corresponding to a request identifier saved + in <c><anno>ReqIdCollection</anno></c>. All request identifiers + of <c><anno>ReqIdCollection</anno></c> must correspond to requests + that have been made using + <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa> or + <seemfa marker="#send_request/5"><c>send_request/5</c></seemfa>, + and all request must have been made by the process calling this + function. </p> + <p> + The <c><anno>Label</anno></c> in the response equals the + <c><anno>Label</anno></c> associated with the request identifier + that the response corresponds to. The <c><anno>Label</anno></c> + of a request identifier is associated when + <seemfa marker="#reqids_add/3">saving the request id</seemfa> in + a request identifier collection, or when sending the request using + <seemfa marker="#send_request/5"><c>send_request/5</c></seemfa>. + </p> + <p> + Compared to + <seemfa marker="#wait_response/2"><c>wait_response/2</c></seemfa>, + the returned result associated with a specific request identifier + or an exception associated with a specific request identifier will + be wrapped in a 3-tuple. The first element of this tuple equals the + value that would have been produced by <c>wait_response/2</c>, + the second element equals the <c><anno>Label</anno></c> associated + with the specific request identifier, and the third element + <c><anno>NewReqIdCollection</anno></c> is a possibly modified + request identifier collection. + </p> + <p> + If <c><anno>ReqIdCollection</anno></c> is empty, <c>no_request</c> + will be returned. If no response is received before the + <c><anno>WaitTime</anno></c> timeout has triggered, the atom + <c>timeout</c> is returned. It is valid to continue waiting for a + response as many times as needed up until a response has been received + and completed by <c>check_response()</c>, <c>receive_response()</c>, + or <c>wait_response()</c>. + </p> + <p> + The difference between + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa> + and <c>wait_response/3</c> is that <c>receive_response/3</c> + abandons requests at timeout so that a potential future + responses are ignored, while <c>wait_response/3</c> does not. + </p> + <p> + If <c><anno>Delete</anno></c> equals <c>true</c>, the association + with <c><anno>Label</anno></c> will have been deleted from + <c><anno>ReqIdCollection</anno></c> in the resulting + <c><anno>NewReqIdCollection</anno></c>. If + <c><anno>Delete</anno></c> equals <c>false</c>, + <c><anno>NewReqIdCollection</anno></c> will equal + <c><anno>ReqIdCollection</anno></c>. Note that deleting an + association is not for free and that a collection containing + already handled requests can still be used by subsequent calls to + <c>wait_response/3</c>, + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>, + and + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>. + However, without deleting handled associations, the above calls will + not be able to detect when there are no more outstanding requests to + handle, so you will have to keep track of this some other way than + relying on a <c>no_request</c> return. Note that if you pass a + collection only containing associations of already handled or + abandoned requests to <c>wait_response/3</c>, it will always block + until a timeout determined by <c><anno>WaitTime</anno></c> is + triggered and then return <c>no_reply</c>. + </p> </desc> </func> diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml index 8f07adc1ac..d11799f2ab 100644 --- a/lib/stdlib/doc/src/gen_server.xml +++ b/lib/stdlib/doc/src/gen_server.xml @@ -412,12 +412,67 @@ gen_server:abcast -----> Module:handle_cast/2 <name name="request_id"/> <desc> <p> - A request handle, see - <seemfa marker="#send_request/2"> <c>send_request/2</c> </seemfa> + An opaque request identifier. See + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> for details. </p> </desc> </datatype> + + <datatype> + <name name="request_id_collection"/> + <desc> + <p> + An opaque collection of request identifiers + (<seetype marker="#request_id"><c>request_id()</c></seetype>) + where each request identifier can be associated with a label + chosen by the user. For more information see + <seemfa marker="#reqids_new/0"><c>reqids_new/0</c></seemfa>. + </p> + </desc> + </datatype> + + <datatype> + <name name="response_timeout"/> + <desc> + <p> + Used to set a time limit on how long to wait for a response using + either + <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa>, + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + <seemfa marker="#wait_response/2"><c>wait_response/2</c></seemfa>, + or + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>. + The time unit used is <c>millisecond</c>. Currently valid values: + </p> + <taglist> + <tag><c>0..4294967295</c></tag> + <item><p> + Timeout relative to current time in milliseconds. + </p></item> + <tag><c>infinity</c></tag> + <item><p> + Infinite timeout. That is, the operation will never time out. + </p></item> + <tag><c>{abs, Timeout}</c></tag> + <item><p> + An absolute + <seemfa marker="erts:erlang#monotonic_time/1">Erlang monotonic time</seemfa> + timeout in milliseconds. That is, the operation will time out when + <seemfa marker="erts:erlang#monotonic_time/1"><c>erlang:monotonic_time(millisecond)</c></seemfa> + returns a value larger than or equal to <c>Timeout</c>. <c>Timeout</c> + is not allowed to identify a time further into the future than <c>4294967295</c> + milliseconds. Identifying the timeout using an absolute timeout value + is especially handy when you have a deadline for responses corresponding + to a complete collection of requests + (<seetype marker="#request_id_collection"><c>request_id_collection()</c></seetype>) +, + since you do not have to recalculate the relative time until the deadline + over and over again. + </p></item> + </taglist> + </desc> + </datatype> </datatypes> @@ -607,31 +662,101 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name name="check_response" arity="2" since="OTP-23"/> - <fsummary>Check if a message is a reply from a server.</fsummary> + <name name="check_response" arity="2" since="OTP 23.0"/> + <fsummary>Check if a message is a response from a server.</fsummary> <desc> <p> - This function is used to check if a previously received - message, for example by <c>receive</c> or - <c>handle_info/2</c>, is a result of a request made with - <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>. - If <c><anno>Msg</anno></c> is a reply - to the handle <c><anno>RequestId</anno></c> - the result of the request is returned in <c><anno>Reply</anno></c>. - Otherwise returns <c>no_reply</c> and no cleanup is done, and - thus the function must be invoked repeatedly until a reply - is returned. + Check if <c><anno>Msg</anno></c> is a response corresponding + to the request identifier <c><anno>ReqId</anno></c>. The request + must have been made by + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>, + and it must have been made by the same process calling + this function. </p> + <p> + If <c><anno>Msg</anno></c> is a response corresponding to + <c><anno>ReqId</anno></c> the response is returned; otherwise, + <c>no_reply</c> is returned and no cleanup is done, and + thus the function must be invoked repeatedly until a response + is returned. + </p> <p> - The return value <c>Reply</c> is passed from the return value - of <c>Module:handle_call/3</c>. + The return value <c><anno>Reply</anno></c> is passed from the + return value of <c>Module:handle_call/3</c>. </p> <p> The function returns an error if the <c>gen_server</c> - dies before or during this request. + died before a reply was sent. </p> </desc> </func> + + <func> + <name name="check_response" arity="3" since="OTP 25.0"/> + <fsummary>Check if a message is a response from a server.</fsummary> + <desc> + <p> + Check if <c><anno>Msg</anno></c> is a response corresponding + to a request identifier saved in <c><anno>ReqIdCollection</anno></c>. + All request identifiers of <c><anno>ReqIdCollection</anno></c> + must correspond to requests that have been made using + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> or + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>, + and all request must have been made by the process calling this + function. + </p> + <p> + The <c><anno>Label</anno></c> in the response equals the + <c><anno>Label</anno></c> associated with the request identifier + that the response corresponds to. The <c><anno>Label</anno></c> + of a request identifier is associated when + <seemfa marker="#reqids_add/3">saving the request id</seemfa> + in a request identifier collection, or when sending the request using + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>. + </p> + <p> + Compared to + <seemfa marker="#check_response/2"><c>check_response/2</c></seemfa>, + the returned result associated with a specific request identifier + or an exception associated with a specific request identifier will + be wrapped in a 3-tuple. The first element of this tuple equals the + value that would have been produced by <c>check_response/2</c>, + the second element equals the <c><anno>Label</anno></c> associated + with the specific request identifier, and the third element + <c><anno>NewReqIdCollection</anno></c> is a possibly modified + request identifier collection. + </p> + <p> + If <c><anno>ReqIdCollection</anno></c> is empty, the atom + <c>no_request</c> will be returned. If <c><anno>Msg</anno></c> + does not correspond to any of the request identifiers in + <c><anno>ReqIdCollection</anno></c>, the atom + <c>no_reply</c> is returned. + </p> + <p> + If <c><anno>Delete</anno></c> equals <c>true</c>, the association + with <c><anno>Label</anno></c> will have been deleted from + <c><anno>ReqIdCollection</anno></c> in the resulting + <c><anno>NewReqIdCollection</anno></c>. If + <c><anno>Delete</anno></c> equals <c>false</c>, + <c><anno>NewReqIdCollection</anno></c> will equal + <c><anno>ReqIdCollection</anno></c>. Note that deleting an + association is not for free and that a collection containing + already handled requests can still be used by subsequent calls to + <c>check_response/3</c>, + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + and + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>. + However, without deleting handled associations, the above calls will + not be able to detect when there are no more outstanding requests to + handle, so you will have to keep track of this some other way than + relying on a <c>no_request</c> return. Note that if you pass a + collection only containing associations of already handled or + abandoned requests to <c>check_response/3</c>, it will always + return <c>no_reply</c>. + </p> + </desc> + </func> <func> <name name="enter_loop" arity="3" since=""/> @@ -781,45 +906,123 @@ gen_server:abcast -----> Module:handle_cast/2 <func> <name name="receive_response" arity="2" since="OTP 24.0"/> - <fsummary>Receive a reply from a server.</fsummary> + <fsummary>Receive a response from a server.</fsummary> <desc> <p> - This function is used to receive a reply to a request made with - <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> - to a <c>gen_server</c> process. - This function must be called by the same process that called - <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>. + Receive a response corresponding to the request identifier + <c><anno>ReqId</anno></c>. The request must have been made by + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>, + and it must have been made by the same process calling + this function. </p> <p> - <c><anno>Timeout</anno></c> is an integer - that specifies how many milliseconds - to wait for a reply, or the atom <c>infinity</c> - to wait indefinitely. - If no reply is received within the specified time, - the function returns <c>timeout</c>. - If the server executes on a node supporting aliases - (introduced in OTP 24) no response will be received - after a time-out. Otherwise, a garbage response - might be received at a later time. + <c><anno>Timeout</anno></c> specifies how long to wait for + a response. If no response is received within the specified time, + the function returns <c>timeout</c>. Assuming that the + server executes on a node supporting aliases (introduced in + OTP 24) the request will also be abandoned. That is, no + response will be received after a timeout. Otherwise, a + stray response might be received at a later time. </p> <p> - A returned <c><anno>Reply</anno></c> is passed from - the return value of <c>Module:handle_call/3</c>. + The return value <c><anno>Reply</anno></c> is passed from the + return value of <c>Module:handle_call/3</c>. </p> <p> The function returns an error if the <c>gen_server</c> - dies before or during this request. + died before a reply was sent. </p> <p> - The difference between - <seemfa marker="#wait_response/2"><c>wait_response()</c></seemfa> - and <c>receive_response()</c> is that <c>receive_response()</c> - abandons the request at time-out so that a potential late - response is ignored, while <c>wait_response()</c> does not. + The difference between <c>receive_response/2</c> and + <seemfa marker="#wait_response/2"><c>wait_response/2</c></seemfa> + is that <c>receive_response/2</c> abandons the request at + timeout so that a potential future response is ignored, while + <c>wait_response/2</c> does not. </p> </desc> </func> + <func> + <name name="receive_response" arity="3" since="OTP 25.0"/> + <fsummary>Receive a response from a server.</fsummary> + <desc> + <p> + Receive a response corresponding to a request identifier saved + in <c><anno>ReqIdCollection</anno></c>. All request identifiers + of <c><anno>ReqIdCollection</anno></c> must correspond to requests + that have been made using + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> or + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>, + and all request must have been made by the process calling this + function. + </p> + <p> + The <c><anno>Label</anno></c> in the response equals the + <c><anno>Label</anno></c> associated with the request identifier + that the response corresponds to. The <c><anno>Label</anno></c> + of a request identifier is associated when + <seemfa marker="#reqids_add/3">adding the request id</seemfa> + in a request identifier collection, or when sending the request using + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>. + </p> + <p> + Compared to + <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa>, + the returned result associated with a specific request identifier + will be wrapped in a 3-tuple. The first element of this tuple equals + the value that would have been produced by <c>receive_response/2</c>, + the second element equals the <c><anno>Label</anno></c> associated + with the specific request identifier, and the third element + <c><anno>NewReqIdCollection</anno></c> is a possibly modified + request identifier collection. + </p> + <p> + If <c><anno>ReqIdCollection</anno></c> is empty, the atom + <c>no_request</c> will be returned. + </p> + <p> + <c><anno>Timeout</anno></c> specifies how long to wait for + a response. If no response is received within the specified time, + the function returns <c>timeout</c>. Assuming that the + server executes on a node supporting aliases (introduced in + OTP 24) all requests identified by <c><anno>ReqIdCollection</anno></c> + will also be abandoned. That is, no responses will be received + after a timeout. Otherwise, stray responses might be received + at a later time. + </p> + <p> + The difference between <c>receive_response/3</c> and + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa> + is that <c>receive_response/3</c> abandons the requests at timeout + so that potential future responses are ignored, while + <c>wait_response/3</c> does not. + </p> + <p> + If <c><anno>Delete</anno></c> equals <c>true</c>, the association + with <c><anno>Label</anno></c> will have been deleted from + <c><anno>ReqIdCollection</anno></c> in the resulting + <c><anno>NewReqIdCollection</anno></c>. If + <c><anno>Delete</anno></c> equals <c>false</c>, + <c><anno>NewReqIdCollection</anno></c> will equal + <c><anno>ReqIdCollection</anno></c>. Note that deleting an + association is not for free and that a collection containing + already handled requests can still be used by subsequent calls to + <c>receive_response/3</c>, + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>, + and + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>. + However, without deleting handled associations, the above calls will + not be able to detect when there are no more outstanding requests to + handle, so you will have to keep track of this some other way than + relying on a <c>no_request</c> return. Note that if you pass a + collection only containing associations of already handled or + abandoned requests to <c>receive_response/3</c>, it will always block + until a timeout determined by <c><anno>Timeout</anno></c> is + triggered. + </p> + </desc> + </func> + <func> <name name="reply" arity="2" since=""/> <fsummary>Send a reply to a client.</fsummary> @@ -843,29 +1046,105 @@ gen_server:abcast -----> Module:handle_cast/2 </desc> </func> + <func> + <name name="reqids_add" arity="3" since="OTP 25.0"/> + <fsummary>Save a request identifier.</fsummary> + <desc> + <p> + Saves <c><anno>ReqId</anno></c> and associates a <c><anno>Label</anno></c> + with the request identifier by adding this information to + <c><anno>ReqIdCollection</anno></c> and returning the + resulting request identifier collection. + </p> + </desc> + </func> + + <func> + <name name="reqids_new" arity="0" since="OTP 25.0"/> + <fsummary>Create a new empty request identifier collection.</fsummary> + <desc> + <p> + Returns a new empty request identifier collection. A + request identifier collection can be utilized in order + the handle multiple outstanding requests. + </p> + <p> + Request identifiers of requests made by + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> + can be saved in a request identifier collection using + <seemfa marker="#reqids_add/3"><c>reqids_add/3</c></seemfa>. + Such a collection of request identifiers can later be used in + order to get one response corresponding to a request in the + collection by passing the collection as argument to + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>, + or, + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>. + </p> + <p> + <seemfa marker="#reqids_size/1"><c>reqids_size/1</c></seemfa> + can be used to determine the amount of request identifiers in a + request identifier collection. + </p> + </desc> + </func> + + <func> + <name name="reqids_size" arity="1" since="OTP 25.0"/> + <fsummary>Get size of a request identifier collection.</fsummary> + <desc> + <p> + Returns the amount of request identifiers saved in + <c><anno>ReqIdCollection</anno></c>. + </p> + </desc> + </func> + + <func> + <name name="reqids_to_list" arity="1" since="OTP 25.0"/> + <fsummary>List a request identifiers.</fsummary> + <desc> + <p> + Returns a list of <c>{<anno>ReqId</anno>, <anno>Label</anno>}</c> + tuples which corresponds to all request identifiers with their + associated labels present in the <c><anno>ReqIdCollection</anno></c> + collection. + </p> + </desc> + </func> + <func> <name name="send_request" arity="2" since="OTP 23.0"/> <fsummary>Sends a request to a generic server.</fsummary> <desc> <p> - Sends a request to the <c><anno>ServerRef</anno></c> - of the <c>gen_server</c> process - and returns a handle <c><anno>RequestId</anno></c>. - The returned handle shall later be used with - <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa>, - <seemfa marker="#wait_response/2"><c>wait_response/2</c></seemfa>, or - <seemfa marker="#check_response/2"><c>check_response/2</c></seemfa> - to fetch the actual result of the request. + Sends an asynchronous <c>call</c> request <c><anno>Request</anno></c> + to the <c>gen_server</c> process identified by <c><anno>ServerRef</anno></c> + and returns a request identifier <c><anno>ReqId</anno></c>. The return + value <c><anno>ReqId</anno></c> shall later be used with + <seemfa marker="#receive_response/2"> <c>receive_response/2</c></seemfa>, + <seemfa marker="#wait_response/2"> <c>wait_response/2</c></seemfa>, or + <seemfa marker="#check_response/2"> <c>check_response/2</c></seemfa> + to fetch the actual result of the request. Besides passing + the request identifier directly to these functions, it can also be + saved in a request identifier collection using + <seemfa marker="#reqids_add/3"><c>reqids_add/3</c></seemfa>. + Such a collection of request identifiers can later be used in + order to get one response corresponding to a request in the + collection by passing the collection as argument to + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>, or + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>. + If you are about to save the request identifier in a request identifier + collection, you may want to consider using + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa> + instead. </p> <p> - The call - <c> - gen_server:wait_response(gen_server:send_request(<anno>ServerRef</anno>, - <anno>Request</anno>), Timeout) - </c> - can be seen as equivalent to - <seemfa marker="#call/3"><c>gen_server:call(<anno>ServerRef</anno>, <anno>Request</anno>, Timeout)</c></seemfa>, - ignoring the error handling. + The call <c>gen_server:receive_response(gen_server:send_request(<anno>ServerRef</anno>, + <anno>Request</anno>), Timeout)</c> can be seen as equivalent to + <seemfa marker="#call/3"><c>gen_server:call(<anno>ServerRef</anno>, <anno>Request</anno>, + Timeout)</c></seemfa>, ignoring the error handling. </p> <p> The <c>gen_server</c> process calls @@ -886,6 +1165,36 @@ gen_server:abcast -----> Module:handle_cast/2 </desc> </func> + <func> + <name name="send_request" arity="4" since="OTP 25.0"/> + <fsummary>Sends a request to a generic server.</fsummary> + <desc> + <p> + Sends an asynchronous <c>call</c> request <c><anno>Request</anno></c> + to the <c>gen_server</c> process identified by <c><anno>ServerRef</anno></c>. + The <c><anno>Label</anno></c> will be associated with the request + identifier of the operation and added to the returned request + identifier collection <c><anno>NewReqIdCollection</anno></c>. + The collection can later be used in order to get one response + corresponding to a request in the collection by passing the + collection as argument to + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>, + or, + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>. + </p> + + <p> + The same as calling + <seemfa marker="#reqids_add/3"><c>gen_server:reqids_add</c></seemfa>(<seemfa + marker="#send_request/2"><c>gen_server:send_request</c></seemfa><c>(<anno>ServerRef</anno>, + <anno>Request</anno>), <anno>Label</anno>, + <anno>ReqIdCollection</anno>)</c>, but calling <c>send_request/4</c> + is slightly more efficient. + </p> + </desc> + </func> + <func> <name name="start" arity="3" since=""/> <name name="start" arity="4" since=""/> @@ -1028,41 +1337,116 @@ gen_server:abcast -----> Module:handle_cast/2 <func> <name name="wait_response" arity="2" since="OTP 23.0"/> - <fsummary>Wait for a reply from a server.</fsummary> + <fsummary>Wait or poll for a response from a server.</fsummary> <desc> <p> - This function is used to wait for a reply of a request made with - <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> - from the <c>gen_server</c> process. - This function must be called from the same process that called - <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>. + Wait for a response corresponding to the request identifier + <c><anno>ReqId</anno></c>. The request must have been made by + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>, + and it must have been made by the same process calling + this function. </p> <p> - <c><anno>Timeout</anno></c> is an integer - that specifies how many milliseconds to wait for a reply, - or the atom <c>infinity</c> to wait indefinitely. - If no reply is received within the specified time, - the function returns <c>timeout</c> and no cleanup is done; - thus the function can be invoked repeatedly until a + <c><anno>WaitTime</anno></c> specifies how long to wait for + a reply. If no reply is received within the specified + time, the function returns <c>timeout</c> and no cleanup is + done, and thus the function can be invoked repeatedly until a reply is returned. </p> <p> - The return value <c><anno>Reply</anno></c> is passed from - the return value of <c>Module:handle_call/3</c>. + The return value <c><anno>Reply</anno></c> is passed from the + return value of <c>Module:handle_call/3</c>. </p> <p> The function returns an error if the <c>gen_server</c> - dies before or during this request. + died before a reply was sent. </p> <p> The difference between - <seemfa marker="#receive_response/2"><c>receive_response()</c></seemfa> - and <c>wait_response()</c> is that <c>receive_response()</c> + <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa> + and <c>wait_response/2</c> is that <c>receive_response/2</c> abandons the request at time-out so that a potential future - response is ignored, while <c>wait_response()</c> does not. + response is ignored, while <c>wait_response/2</c> does not. </p> </desc> </func> + + <func> + <name name="wait_response" arity="3" since="OTP 25.0"/> + <fsummary>Wait or poll for a response from a server.</fsummary> + <desc> + <p> + Wait for a response corresponding to a request identifier saved + in <c><anno>ReqIdCollection</anno></c>. All request identifiers + of <c><anno>ReqIdCollection</anno></c> must correspond to requests + that have been made using + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> or + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>, + and all request must have been made by the process calling this + function. + </p> + <p> + The <c><anno>Label</anno></c> in the response equals the + <c><anno>Label</anno></c> associated with the request identifier + that the response corresponds to. The <c><anno>Label</anno></c> + of a request identifier is associated when + <seemfa marker="#reqids_add/3">saving the request id</seemfa> in + a request identifier collection, or when sending the request using + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>. + </p> + <p> + Compared to + <seemfa marker="#wait_response/2"><c>wait_response/2</c></seemfa>, + the returned result associated with a specific request identifier + or an exception associated with a specific request identifier will + be wrapped in a 3-tuple. The first element of this tuple equals the + value that would have been produced by <c>wait_response/2</c>, + the second element equals the <c><anno>Label</anno></c> associated + with the specific request identifier, and the third element + <c><anno>NewReqIdCollection</anno></c> is a possibly modified + request identifier collection. + </p> + <p> + If <c><anno>ReqIdCollection</anno></c> is empty, <c>no_request</c> + will be returned. If no response is received before the + <c><anno>WaitTime</anno></c> timeout has triggered, the atom + <c>timeout</c> is returned. It is valid to continue waiting for a + response as many times as needed up until a response has been received + and completed by <c>check_response()</c>, <c>receive_response()</c>, + or <c>wait_response()</c>. + </p> + <p> + The difference between + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa> + and <c>wait_response/3</c> is that <c>receive_response/3</c> + abandons requests at timeout so that a potential future + responses are ignored, while <c>wait_response/3</c> does not. + </p> + <p> + If <c><anno>Delete</anno></c> equals <c>true</c>, the association + with <c><anno>Label</anno></c> will have been deleted from + <c><anno>ReqIdCollection</anno></c> in the resulting + <c><anno>NewReqIdCollection</anno></c>. If + <c><anno>Delete</anno></c> equals <c>false</c>, + <c><anno>NewReqIdCollection</anno></c> will equal + <c><anno>ReqIdCollection</anno></c>. Note that deleting an + association is not for free and that a collection containing + already handled requests can still be used by subsequent calls to + <c>wait_response/3</c>, + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>, + and + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>. + However, without deleting handled associations, the above calls will + not be able to detect when there are no more outstanding requests to + handle, so you will have to keep track of this some other way than + relying on a <c>no_request</c> return. Note that if you pass a + collection only containing associations of already handled or + abandoned requests to <c>wait_response/3</c>, it will always block + until a timeout determined by <c><anno>WaitTime</anno></c> is + triggered and then return <c>no_reply</c>. + </p> + </desc> + </func> </funcs> diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index c1a2b1e529..6711addc1c 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -1624,11 +1624,67 @@ handle_event(_, _, State, Data) -> <name name="request_id"/> <desc> <p> - A request handle, see <seemfa marker="#send_request/2"> <c>send_request/2</c> </seemfa> + An opaque request identifier. See + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> for details. </p> </desc> </datatype> + + <datatype> + <name name="request_id_collection"/> + <desc> + <p> + An opaque collection of request identifiers + (<seetype marker="#request_id"><c>request_id()</c></seetype>) + where each request identifier can be associated with a label + chosen by the user. For more information see + <seemfa marker="#reqids_new/0"><c>reqids_new/0</c></seemfa>. + </p> + </desc> + </datatype> + + <datatype> + <name name="response_timeout"/> + <desc> + <p> + Used to set a time limit on how long to wait for a response using + either + <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa>, + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + <seemfa marker="#wait_response/2"><c>wait_response/2</c></seemfa>, + or + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>. + The time unit used is <c>millisecond</c>. Currently valid values: + </p> + <taglist> + <tag><c>0..4294967295</c></tag> + <item><p> + Timeout relative to current time in milliseconds. + </p></item> + <tag><c>infinity</c></tag> + <item><p> + Infinite timeout. That is, the operation will never time out. + </p></item> + <tag><c>{abs, Timeout}</c></tag> + <item><p> + An absolute + <seemfa marker="erts:erlang#monotonic_time/1">Erlang monotonic time</seemfa> + timeout in milliseconds. That is, the operation will time out when + <seemfa marker="erts:erlang#monotonic_time/1"><c>erlang:monotonic_time(millisecond)</c></seemfa> + returns a value larger than or equal to <c>Timeout</c>. <c>Timeout</c> + is not allowed to identify a time further into the future than <c>4294967295</c> + milliseconds. Identifying the timeout using an absolute timeout value + is especially handy when you have a deadline for responses corresponding + to a complete collection of requests + (<seetype marker="#request_id_collection"><c>request_id_collection()</c></seetype>) +, + since you do not have to recalculate the relative time until the deadline + over and over again. + </p></item> + </taglist> + </desc> + </datatype> </datatypes> <funcs> @@ -1744,15 +1800,15 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="check_response" arity="2" since="OTP-23"/> + <name name="check_response" arity="2" since="OTP 23.0"/> <fsummary>Check if a message is a reply from a server.</fsummary> <desc> <p> - This function is used to check if a previously received - message, for example by <c>receive</c> or - <c>handle_info/2</c>, is a result of a request made with + Check if <c><anno>Msg</anno></c> is a response corresponding + to the request identifier <c><anno>ReqId</anno></c>. The request + must have been made by <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>. - If <c>Msg</c> is a reply to the handle <c>RequestId</c> + If <c>Msg</c> is a reply to the handle <c>ReqId</c> the result of the request is returned in <c>Reply</c>. Otherwise returns <c>no_reply</c> and no cleanup is done, and thus the function shall be invoked repeatedly until a reply @@ -1774,6 +1830,73 @@ handle_event(_, _, State, Data) -> </desc> </func> + <func> + <name name="check_response" arity="3" since="OTP 25.0"/> + <fsummary>Check if a message is a reply from a server.</fsummary> + <desc> + <p> + Check if <c><anno>Msg</anno></c> is a response corresponding + to a request identifier saved in <c><anno>ReqIdCollection</anno></c>. + All request identifiers of <c><anno>ReqIdCollection</anno></c> + must correspond to requests that have been made using + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> or + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>, + and all request must have been made by the process calling this + function. + </p> + <p> + The <c><anno>Label</anno></c> in the response equals the + <c><anno>Label</anno></c> associated with the request identifier + that the response corresponds to. The <c><anno>Label</anno></c> + of a request identifier is associated when + <seemfa marker="#reqids_add/3">saving the request id</seemfa> + in a request identifier collection, or when sending the request using + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>. + </p> + <p> + Compared to + <seemfa marker="#check_response/2"><c>check_response/2</c></seemfa>, + the returned result associated with a specific request identifier + or an exception associated with a specific request identifier will + be wrapped in a 3-tuple. The first element of this tuple equals the + value that would have been produced by <c>check_response/2</c>, + the second element equals the <c><anno>Label</anno></c> associated + with the specific request identifier, and the third element + <c><anno>NewReqIdCollection</anno></c> is a possibly modified + request identifier collection. + </p> + <p> + If <c><anno>ReqIdCollection</anno></c> is empty, the atom + <c>no_request</c> will be returned. If <c><anno>Msg</anno></c> + does not correspond to any of the request identifiers in + <c><anno>ReqIdCollection</anno></c>, the atom + <c>no_reply</c> is returned. + </p> + <p> + If <c><anno>Delete</anno></c> equals <c>true</c>, the association + with <c><anno>Label</anno></c> will have been deleted from + <c><anno>ReqIdCollection</anno></c> in the resulting + <c><anno>NewReqIdCollection</anno></c>. If + <c><anno>Delete</anno></c> equals <c>false</c>, + <c><anno>NewReqIdCollection</anno></c> will equal + <c><anno>ReqIdCollection</anno></c>. Note that deleting an + association is not for free and that a collection containing + already handled requests can still be used by subsequent calls to + <c>check_response/3</c>, + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + and + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>. + However, without deleting handled associations, the above calls will + not be able to detect when there are no more outstanding requests to + handle, so you will have to keep track of this some other way than + relying on a <c>no_request</c> return. Note that if you pass a + collection only containing associations of already handled or + abandoned requests to <c>check_response/3</c>, it will always + return <c>no_reply</c>. + </p> + </desc> + </func> + <func> <name name="enter_loop" arity="4" since="OTP 19.1"/> <fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary> @@ -1871,11 +1994,23 @@ handle_event(_, _, State, Data) -> <func> <name name="receive_response" arity="1" since="OTP 24.0"/> - <name name="receive_response" arity="2" since="OTP 24.0"/> <fsummary>Receive for a reply from a server.</fsummary> <desc> <p> - This function is used to receive for a reply of a request made with + The same as calling + <seemfa marker="#receive_response/2"><c>gen_statem:receive_response(ReqId, + infinity)</c></seemfa>. + </p> + </desc> + </func> + + <func> + <name name="receive_response" arity="2" clause_i="1" since="OTP 24.0"/> + <fsummary>Receive for a reply from a server.</fsummary> + <desc> + <p> + Receive a response corresponding to the request identifier + <c><anno>ReqId</anno></c>- The request must have been made by <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> to the <c>gen_statem</c> process. This function must be called from the same process from which @@ -1883,15 +2018,13 @@ handle_event(_, _, State, Data) -> was made. </p> <p> - <c>Timeout</c> is an integer - that specifies how many milliseconds to wait for an reply, or - the atom <c>infinity</c> to wait indefinitely. Defaults to - <c>infinity</c>. - If no reply is received within the specified - time, the function returns <c>timeout</c>. Assuming that the + <c><anno>Timeout</anno></c> specifies how long to wait for + a response. If no response is received within the specified time, + the function returns <c>timeout</c>. Assuming that the server executes on a node supporting aliases (introduced in - OTP 24) no response will be received after a timeout. Otherwise, - a garbage response might be received at a later time. + OTP 24) the request will also be abandoned. That is, no + response will be received after a timeout. Otherwise, a + stray response might be received at a later time. </p> <p> The return value <c><anno>Reply</anno></c> is generated when a @@ -1908,13 +2041,94 @@ handle_event(_, _, State, Data) -> </p> <p> The difference between - <seemfa marker="#wait_response/2"><c>wait_response()</c></seemfa> - and <c>receive_response()</c> is that <c>receive_response()</c> + <seemfa marker="#wait_response/2"><c>wait_response/2</c></seemfa> + and <c>receive_response/2</c> is that <c>receive_response/2</c> abandons the request at timeout so that a potential future - response is ignored, while <c>wait_response()</c> does not. + response is ignored, while <c>wait_response/2</c> does not. </p> </desc> </func> + + <func> + <name name="receive_response" arity="3" since="OTP 25.0"/> + <fsummary>Receive a response from a server.</fsummary> + <desc> + <p> + Receive a response corresponding to a request identifier saved + in <c><anno>ReqIdCollection</anno></c>. All request identifiers + of <c><anno>ReqIdCollection</anno></c> must correspond to requests + that have been made using + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> or + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>, + and all request must have been made by the process calling this + function. + </p> + <p> + The <c><anno>Label</anno></c> in the response equals the + <c><anno>Label</anno></c> associated with the request identifier + that the response corresponds to. The <c><anno>Label</anno></c> + of a request identifier is associated when + <seemfa marker="#reqids_add/3">adding the request id</seemfa> + in a request identifier collection, or when sending the request using + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>. + </p> + <p> + Compared to + <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa>, + the returned result associated with a specific request identifier + will be wrapped in a 3-tuple. The first element of this tuple equals + the value that would have been produced by <c>receive_response/2</c>, + the second element equals the <c><anno>Label</anno></c> associated + with the specific request identifier, and the third element + <c><anno>NewReqIdCollection</anno></c> is a possibly modified + request identifier collection. + </p> + <p> + If <c><anno>ReqIdCollection</anno></c> is empty, the atom + <c>no_request</c> will be returned. + </p> + <p> + <c><anno>Timeout</anno></c> specifies how long to wait for + a response. If no response is received within the specified time, + the function returns <c>timeout</c>. Assuming that the + server executes on a node supporting aliases (introduced in + OTP 24) all requests identified by <c><anno>ReqIdCollection</anno></c> + will also be abandoned. That is, no responses will be received + after a timeout. Otherwise, stray responses might be received + at a later time. + </p> + <p> + The difference between <c>receive_response/3</c> and + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa> + is that <c>receive_response/3</c> abandons the requests at timeout + so that potential future responses are ignored, while + <c>wait_response/3</c> does not. + </p> + <p> + If <c><anno>Delete</anno></c> equals <c>true</c>, the association + with <c><anno>Label</anno></c> will have been deleted from + <c><anno>ReqIdCollection</anno></c> in the resulting + <c><anno>NewReqIdCollection</anno></c>. If + <c><anno>Delete</anno></c> equals <c>false</c>, + <c><anno>NewReqIdCollection</anno></c> will equal + <c><anno>ReqIdCollection</anno></c>. Note that deleting an + association is not for free and that a collection containing + already handled requests can still be used by subsequent calls to + <c>receive_response/3</c>, + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>, + and + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>. + However, without deleting handled associations, the above calls will + not be able to detect when there are no more outstanding requests to + handle, so you will have to keep track of this some other way than + relying on a <c>no_request</c> return. Note that if you pass a + collection only containing associations of already handled or + abandoned requests to <c>receive_response/3</c>, it will always block + until a timeout determined by <c><anno>Timeout</anno></c> is + triggered. + </p> + </desc> + </func> <func> <name name="reply" arity="1" since="OTP 19.0"/> @@ -1950,20 +2164,98 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="send_request" arity="2" since="OTP-23"/> - <fsummary>Send a request to a <c>gen_statem</c>.</fsummary> + <name name="reqids_add" arity="3" since="OTP 25.0"/> + <fsummary>Save a request identifier.</fsummary> <desc> + <p> + Saves <c><anno>ReqId</anno></c> and associates a <c><anno>Label</anno></c> + with the request identifier by adding this information to + <c><anno>ReqIdCollection</anno></c> and returning the + resulting request identifier collection. + </p> + </desc> + </func> + + <func> + <name name="reqids_new" arity="0" since="OTP 25.0"/> + <fsummary>Create a new empty request identifier collection.</fsummary> + <desc> + <p> + Returns a new empty request identifier collection. A + request identifier collection can be utilized in order + the handle multiple outstanding requests. + </p> <p> - Sends a request to the <c>gen_statem</c> - <seetype marker="#server_ref"><c><anno>ServerRef</anno></c></seetype> - and returns a handle <c><anno>RequestId</anno></c>. - </p> + Request identifiers of requests made by + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> + can be saved in a request identifier collection using + <seemfa marker="#reqids_add/3"><c>reqids_add/3</c></seemfa>. + Such a collection of request identifiers can later be used in + order to get one response corresponding to a request in the + collection by passing the collection as argument to + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>, + or, + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>. + </p> + <p> + <seemfa marker="#reqids_size/1"><c>reqids_size/1</c></seemfa> + can be used to determine the amount of request identifiers in a + request identifier collection. + </p> + </desc> + </func> + + <func> + <name name="reqids_size" arity="1" since="OTP 25.0"/> + <fsummary>Get size of a request identifier collection.</fsummary> + <desc> <p> - The return value <c><anno>RequestId</anno></c> shall later be used with - <seemfa marker="#receive_response/2"> <c>receive_response/1,2</c></seemfa>, - <seemfa marker="#wait_response/2"> <c>wait_response/1,2</c></seemfa>, or + Returns the amount of request identifiers saved in + <c><anno>ReqIdCollection</anno></c>. + </p> + </desc> + </func> + + <func> + <name name="reqids_to_list" arity="1" since="OTP 25.0"/> + <fsummary>List a request identifiers.</fsummary> + <desc> + <p> + Returns a list of <c>{<anno>ReqId</anno>, <anno>Label</anno>}</c> + tuples which corresponds to all request identifiers with their + associated labels present in the <c><anno>ReqIdCollection</anno></c> + collection. + </p> + </desc> + </func> + + <func> + <name name="send_request" arity="2" since="OTP 23.0"/> + <fsummary>Send a request to a <c>gen_statem</c>.</fsummary> + <desc> + <p> + Sends an asynchronous <c>call</c> request <c><anno>Request</anno></c> + to the <c>gen_statem</c> process identified by <c><anno>ServerRef</anno></c> + and returns a request identifier <c><anno>ReqId</anno></c>. The return + value <c><anno>ReqId</anno></c> shall later be used with + <seemfa marker="#receive_response/2"> <c>receive_response/2</c></seemfa>, + <seemfa marker="#wait_response/2"> <c>wait_response/2</c></seemfa>, or <seemfa marker="#check_response/2"> <c>check_response/2</c></seemfa> - to fetch the actual result of the request. + to fetch the actual result of the request. Besides passing + the request identifier directly to these functions, it can also be + saved in a request identifier collection using + <seemfa marker="#reqids_add/3"><c>reqids_add/3</c></seemfa>. + Such a collection of request identifiers can later be used in + order to get one response corresponding to a request in the + collection by passing the collection as argument to + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>, or + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>. + If you are about to save the request identifier in a request identifier + collection, you may want to consider using + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa> + instead. </p> <p> The call <c>gen_statem:wait_response(gen_statem:send_request(ServerRef,Request), Timeout)</c> @@ -1993,6 +2285,36 @@ handle_event(_, _, State, Data) -> </desc> </func> + <func> + <name name="send_request" arity="4" since="OTP 25.0"/> + <fsummary>Sends a request to a generic server.</fsummary> + <desc> + <p> + Sends an asynchronous <c>call</c> request <c><anno>Request</anno></c> + to the <c>gen_statem</c> process identified by <c><anno>ServerRef</anno></c>. + The <c><anno>Label</anno></c> will be associated with the request + identifier of the operation and added to the returned request + identifier collection <c><anno>NewReqIdCollection</anno></c>. + The collection can later be used in order to get one response + corresponding to a request in the collection by passing the + collection as argument to + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>, + <seemfa marker="#wait_response/3"><c>wait_response/3</c></seemfa>, + or, + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>. + </p> + + <p> + The same as calling + <seemfa marker="#reqids_add/3"><c>gen_statem:reqids_add</c></seemfa>(<seemfa + marker="#send_request/2"><c>statem:send_request</c></seemfa><c>(<anno>ServerRef</anno>, + <anno>Request</anno>), <anno>Label</anno>, + <anno>ReqIdCollection</anno>)</c>, but calling <c>send_request/4</c> + is slightly more efficient. + </p> + </desc> + </func> + <func> <name name="start" arity="3" since="OTP 19.0"/> <name name="start" arity="4" since="OTP 19.0"/> @@ -2222,12 +2544,24 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="wait_response" arity="1" since="OTP 23.0"/> - <name name="wait_response" arity="2" since="OTP 23.0"/> + <name name="wait_response" arity="1" clause_i="1" since="OTP 23.0"/> <fsummary>Wait for a reply from a server.</fsummary> <desc> <p> - This function is used to wait for a reply of a request made with + The same as calling + <seemfa marker="#receive_response/2"><c>gen_statem:receive_response(ReqId, + infinity)</c></seemfa>. + </p> + </desc> + </func> + + <func> + <name name="wait_response" arity="2" since="OTP 23.0"/> + <fsummary>Wait or poll for a reply from a server.</fsummary> + <desc> + <p> + Wait for a response corresponding to the request identifier + <c><anno>ReqId</anno></c>. The request must have been made by <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> to the <c>gen_statem</c> process. This function must be called from the same process from which @@ -2235,11 +2569,8 @@ handle_event(_, _, State, Data) -> was made. </p> <p> - <c>Timeout</c> is an integer - that specifies how many milliseconds to wait for an reply, or - the atom <c>infinity</c> to wait indefinitely. Defaults to - <c>infinity</c>. - If no reply is received within the specified + <c><anno>WaitTime</anno></c> specifies how long to wait for + a reply. If no reply is received within the specified time, the function returns <c>timeout</c> and no cleanup is done, and thus the function can be invoked repeatedly until a reply is returned. @@ -2259,13 +2590,90 @@ handle_event(_, _, State, Data) -> </p> <p> The difference between - <seemfa marker="#receive_response/2"><c>receive_response()</c></seemfa> - and <c>wait_response()</c> is that <c>receive_response()</c> + <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa> + and <c>wait_response/2</c> is that <c>receive_response/2</c> abandons the request at timeout so that a potential future - response is ignored, while <c>wait_response()</c> does not. + response is ignored, while <c>wait_response/2</c> does not. </p> </desc> </func> + + <func> + <name name="wait_response" arity="3" since="OTP 25.0"/> + <fsummary>Wait or poll for a response from a server.</fsummary> + <desc> + <p> + Wait for a response corresponding to a request identifier saved + in <c><anno>ReqIdCollection</anno></c>. All request identifiers + of <c><anno>ReqIdCollection</anno></c> must correspond to requests + that have been made using + <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa> or + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>, + and all request must have been made by the process calling this + function. + </p> + <p> + The <c><anno>Label</anno></c> in the response equals the + <c><anno>Label</anno></c> associated with the request identifier + that the response corresponds to. The <c><anno>Label</anno></c> + of a request identifier is associated when + <seemfa marker="#reqids_add/3">saving the request id</seemfa> in + a request identifier collection, or when sending the request using + <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>. + </p> + <p> + Compared to + <seemfa marker="#wait_response/2"><c>wait_response/2</c></seemfa>, + the returned result associated with a specific request identifier + or an exception associated with a specific request identifier will + be wrapped in a 3-tuple. The first element of this tuple equals the + value that would have been produced by <c>wait_response/2</c>, + the second element equals the <c><anno>Label</anno></c> associated + with the specific request identifier, and the third element + <c><anno>NewReqIdCollection</anno></c> is a possibly modified + request identifier collection. + </p> + <p> + If <c><anno>ReqIdCollection</anno></c> is empty, <c>no_request</c> + will be returned. If no response is received before the + <c><anno>WaitTime</anno></c> timeout has triggered, the atom + <c>timeout</c> is returned. It is valid to continue waiting for a + response as many times as needed up until a response has been received + and completed by <c>check_response()</c>, <c>receive_response()</c>, + or <c>wait_response()</c>. + </p> + <p> + The difference between + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa> + and <c>wait_response/3</c> is that <c>receive_response/3</c> + abandons requests at timeout so that a potential future + responses are ignored, while <c>wait_response/3</c> does not. + </p> + <p> + If <c><anno>Delete</anno></c> equals <c>true</c>, the association + with <c><anno>Label</anno></c> will have been deleted from + <c><anno>ReqIdCollection</anno></c> in the resulting + <c><anno>NewReqIdCollection</anno></c>. If + <c><anno>Delete</anno></c> equals <c>false</c>, + <c><anno>NewReqIdCollection</anno></c> will equal + <c><anno>ReqIdCollection</anno></c>. Note that deleting an + association is not for free and that a collection containing + already handled requests can still be used by subsequent calls to + <c>wait_response/3</c>, + <seemfa marker="#check_response/3"><c>check_response/3</c></seemfa>, + and + <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>. + However, without deleting handled associations, the above calls will + not be able to detect when there are no more outstanding requests to + handle, so you will have to keep track of this some other way than + relying on a <c>no_request</c> return. Note that if you pass a + collection only containing associations of already handled or + abandoned requests to <c>wait_response/3</c>, it will always block + until a timeout determined by <c><anno>WaitTime</anno></c> is + triggered and then return <c>no_reply</c>. + </p> + </desc> + </func> </funcs> diff --git a/lib/stdlib/src/gen.erl b/lib/stdlib/src/gen.erl index 1ebd4ac868..363094fb15 100644 --- a/lib/stdlib/src/gen.erl +++ b/lib/stdlib/src/gen.erl @@ -29,23 +29,27 @@ -export([start/5, start/6, debug_options/2, hibernate_after/1, name/1, unregister_name/1, get_proc_name/1, get_parent/0, call/3, call/4, reply/2, - send_request/3, wait_response/2, - receive_response/2, check_response/2, + send_request/3, send_request/5, + wait_response/2, receive_response/2, check_response/2, + wait_response/3, receive_response/3, check_response/3, + reqids_new/0, reqids_size/1, + reqids_add/3, reqids_to_list/1, stop/1, stop/3]). -export([init_it/6, init_it/7]). -export([format_status_header/2, format_status/4]). +-define(MAX_INT_TIMEOUT, 4294967295). -define(default_timeout, 5000). -include("logger.hrl"). %%----------------------------------------------------------------- --export_type( - [reply_tag/0, - request_id/0]). +-export_type([reply_tag/0, + request_id/0, + request_id_collection/0]). -type linkage() :: 'monitor' | 'link' | 'nolink'. -type emgr_name() :: {'local', atom()} @@ -73,6 +77,10 @@ -opaque request_id() :: reference(). +-opaque request_id_collection() :: map(). + +-type response_timeout() :: + 0..?MAX_INT_TIMEOUT | 'infinity' | {abs, integer()}. %%----------------------------------------------------------------- %% Starts a generic process. @@ -278,11 +286,12 @@ get_node(Process) -> node(Process) end. --spec send_request(Name::server_ref(), Label::term(), Request::term()) -> request_id(). -send_request(Process, Label, Request) when is_pid(Process) -> - do_send_request(Process, Label, Request); -send_request(Process, Label, Request) -> - Fun = fun(Pid) -> do_send_request(Pid, Label, Request) end, +-spec send_request(Name::server_ref(), Tag::term(), Request::term()) -> + request_id(). +send_request(Process, Tag, Request) when is_pid(Process) -> + do_send_request(Process, Tag, Request); +send_request(Process, Tag, Request) -> + Fun = fun(Pid) -> do_send_request(Pid, Tag, Request) end, try do_for_proc(Process, Fun) catch exit:Reason -> %% Make send_request async and fake a down message @@ -291,62 +300,232 @@ send_request(Process, Label, Request) -> Mref end. +-spec send_request(Name::server_ref(), Tag::term(), Request::term(), + Label::term(), ReqIdCol::request_id_collection()) -> + request_id_collection(). +send_request(Process, Tag, Request, Label, ReqIdCol) when is_map(ReqIdCol) -> + maps:put(send_request(Process, Tag, Request), Label, ReqIdCol). + -dialyzer({no_improper_lists, do_send_request/3}). -do_send_request(Process, Label, Request) -> - Mref = erlang:monitor(process, Process, [{alias, demonitor}]), - erlang:send(Process, {Label, {self(), [alias|Mref]}, Request}, [noconnect]), - Mref. +do_send_request(Process, Tag, Request) -> + ReqId = erlang:monitor(process, Process, [{alias, demonitor}]), + _ = erlang:send(Process, {Tag, {self(), [alias|ReqId]}, Request}, [noconnect]), + ReqId. %% %% Wait for a reply to the client. %% Note: if timeout is returned monitors are kept. --spec wait_response(RequestId::request_id(), timeout()) -> - {reply, Reply::term()} | 'timeout' | {error, {term(), server_ref()}}. -wait_response(Mref, Timeout) when is_reference(Mref) -> +-spec wait_response(ReqId, Timeout) -> Result when + ReqId :: request_id(), + Timeout :: response_timeout(), + Resp :: {reply, Reply::term()} | {error, {Reason::term(), server_ref()}}, + Result :: Resp | 'timeout'. + +wait_response(ReqId, Timeout) -> + TMO = timeout_value(Timeout), receive - {[alias|Mref], Reply} -> - erlang:demonitor(Mref, [flush]), + {[alias|ReqId], Reply} -> + erlang:demonitor(ReqId, [flush]), {reply, Reply}; - {'DOWN', Mref, _, Object, Reason} -> + {'DOWN', ReqId, _, Object, Reason} -> {error, {Reason, Object}} - after Timeout -> + after TMO -> timeout end. --spec receive_response(RequestId::request_id(), timeout()) -> - {reply, Reply::term()} | 'timeout' | {error, {term(), server_ref()}}. -receive_response(Mref, Timeout) when is_reference(Mref) -> +-spec wait_response(ReqIdCol, Timeout, Delete) -> Result when + ReqIdCol :: request_id_collection(), + Timeout :: response_timeout(), + Delete :: boolean(), + Resp :: {reply, Reply::term()} | {error, {Reason::term(), server_ref()}}, + Result :: {Resp, Label::term(), NewReqIdCol::request_id_collection()} | + 'no_request' | 'timeout'. + +wait_response(ReqIdCol, Timeout, Delete) when map_size(ReqIdCol) == 0, + is_boolean(Delete) -> + _ = timeout_value(Timeout), + no_request; +wait_response(ReqIdCol, Timeout, Delete) when is_map(ReqIdCol), + is_boolean(Delete) -> + TMO = timeout_value(Timeout), receive - {[alias|Mref], Reply} -> - erlang:demonitor(Mref, [flush]), + {[alias|ReqId], _} = Msg when is_map_key(ReqId, ReqIdCol) -> + collection_result(Msg, ReqIdCol, Delete); + {'DOWN', ReqId, _, _, _} = Msg when is_map_key(ReqId, ReqIdCol) -> + collection_result(Msg, ReqIdCol, Delete) + after TMO -> + timeout + end. + +-spec receive_response(ReqId, Timeout) -> Result when + ReqId :: request_id(), + Timeout :: response_timeout(), + Resp :: {reply, Reply::term()} | {error, {Reason::term(), server_ref()}}, + Result :: Resp | 'timeout'. + +receive_response(ReqId, Timeout) -> + TMO = timeout_value(Timeout), + receive + {[alias|ReqId], Reply} -> + erlang:demonitor(ReqId, [flush]), {reply, Reply}; - {'DOWN', Mref, _, Object, Reason} -> + {'DOWN', ReqId, _, Object, Reason} -> {error, {Reason, Object}} - after Timeout -> - erlang:demonitor(Mref, [flush]), + after TMO -> + erlang:demonitor(ReqId, [flush]), receive - {[alias|Mref], Reply} -> + {[alias|ReqId], Reply} -> {reply, Reply} after 0 -> timeout end end. --spec check_response(RequestId::term(), Key::request_id()) -> - {reply, Reply::term()} | 'no_reply' | {error, {term(), server_ref()}}. -check_response(Msg, Mref) when is_reference(Mref) -> +-spec receive_response(ReqIdCol, Timeout, Delete) -> Result when + ReqIdCol :: request_id_collection(), + Timeout :: response_timeout(), + Delete :: boolean(), + Resp :: {reply, Reply::term()} | {error, {Reason::term(), server_ref()}}, + Result :: {Resp, Label::term(), NewReqIdCol::request_id_collection()} + | 'no_request' | 'timeout'. + +receive_response(ReqIdCol, Timeout, Delete) when map_size(ReqIdCol) == 0, + is_boolean(Delete) -> + _ = timeout_value(Timeout), + no_request; +receive_response(ReqIdCol, Timeout, Delete) when is_map(ReqIdCol), + is_boolean(Delete) -> + TMO = timeout_value(Timeout), + receive + {[alias|ReqId], _} = Msg when is_map_key(ReqId, ReqIdCol) -> + collection_result(Msg, ReqIdCol, Delete); + {'DOWN', Mref, _, _, _} = Msg when is_map_key(Mref, ReqIdCol) -> + collection_result(Msg, ReqIdCol, Delete) + after TMO -> + maps:foreach(fun (ReqId, _Label) when is_reference(ReqId) -> + erlang:demonitor(ReqId, [flush]); + (_, _) -> + error(badarg) + end, ReqIdCol), + flush_responses(ReqIdCol), + timeout + end. + +-spec check_response(Msg::term(), ReqIdOrReqIdCol) -> Result when + ReqIdOrReqIdCol :: request_id() | request_id_collection(), + ReqIdResp :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + ReqIdColResp :: {{reply, Reply::term()}, Label::term()} | + {{error, {Reason::term(), server_ref()}}, Label::term()}, + Result :: ReqIdResp | ReqIdColResp | 'no_reply'. + +check_response(Msg, ReqId) when is_reference(ReqId) -> case Msg of - {[alias|Mref], Reply} -> - erlang:demonitor(Mref, [flush]), + {[alias|ReqId], Reply} -> + erlang:demonitor(ReqId, [flush]), {reply, Reply}; - {'DOWN', Mref, _, Object, Reason} -> + {'DOWN', ReqId, _, Object, Reason} -> {error, {Reason, Object}}; _ -> no_reply + end; +check_response(_, _) -> + error(badarg). + +-spec check_response(Msg, ReqIdCol, Delete) -> Result when + Msg :: term(), + ReqIdCol :: request_id_collection(), + Delete :: boolean(), + Resp :: {reply, Reply::term()} | {error, {Reason::term(), server_ref()}}, + Result :: {Resp, Label::term(), NewReqIdCol::request_id_collection()} + | 'no_request' | 'no_reply'. + +check_response(_Msg, ReqIdCol, Delete) when map_size(ReqIdCol) == 0, + is_boolean(Delete) -> + no_request; +check_response(Msg, ReqIdCol, Delete) when is_map(ReqIdCol), + is_boolean(Delete) -> + case Msg of + {[alias|ReqId], _} = Msg when is_map_key(ReqId, ReqIdCol) -> + collection_result(Msg, ReqIdCol, Delete); + {'DOWN', Mref, _, _, _} = Msg when is_map_key(Mref, ReqIdCol) -> + collection_result(Msg, ReqIdCol, Delete); + _ -> + no_reply end. +collection_result({[alias|ReqId], Reply}, ReqIdCol, Delete) -> + _ = erlang:demonitor(ReqId, [flush]), + collection_result({reply, Reply}, ReqId, ReqIdCol, Delete); +collection_result({'DOWN', ReqId, _, Object, Reason}, ReqIdCol, Delete) -> + collection_result({error, {Reason, Object}}, ReqId, ReqIdCol, Delete). + +collection_result(Resp, ReqId, ReqIdCol, false) -> + {Resp, maps:get(ReqId, ReqIdCol), ReqIdCol}; +collection_result(Resp, ReqId, ReqIdCol, true) -> + {Label, NewReqIdCol} = maps:take(ReqId, ReqIdCol), + {Resp, Label, NewReqIdCol}. + +flush_responses(ReqIdCol) -> + receive + {[alias|Mref], _Reply} when is_map_key(Mref, ReqIdCol) -> + flush_responses(ReqIdCol) + after 0 -> + ok + end. + +timeout_value(infinity) -> + infinity; +timeout_value(Timeout) when 0 =< Timeout, Timeout =< ?MAX_INT_TIMEOUT -> + Timeout; +timeout_value({abs, Timeout}) when is_integer(Timeout) -> + case Timeout - erlang:monotonic_time(millisecond) of + TMO when TMO < 0 -> + 0; + TMO when TMO > ?MAX_INT_TIMEOUT -> + error(badarg); + TMO -> + TMO + end; +timeout_value(_) -> + error(badarg). + +-spec reqids_new() -> + NewReqIdCol::request_id_collection(). + +reqids_new() -> + maps:new(). + +-spec reqids_size(request_id_collection()) -> + non_neg_integer(). +reqids_size(ReqIdCol) when is_map(ReqIdCol) -> + maps:size(ReqIdCol); +reqids_size(_) -> + error(badarg). + +-spec reqids_add(ReqId::request_id(), Label::term(), + ReqIdCol::request_id_collection()) -> + NewReqIdCol::request_id_collection(). + +reqids_add(ReqId, _, ReqIdCol) when is_reference(ReqId), + is_map_key(ReqId, ReqIdCol) -> + error(badarg); +reqids_add(ReqId, Label, ReqIdCol) when is_reference(ReqId), + is_map(ReqIdCol) -> + maps:put(ReqId, Label, ReqIdCol); +reqids_add(_, _, _) -> + error(badarg). + +-spec reqids_to_list(ReqIdCol::request_id_collection()) -> + [{ReqId::request_id(), Label::term()}]. + +reqids_to_list(ReqIdCol) when is_map(ReqIdCol) -> + maps:to_list(ReqIdCol); +reqids_to_list(_) -> + error(badarg). + %% %% Send a reply to the client. %% diff --git a/lib/stdlib/src/gen_event.erl b/lib/stdlib/src/gen_event.erl index afb63c5b28..3f5737f304 100644 --- a/lib/stdlib/src/gen_event.erl +++ b/lib/stdlib/src/gen_event.erl @@ -43,7 +43,11 @@ notify/2, sync_notify/2, add_handler/3, add_sup_handler/3, delete_handler/3, swap_handler/3, swap_sup_handler/3, which_handlers/1, call/3, call/4, - send_request/3, wait_response/2, receive_response/2, check_response/2, + send_request/3, send_request/5, + wait_response/2, receive_response/2, check_response/2, + wait_response/3, receive_response/3, check_response/3, + reqids_new/0, reqids_size/1, + reqids_add/3, reqids_to_list/1, wake_hib/5]). -export([init_it/6, @@ -58,7 +62,7 @@ -export([format_log/1, format_log/2]). -export_type([handler/0, handler_args/0, add_handler_ret/0, - del_handler_ret/0, request_id/0]). + del_handler_ret/0, request_id/0, request_id_collection/0]). -record(handler, {module :: atom(), id = false, @@ -150,6 +154,11 @@ -opaque request_id() :: gen:request_id(). +-opaque request_id_collection() :: gen:request_id_collection(). + +-type response_timeout() :: + timeout() | {abs, integer()}. + %%--------------------------------------------------------------------------- -define(NO_CALLBACK, 'no callback module'). @@ -243,32 +252,182 @@ call(M, Handler, Query) -> call1(M, Handler, Query). -spec call(emgr_ref(), handler(), term(), timeout()) -> term(). call(M, Handler, Query, Timeout) -> call1(M, Handler, Query, Timeout). --spec send_request(emgr_ref(), handler(), term()) -> request_id(). -send_request(M, Handler, Query) -> - gen:send_request(M, self(), {call, Handler, Query}). +-spec send_request(EventMgrRef::emgr_ref(), Handler::handler(), Request::term()) -> + ReqId::request_id(). +send_request(M, Handler, Request) -> + try + gen:send_request(M, self(), {call, Handler, Request}) + catch + error:badarg -> + error(badarg, [M, Handler, Request]) + end. --spec wait_response(RequestId::request_id(), timeout()) -> - {reply, Reply::term()} | 'timeout' | {error, {Reason::term(), emgr_ref()}}. -wait_response(RequestId, Timeout) -> - case gen:wait_response(RequestId, Timeout) of +-spec send_request(EventMgrRef::emgr_ref(), + Handler::handler(), + Request::term(), + Label::term(), + ReqIdCollection::request_id_collection()) -> + NewReqIdCollection::request_id_collection(). +send_request(M, Handler, Request, Label, ReqIdCol) -> + try + gen:send_request(M, self(), {call, Handler, Request}, Label, ReqIdCol) + catch + error:badarg -> + error(badarg, [M, Handler, Request, Label, ReqIdCol]) + end. + +-spec wait_response(ReqId, WaitTime) -> Result when + ReqId :: request_id(), + WaitTime :: response_timeout(), + Response :: {reply, Reply::term()} + | {error, {Reason::term(), emgr_ref()}}, + Result :: Response | 'timeout'. + +wait_response(ReqId, WaitTime) -> + try gen:wait_response(ReqId, WaitTime) of {reply, {error, _} = Err} -> Err; Return -> Return + catch + error:badarg -> + error(badarg, [ReqId, WaitTime]) end. --spec receive_response(RequestId::request_id(), timeout()) -> - {reply, Reply::term()} | 'timeout' | {error, {Reason::term(), emgr_ref()}}. -receive_response(RequestId, Timeout) -> - case gen:receive_response(RequestId, Timeout) of +-spec wait_response(ReqIdCollection, WaitTime, Delete) -> Result when + ReqIdCollection :: request_id_collection(), + WaitTime :: response_timeout(), + Delete :: boolean(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), emgr_ref()}}, + Result :: {Response, + Label::term(), + NewReqIdCollection::request_id_collection()} | + 'no_request' | + 'timeout'. + +wait_response(ReqIdCol, WaitTime, Delete) -> + try gen:wait_response(ReqIdCol, WaitTime, Delete) of + {{reply, {error, _} = Err}, Label, NewReqIdCol} -> + {Err, Label, NewReqIdCol}; + Return -> + Return + catch + error:badarg -> + error(badarg, [ReqIdCol, WaitTime, Delete]) + end. + +-spec receive_response(ReqId, Timeout) -> Result when + ReqId :: request_id(), + Timeout :: response_timeout(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), emgr_ref()}}, + Result :: Response | 'timeout'. + +receive_response(ReqId, Timeout) -> + try gen:receive_response(ReqId, Timeout) of {reply, {error, _} = Err} -> Err; Return -> Return + catch + error:badarg -> + error(badarg, [ReqId, Timeout]) end. --spec check_response(Msg::term(), RequestId::request_id()) -> - {reply, Reply::term()} | 'no_reply' | {error, {Reason::term(), emgr_ref()}}. -check_response(Msg, RequestId) -> - case gen:check_response(Msg, RequestId) of +-spec receive_response(ReqIdCollection, Timeout, Delete) -> Result when + ReqIdCollection :: request_id_collection(), + Timeout :: response_timeout(), + Delete :: boolean(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), emgr_ref()}}, + Result :: {Response, + Label::term(), + NewReqIdCollection::request_id_collection()} | + 'no_request' | + 'timeout'. + +receive_response(ReqIdCol, Timeout, Delete) -> + try gen:receive_response(ReqIdCol, Timeout, Delete) of + {{reply, {error, _} = Err}, Label, NewReqIdCol} -> + {Err, Label, NewReqIdCol}; + Return -> + Return + catch + error:badarg -> + error(badarg, [ReqIdCol, Timeout, Delete]) + end. + +-spec check_response(Msg, ReqId) -> Result when + Msg :: term(), + ReqId :: request_id(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), emgr_ref()}}, + Result :: Response | 'no_reply'. + +check_response(Msg, ReqId) -> + try gen:check_response(Msg, ReqId) of {reply, {error, _} = Err} -> Err; Return -> Return + catch + error:badarg -> + error(badarg, [Msg, ReqId]) + end. + +-spec check_response(Msg, ReqIdCollection, Delete) -> Result when + Msg :: term(), + ReqIdCollection :: request_id_collection(), + Delete :: boolean(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), emgr_ref()}}, + Result :: {Response, + Label::term(), + NewReqIdCollection::request_id_collection()} | + 'no_request' | + 'no_reply'. + +check_response(Msg, ReqIdCol, Delete) -> + try gen:check_response(Msg, ReqIdCol, Delete) of + {{reply, {error, _} = Err}, Label, NewReqIdCol} -> + {Err, Label, NewReqIdCol}; + Return -> + Return + catch + error:badarg -> + error(badarg, [Msg, ReqIdCol, Delete]) + end. + +-spec reqids_new() -> + NewReqIdCollection::request_id_collection(). + +reqids_new() -> + gen:reqids_new(). + +-spec reqids_size(ReqIdCollection::request_id_collection()) -> + non_neg_integer(). + +reqids_size(ReqIdCollection) -> + try + gen:reqids_size(ReqIdCollection) + catch + error:badarg -> error(badarg, [ReqIdCollection]) + end. + +-spec reqids_add(ReqId::request_id(), Label::term(), + ReqIdCollection::request_id_collection()) -> + NewReqIdCollection::request_id_collection(). + +reqids_add(ReqId, Label, ReqIdCollection) -> + try + gen:reqids_add(ReqId, Label, ReqIdCollection) + catch + error:badarg -> error(badarg, [ReqId, Label, ReqIdCollection]) + end. + +-spec reqids_to_list(ReqIdCollection::request_id_collection()) -> + [{ReqId::request_id(), Label::term()}]. + +reqids_to_list(ReqIdCollection) -> + try + gen:reqids_to_list(ReqIdCollection) + catch + error:badarg -> error(badarg, [ReqIdCollection]) end. -spec delete_handler(emgr_ref(), handler(), term()) -> term(). diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl index 6e26d5270a..01f6d67663 100644 --- a/lib/stdlib/src/gen_server.erl +++ b/lib/stdlib/src/gen_server.erl @@ -97,8 +97,11 @@ start_monitor/3, start_monitor/4, stop/1, stop/3, call/2, call/3, - send_request/2, wait_response/2, - receive_response/2, check_response/2, + send_request/2, send_request/4, + wait_response/2, receive_response/2, check_response/2, + wait_response/3, receive_response/3, check_response/3, + reqids_new/0, reqids_size/1, + reqids_add/3, reqids_to_list/1, cast/2, reply/2, abcast/2, abcast/3, multi_call/2, multi_call/3, multi_call/4, @@ -123,7 +126,8 @@ -export_type( [from/0, reply_tag/0, - request_id/0]). + request_id/0, + request_id_collection/0]). -export_type( [server_name/0, @@ -195,6 +199,11 @@ -opaque request_id() :: gen:request_id(). +-opaque request_id_collection() :: gen:request_id_collection(). + +-type response_timeout() :: + timeout() | {abs, integer()}. + %%% ----------------------------------------------------------------- %%% Starts a generic server. %%% start(Mod, Args, Options) @@ -373,45 +382,172 @@ call(ServerRef, Request, Timeout) -> %% used with wait_response/2 or check_response/2 to fetch the %% result of the request. --spec send_request( - ServerRef :: server_ref(), - Request :: term() - ) -> - RequestId :: request_id(). +-spec send_request(ServerRef::server_ref(), Request::term()) -> + ReqId::request_id(). + send_request(ServerRef, Request) -> - gen:send_request(ServerRef, '$gen_call', Request). - --spec wait_response( - RequestId :: request_id(), - Timeout :: timeout()) -> - {reply, Reply :: term()} | - 'timeout' | - {error, - {Reason :: term(), ServerRef :: server_ref()}}. -wait_response(RequestId, Timeout) -> - gen:wait_response(RequestId, Timeout). - --spec receive_response( - RequestId :: request_id(), - Timeout :: timeout() - ) -> - {reply, Reply :: term()} | - 'timeout' | - {error, - {Reason :: term(), ServerRef :: server_ref()}}. -receive_response(RequestId, Timeout) -> - gen:receive_response(RequestId, Timeout). - --spec check_response( - Msg :: term(), - RequestId :: request_id() - ) -> - {reply, Reply :: term()} | - 'no_reply' | - {error, - {Reason :: term(), ServerRef :: server_ref()}}. -check_response(Msg, RequestId) -> - gen:check_response(Msg, RequestId). + try + gen:send_request(ServerRef, '$gen_call', Request) + catch + error:badarg -> + error(badarg, [ServerRef, Request]) + end. + +-spec send_request(ServerRef::server_ref(), + Request::term(), + Label::term(), + ReqIdCollection::request_id_collection()) -> + NewReqIdCollection::request_id_collection(). + +send_request(ServerRef, Request, Label, ReqIdCol) -> + try + gen:send_request(ServerRef, '$gen_call', Request, Label, ReqIdCol) + catch + error:badarg -> + error(badarg, [ServerRef, Request, Label, ReqIdCol]) + end. + +-spec wait_response(ReqId, WaitTime) -> Result when + ReqId :: request_id(), + WaitTime :: response_timeout(), + Response :: {reply, Reply::term()} + | {error, {Reason::term(), server_ref()}}, + Result :: Response | 'timeout'. + +wait_response(ReqId, WaitTime) -> + try + gen:wait_response(ReqId, WaitTime) + catch + error:badarg -> + error(badarg, [ReqId, WaitTime]) + end. + +-spec wait_response(ReqIdCollection, WaitTime, Delete) -> Result when + ReqIdCollection :: request_id_collection(), + WaitTime :: response_timeout(), + Delete :: boolean(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: {Response, + Label::term(), + NewReqIdCollection::request_id_collection()} | + 'no_request' | + 'timeout'. + +wait_response(ReqIdCol, WaitTime, Delete) -> + try + gen:wait_response(ReqIdCol, WaitTime, Delete) + catch + error:badarg -> + error(badarg, [ReqIdCol, WaitTime, Delete]) + end. + +-spec receive_response(ReqId, Timeout) -> Result when + ReqId :: request_id(), + Timeout :: response_timeout(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: Response | 'timeout'. + +receive_response(ReqId, Timeout) -> + try + gen:receive_response(ReqId, Timeout) + catch + error:badarg -> + error(badarg, [ReqId, Timeout]) + end. + +-spec receive_response(ReqIdCollection, Timeout, Delete) -> Result when + ReqIdCollection :: request_id_collection(), + Timeout :: response_timeout(), + Delete :: boolean(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: {Response, + Label::term(), + NewReqIdCollection::request_id_collection()} | + 'no_request' | + 'timeout'. + +receive_response(ReqIdCol, Timeout, Delete) -> + try + gen:receive_response(ReqIdCol, Timeout, Delete) + catch + error:badarg -> + error(badarg, [ReqIdCol, Timeout, Delete]) + end. + +-spec check_response(Msg, ReqId) -> Result when + Msg :: term(), + ReqId :: request_id(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: Response | 'no_reply'. + +check_response(Msg, ReqId) -> + try + gen:check_response(Msg, ReqId) + catch + error:badarg -> + error(badarg, [Msg, ReqId]) + end. + +-spec check_response(Msg, ReqIdCollection, Delete) -> Result when + Msg :: term(), + ReqIdCollection :: request_id_collection(), + Delete :: boolean(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: {Response, + Label::term(), + NewReqIdCollection::request_id_collection()} | + 'no_request' | + 'no_reply'. + +check_response(Msg, ReqIdCol, Delete) -> + try + gen:check_response(Msg, ReqIdCol, Delete) + catch + error:badarg -> + error(badarg, [Msg, ReqIdCol, Delete]) + end. + +-spec reqids_new() -> + NewReqIdCollection::request_id_collection(). + +reqids_new() -> + gen:reqids_new(). + +-spec reqids_size(ReqIdCollection::request_id_collection()) -> + non_neg_integer(). + +reqids_size(ReqIdCollection) -> + try + gen:reqids_size(ReqIdCollection) + catch + error:badarg -> error(badarg, [ReqIdCollection]) + end. + +-spec reqids_add(ReqId::request_id(), Label::term(), + ReqIdCollection::request_id_collection()) -> + NewReqIdCollection::request_id_collection(). + +reqids_add(ReqId, Label, ReqIdCollection) -> + try + gen:reqids_add(ReqId, Label, ReqIdCollection) + catch + error:badarg -> error(badarg, [ReqId, Label, ReqIdCollection]) + end. + +-spec reqids_to_list(ReqIdCollection::request_id_collection()) -> + [{ReqId::request_id(), Label::term()}]. + +reqids_to_list(ReqIdCollection) -> + try + gen:reqids_to_list(ReqIdCollection) + catch + error:badarg -> error(badarg, [ReqIdCollection]) + end. %% ----------------------------------------------------------------- %% Make a cast to a generic server. diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 51ac904ee9..f82965d925 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -32,8 +32,12 @@ start_monitor/3,start_monitor/4, stop/1,stop/3, cast/2,call/2,call/3, - send_request/2,wait_response/1,wait_response/2, - receive_response/1,receive_response/2,check_response/2, + send_request/2, send_request/4, + wait_response/1, wait_response/2, wait_response/3, + receive_response/1, receive_response/2, receive_response/3, + check_response/2, check_response/3, + reqids_new/0, reqids_size/1, + reqids_add/3, reqids_to_list/1, enter_loop/4,enter_loop/5,enter_loop/6, reply/1,reply/2]). @@ -72,7 +76,8 @@ reply_action/0, enter_action/0, action/0, - request_id/0 + request_id/0, + request_id_collection/0 ]). %% Old types, not advertised -export_type( @@ -284,6 +289,11 @@ -opaque request_id() :: gen:request_id(). +-opaque request_id_collection() :: gen:request_id_collection(). + +-type response_timeout() :: + timeout() | {abs, integer()}. + %% The state machine init function. It is called only once and %% the server is not running until this function has returned %% an {ok, ...} tuple. Thereafter the state callbacks are called @@ -625,34 +635,189 @@ call(ServerRef, Request, Timeout) -> call_clean(ServerRef, Request, Timeout, Timeout). -spec send_request(ServerRef::server_ref(), Request::term()) -> - RequestId::request_id(). + ReqId::request_id(). send_request(Name, Request) -> - gen:send_request(Name, '$gen_call', Request). - --spec wait_response(RequestId::request_id()) -> - {reply, Reply::term()} | {error, {term(), server_ref()}}. -wait_response(RequestId) -> - gen:wait_response(RequestId, infinity). - --spec wait_response(RequestId::request_id(), timeout()) -> - {reply, Reply::term()} | 'timeout' | {error, {term(), server_ref()}}. -wait_response(RequestId, Timeout) -> - gen:wait_response(RequestId, Timeout). - --spec receive_response(RequestId::request_id()) -> - {reply, Reply::term()} | {error, {term(), server_ref()}}. -receive_response(RequestId) -> - gen:receive_response(RequestId, infinity). - --spec receive_response(RequestId::request_id(), timeout()) -> - {reply, Reply::term()} | 'timeout' | {error, {term(), server_ref()}}. -receive_response(RequestId, Timeout) -> - gen:receive_response(RequestId, Timeout). - --spec check_response(Msg::term(), RequestId::request_id()) -> - {reply, Reply::term()} | 'no_reply' | {error, {term(), server_ref()}}. -check_response(Msg, RequestId) -> - gen:check_response(Msg, RequestId). + try + gen:send_request(Name, '$gen_call', Request) + catch + error:badarg -> + error(badarg, [Name, Request]) + end. + +-spec send_request(ServerRef::server_ref(), + Request::term(), + Label::term(), + ReqIdCollection::request_id_collection()) -> + NewReqIdCollection::request_id_collection(). + +send_request(ServerRef, Request, Label, ReqIdCol) -> + try + gen:send_request(ServerRef, '$gen_call', Request, Label, ReqIdCol) + catch + error:badarg -> + error(badarg, [ServerRef, Request, Label, ReqIdCol]) + end. + + +-spec wait_response(ReqId) -> Result when + ReqId :: request_id(), + Response :: {reply, Reply::term()} + | {error, {Reason::term(), server_ref()}}, + Result :: Response | 'timeout'. + +wait_response(ReqId) -> + wait_response(ReqId, infinity). + +-spec wait_response(ReqId, WaitTime) -> Result when + ReqId :: request_id(), + WaitTime :: response_timeout(), + Response :: {reply, Reply::term()} + | {error, {Reason::term(), server_ref()}}, + Result :: Response | 'timeout'. + +wait_response(ReqId, WaitTime) -> + try + gen:wait_response(ReqId, WaitTime) + catch + error:badarg -> + error(badarg, [ReqId, WaitTime]) + end. + +-spec wait_response(ReqIdCollection, WaitTime, Delete) -> Result when + ReqIdCollection :: request_id_collection(), + WaitTime :: response_timeout(), + Delete :: boolean(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: {Response, + Label::term(), + NewReqIdCollection::request_id_collection()} | + 'no_request' | + 'timeout'. + +wait_response(ReqIdCol, WaitTime, Delete) -> + try + gen:wait_response(ReqIdCol, WaitTime, Delete) + catch + error:badarg -> + error(badarg, [ReqIdCol, WaitTime, Delete]) + end. + +-spec receive_response(ReqId) -> Result when + ReqId :: request_id(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: Response | 'timeout'. + +receive_response(ReqId) -> + receive_response(ReqId, infinity). + +-spec receive_response(ReqId, Timeout) -> Result when + ReqId :: request_id(), + Timeout :: response_timeout(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: Response | 'timeout'. + +receive_response(ReqId, Timeout) -> + try + gen:receive_response(ReqId, Timeout) + catch + error:badarg -> + error(badarg, [ReqId, Timeout]) + end. + +-spec receive_response(ReqIdCollection, Timeout, Delete) -> Result when + ReqIdCollection :: request_id_collection(), + Timeout :: response_timeout(), + Delete :: boolean(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: {Response, + Label::term(), + NewReqIdCollection::request_id_collection()} | + 'no_request' | + 'timeout'. + +receive_response(ReqIdCol, Timeout, Delete) -> + try + gen:receive_response(ReqIdCol, Timeout, Delete) + catch + error:badarg -> + error(badarg, [ReqIdCol, Timeout, Delete]) + end. + +-spec check_response(Msg, ReqId) -> Result when + Msg :: term(), + ReqId :: request_id(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: Response | 'no_reply'. + +check_response(Msg, ReqId) -> + try + gen:check_response(Msg, ReqId) + catch + error:badarg -> + error(badarg, [Msg, ReqId]) + end. + +-spec check_response(Msg, ReqIdCollection, Delete) -> Result when + Msg :: term(), + ReqIdCollection :: request_id_collection(), + Delete :: boolean(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: {Response, + Label::term(), + NewReqIdCollection::request_id_collection()} | + 'no_request' | + 'no_reply'. + +check_response(Msg, ReqIdCol, Delete) -> + try + gen:check_response(Msg, ReqIdCol, Delete) + catch + error:badarg -> + error(badarg, [Msg, ReqIdCol, Delete]) + end. + +-spec reqids_new() -> + NewReqIdCollection::request_id_collection(). + +reqids_new() -> + gen:reqids_new(). + +-spec reqids_size(ReqIdCollection::request_id_collection()) -> + non_neg_integer(). + +reqids_size(ReqIdCollection) -> + try + gen:reqids_size(ReqIdCollection) + catch + error:badarg -> error(badarg, [ReqIdCollection]) + end. + +-spec reqids_add(ReqId::request_id(), Label::term(), + ReqIdCollection::request_id_collection()) -> + NewReqIdCollection::request_id_collection(). + +reqids_add(ReqId, Label, ReqIdCollection) -> + try + gen:reqids_add(ReqId, Label, ReqIdCollection) + catch + error:badarg -> error(badarg, [ReqId, Label, ReqIdCollection]) + end. + +-spec reqids_to_list(ReqIdCollection::request_id_collection()) -> + [{ReqId::request_id(), Label::term()}]. + +reqids_to_list(ReqIdCollection) -> + try + gen:reqids_to_list(ReqIdCollection) + catch + error:badarg -> error(badarg, [ReqIdCollection]) + end. %% Reply from a state machine callback to whom awaits in call/2 -spec reply([reply_action()] | reply_action()) -> ok. diff --git a/lib/stdlib/test/dummy_h.erl b/lib/stdlib/test/dummy_h.erl index f75c580be1..4fffcd86d8 100644 --- a/lib/stdlib/test/dummy_h.erl +++ b/lib/stdlib/test/dummy_h.erl @@ -63,6 +63,9 @@ handle_call(hibernate, _State) -> handle_call(hibernate_later, _State) -> timer:send_after(1000,sleep), {ok,later,[]}; +handle_call({delayed_answer, T}, State) -> + receive after T -> ok end, + {ok, delayed, State}; handle_call(_Query, State) -> {ok, ok, State}. diff --git a/lib/stdlib/test/gen_event_SUITE.erl b/lib/stdlib/test/gen_event_SUITE.erl index 6dbadd3602..65c5a83c9e 100644 --- a/lib/stdlib/test/gen_event_SUITE.erl +++ b/lib/stdlib/test/gen_event_SUITE.erl @@ -32,7 +32,9 @@ start_opt/1, undef_init/1, undef_handle_call/1, undef_handle_event/1, undef_handle_info/1, undef_code_change/1, undef_terminate/1, - undef_in_terminate/1, format_log_1/1, format_log_2/1]). + undef_in_terminate/1, format_log_1/1, format_log_2/1, + send_request_receive_reqid_collection/1, send_request_wait_reqid_collection/1, + send_request_check_reqid_collection/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -41,7 +43,9 @@ all() -> call_format_status, call_format_status_anon, error_format_status, get_state, replace_state, start_opt, {group, undef_callbacks}, undef_in_terminate, - format_log_1, format_log_2]. + format_log_1, format_log_2, + send_request_receive_reqid_collection, send_request_wait_reqid_collection, + send_request_check_reqid_collection]. groups() -> [{test_all, [], @@ -1522,3 +1526,324 @@ format_log_2(_Config) -> flatten_format_log(Report, Format) -> lists:flatten(gen_event:format_log(Report, Format)). + + +send_request_receive_reqid_collection(Config) when is_list(Config) -> + {ok, Pid1} = gen_event:start(), + ok = gen_event:add_handler(Pid1, dummy_h, [self()]), + [dummy_h] = gen_event:which_handlers(Pid1), + {ok, Pid2} = gen_event:start(), + ok = gen_event:add_handler(Pid2, dummy_h, [self()]), + [dummy_h] = gen_event:which_handlers(Pid2), + {ok, Pid3} = gen_event:start(), + ok = gen_event:add_handler(Pid3, dummy_h, [self()]), + [dummy_h] = gen_event:which_handlers(Pid3), + send_request_receive_reqid_collection(Pid1, Pid2, Pid3), + send_request_receive_reqid_collection_timeout(Pid1, Pid2, Pid3), + send_request_receive_reqid_collection_error(Pid1, Pid2, Pid3), + ok = gen_event:stop(Pid1), + try gen_event:stop(Pid2) catch exit:noproc -> ok end, + ok = gen_event:stop(Pid3). + +send_request_receive_reqid_collection(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_event:send_request(Pid1, dummy_h, hejsan), + + ReqIdC0 = gen_event:reqids_new(), + + ReqId1 = gen_event:send_request(Pid1, dummy_h, {delayed_answer,400}), + ReqIdC1 = gen_event:reqids_add(ReqId1, req1, ReqIdC0), + 1 = gen_event:reqids_size(ReqIdC1), + + ReqIdC2 = gen_event:send_request(Pid2, dummy_h, {delayed_answer,1}, req2, ReqIdC1), + 2 = gen_event:reqids_size(ReqIdC2), + + ReqIdC3 = gen_event:send_request(Pid3, dummy_h, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_event:reqids_size(ReqIdC3), + + {{reply, delayed}, req2, ReqIdC4} = gen_event:receive_response(ReqIdC3, infinity, true), + 2 = gen_event:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC5} = gen_event:receive_response(ReqIdC4, 5678, true), + 1 = gen_event:reqids_size(ReqIdC5), + + {{reply, delayed}, req1, ReqIdC6} = gen_event:receive_response(ReqIdC5, 5000, true), + 0 = gen_event:reqids_size(ReqIdC6), + + no_request = gen_event:receive_response(ReqIdC6, 5000, true), + + {reply, {ok, hejhopp}} = gen_event:receive_response(ReqId0, infinity), + + ok. + +send_request_receive_reqid_collection_timeout(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_event:send_request(Pid1, dummy_h, hejsan), + + ReqIdC0 = gen_event:reqids_new(), + + ReqId1 = gen_event:send_request(Pid1, dummy_h, {delayed_answer,1000}), + ReqIdC1 = gen_event:reqids_add(ReqId1, req1, ReqIdC0), + + ReqIdC2 = gen_event:send_request(Pid2, dummy_h, {delayed_answer,1}, req2, ReqIdC1), + + ReqId3 = gen_event:send_request(Pid3, dummy_h, {delayed_answer,500}), + ReqIdC3 = gen_event:reqids_add(ReqId3, req3, ReqIdC2), + + Deadline = erlang:monotonic_time(millisecond) + 100, + + {{reply, delayed}, req2, ReqIdC4} = gen_event:receive_response(ReqIdC3, {abs, Deadline}, true), + 2 = gen_event:reqids_size(ReqIdC4), + + timeout = gen_event:receive_response(ReqIdC4, {abs, Deadline}, true), + + Abandoned = lists:sort([{ReqId1, req1}, {ReqId3, req3}]), + Abandoned = lists:sort(gen_event:reqids_to_list(ReqIdC4)), + + %% Make sure requests were abandoned... + timeout = gen_event:receive_response(ReqIdC4, {abs, Deadline+1000}, true), + + {reply, {ok, hejhopp}} = gen_event:receive_response(ReqId0, infinity), + + ok. + +send_request_receive_reqid_collection_error(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_event:send_request(Pid1, dummy_h, hejsan), + + ReqIdC0 = gen_event:reqids_new(), + + ReqId1 = gen_event:send_request(Pid1, dummy_h, {delayed_answer,400}), + ReqIdC1 = gen_event:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = gen_event:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:badarg -> ok + end, + + unlink(Pid2), + exit(Pid2, kill), + ReqIdC2 = gen_event:send_request(Pid2, dummy_h, {delayed_answer,1}, req2, ReqIdC1), + ReqIdC3 = gen_event:send_request(Pid3, dummy_h, {delayed_answer,200}, req3, ReqIdC2), + ReqIdC4 = gen_event:send_request(Pid1, bad_h, hejsan, req4, ReqIdC3), + 4 = gen_event:reqids_size(ReqIdC4), + + {{error, {noproc, _}}, req2, ReqIdC5} = gen_event:receive_response(ReqIdC4, 2000, true), + 3 = gen_event:reqids_size(ReqIdC5), + + {{reply, delayed}, req3, ReqIdC5} = gen_event:receive_response(ReqIdC5, infinity, false), + {{reply, delayed}, req1, ReqIdC5} = gen_event:receive_response(ReqIdC5, infinity, false), + {{error, bad_module}, req4, ReqIdC5} = gen_event:wait_response(ReqIdC5, infinity, false), + + {reply, {ok, hejhopp}} = gen_event:receive_response(ReqId0, infinity), + + ok. + +send_request_wait_reqid_collection(Config) when is_list(Config) -> + {ok, Pid1} = gen_event:start(), + ok = gen_event:add_handler(Pid1, dummy_h, [self()]), + [dummy_h] = gen_event:which_handlers(Pid1), + {ok, Pid2} = gen_event:start(), + ok = gen_event:add_handler(Pid2, dummy_h, [self()]), + [dummy_h] = gen_event:which_handlers(Pid2), + {ok, Pid3} = gen_event:start(), + ok = gen_event:add_handler(Pid3, dummy_h, [self()]), + [dummy_h] = gen_event:which_handlers(Pid3), + send_request_wait_reqid_collection(Pid1, Pid2, Pid3), + send_request_wait_reqid_collection_timeout(Pid1, Pid2, Pid3), + send_request_wait_reqid_collection_error(Pid1, Pid2, Pid3), + ok = gen_event:stop(Pid1), + try gen_event:stop(Pid2) catch exit:noproc -> ok end, + ok = gen_event:stop(Pid3). + +send_request_wait_reqid_collection(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_event:send_request(Pid1, dummy_h, hejsan), + + ReqIdC0 = gen_event:reqids_new(), + + ReqId1 = gen_event:send_request(Pid1, dummy_h, {delayed_answer,400}), + ReqIdC1 = gen_event:reqids_add(ReqId1, req1, ReqIdC0), + 1 = gen_event:reqids_size(ReqIdC1), + + ReqIdC2 = gen_event:send_request(Pid2, dummy_h, {delayed_answer,1}, req2, ReqIdC1), + 2 = gen_event:reqids_size(ReqIdC2), + + ReqIdC3 = gen_event:send_request(Pid3, dummy_h, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_event:reqids_size(ReqIdC3), + + {{reply, delayed}, req2, ReqIdC4} = gen_event:wait_response(ReqIdC3, infinity, true), + 2 = gen_event:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC5} = gen_event:wait_response(ReqIdC4, 5678, true), + 1 = gen_event:reqids_size(ReqIdC5), + + {{reply, delayed}, req1, ReqIdC6} = gen_event:wait_response(ReqIdC5, 5000, true), + 0 = gen_event:reqids_size(ReqIdC6), + + no_request = gen_event:wait_response(ReqIdC6, 5000, true), + + {reply, {ok, hejhopp}} = gen_event:receive_response(ReqId0, infinity), + + ok. + +send_request_wait_reqid_collection_timeout(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_event:send_request(Pid1, dummy_h, hejsan), + + ReqIdC0 = gen_event:reqids_new(), + + ReqId1 = gen_event:send_request(Pid1, dummy_h, {delayed_answer,1000}), + ReqIdC1 = gen_event:reqids_add(ReqId1, req1, ReqIdC0), + + ReqIdC2 = gen_event:send_request(Pid2, dummy_h, {delayed_answer,1}, req2, ReqIdC1), + + ReqId3 = gen_event:send_request(Pid3, dummy_h, {delayed_answer,500}), + ReqIdC3 = gen_event:reqids_add(ReqId3, req3, ReqIdC2), + + Deadline = erlang:monotonic_time(millisecond) + 100, + + {{reply, delayed}, req2, ReqIdC4} = gen_event:wait_response(ReqIdC3, {abs, Deadline}, true), + 2 = gen_event:reqids_size(ReqIdC4), + + timeout = gen_event:wait_response(ReqIdC4, {abs, Deadline}, true), + + Unhandled = lists:sort([{ReqId1, req1}, {ReqId3, req3}]), + Unhandled = lists:sort(gen_event:reqids_to_list(ReqIdC4)), + + %% Make sure requests were not abandoned... + {{reply, delayed}, req3, ReqIdC4} = gen_event:wait_response(ReqIdC4, {abs, Deadline+1500}, false), + {{reply, delayed}, req1, ReqIdC4} = gen_event:wait_response(ReqIdC4, {abs, Deadline+1500}, false), + + {reply, {ok, hejhopp}} = gen_event:receive_response(ReqId0, infinity), + + ok. + +send_request_wait_reqid_collection_error(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_event:send_request(Pid1, dummy_h, hejsan), + + ReqIdC0 = gen_event:reqids_new(), + + ReqId1 = gen_event:send_request(Pid1, dummy_h, {delayed_answer,400}), + ReqIdC1 = gen_event:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = gen_event:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:badarg -> ok + end, + + unlink(Pid2), + exit(Pid2, kill), + ReqIdC2 = gen_event:send_request(Pid2, dummy_h, {delayed_answer,1}, req2, ReqIdC1), + ReqIdC3 = gen_event:send_request(Pid3, dummy_h, {delayed_answer,200}, req3, ReqIdC2), + ReqIdC4 = gen_event:send_request(Pid1, bad_h, hejsan, req4, ReqIdC3), + 4 = gen_event:reqids_size(ReqIdC4), + + {{error, {noproc, _}}, req2, ReqIdC5} = gen_event:wait_response(ReqIdC4, 2000, true), + 3 = gen_event:reqids_size(ReqIdC5), + + {{reply, delayed}, req3, ReqIdC5} = gen_event:wait_response(ReqIdC5, infinity, false), + {{reply, delayed}, req1, ReqIdC5} = gen_event:wait_response(ReqIdC5, infinity, false), + {{error, bad_module}, req4, ReqIdC5} = gen_event:wait_response(ReqIdC5, infinity, false), + + {reply, {ok, hejhopp}} = gen_event:receive_response(ReqId0, infinity), + + ok. + +send_request_check_reqid_collection(Config) when is_list(Config) -> + {ok, Pid1} = gen_event:start(), + ok = gen_event:add_handler(Pid1, dummy_h, [self()]), + [dummy_h] = gen_event:which_handlers(Pid1), + {ok, Pid2} = gen_event:start(), + ok = gen_event:add_handler(Pid2, dummy_h, [self()]), + [dummy_h] = gen_event:which_handlers(Pid2), + {ok, Pid3} = gen_event:start(), + ok = gen_event:add_handler(Pid3, dummy_h, [self()]), + [dummy_h] = gen_event:which_handlers(Pid3), + send_request_check_reqid_collection(Pid1, Pid2, Pid3), + send_request_check_reqid_collection_error(Pid1, Pid2, Pid3), + ok = gen_event:stop(Pid1), + try gen_event:stop(Pid2) catch exit:noproc -> ok end, + ok = gen_event:stop(Pid3). + +send_request_check_reqid_collection(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_event:send_request(Pid1, dummy_h, hejsan), + + receive after 100 -> ok end, + + ReqIdC0 = gen_event:reqids_new(), + + ReqIdC1 = gen_event:send_request(Pid1, dummy_h, {delayed_answer,400}, req1, ReqIdC0), + 1 = gen_event:reqids_size(ReqIdC1), + + ReqId2 = gen_event:send_request(Pid2, dummy_h, {delayed_answer,1}), + ReqIdC2 = gen_event:reqids_add(ReqId2, req2, ReqIdC1), + 2 = gen_event:reqids_size(ReqIdC2), + + ReqIdC3 = gen_event:send_request(Pid3, dummy_h, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_event:reqids_size(ReqIdC3), + + Msg0 = next_msg(), + no_reply = gen_event:check_response(Msg0, ReqIdC3, true), + + {{reply, delayed}, req2, ReqIdC4} = gen_event:check_response(next_msg(), ReqIdC3, true), + 2 = gen_event:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC5} = gen_event:check_response(next_msg(), ReqIdC4, true), + 1 = gen_event:reqids_size(ReqIdC5), + + {{reply, delayed}, req1, ReqIdC6} = gen_event:check_response(next_msg(), ReqIdC5, true), + 0 = gen_event:reqids_size(ReqIdC6), + + no_request = gen_event:check_response(Msg0, ReqIdC6, true), + + {reply, {ok, hejhopp}} = gen_event:check_response(Msg0, ReqId0), + + ok. + +send_request_check_reqid_collection_error(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_event:send_request(Pid1, dummy_h, hejsan), + + receive after 100 -> ok end, + + ReqIdC0 = gen_event:reqids_new(), + + ReqId1 = gen_event:send_request(Pid1, dummy_h, {delayed_answer,400}), + ReqIdC1 = gen_event:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = gen_event:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:badarg -> ok + end, + + unlink(Pid2), + exit(Pid2, kill), + ReqIdC2 = gen_event:send_request(Pid2, dummy_h, {delayed_answer,1}, req2, ReqIdC1), + + ReqIdC3 = gen_event:send_request(Pid3, dummy_h, {delayed_answer,200}, req3, ReqIdC2), + + ReqIdC4 = gen_event:send_request(Pid1, bad_h, hejsan, req4, ReqIdC3), + 4 = gen_event:reqids_size(ReqIdC4), + + Msg0 = next_msg(), + + no_reply = gen_event:check_response(Msg0, ReqIdC3, true), + + {{error, {noproc, _}}, req2, ReqIdC5} = gen_event:check_response(next_msg(), ReqIdC4, true), + 3 = gen_event:reqids_size(ReqIdC5), + + {{reply, delayed}, req3, ReqIdC5} = gen_event:check_response(next_msg(), ReqIdC5, false), + {{reply, delayed}, req1, ReqIdC5} = gen_event:check_response(next_msg(), ReqIdC5, false), + {{error, bad_module}, req4, ReqIdC5} = gen_event:check_response(next_msg(), ReqIdC5, false), + + no_reply = gen_event:check_response(Msg0, ReqIdC3, false), + + {reply, {ok, hejhopp}} = gen_event:check_response(Msg0, ReqId0), + + ok. + +next_msg() -> + receive M -> M end. diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl index 652bff2a51..5fa604d4fd 100644 --- a/lib/stdlib/test/gen_server_SUITE.erl +++ b/lib/stdlib/test/gen_server_SUITE.erl @@ -26,7 +26,11 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2]). --export([start/1, crash/1, call/1, send_request/1, cast/1, cast_fast/1, +-export([start/1, crash/1, call/1, send_request/1, + send_request_receive_reqid_collection/1, + send_request_wait_reqid_collection/1, + send_request_check_reqid_collection/1, + cast/1, cast_fast/1, continue/1, info/1, abcast/1, multicall/1, multicall_down/1, call_remote1/1, call_remote2/1, call_remote3/1, calling_self/1, call_remote_n1/1, call_remote_n2/1, call_remote_n3/1, spec_init/1, @@ -65,7 +69,9 @@ suite() -> {timetrap,{minutes,1}}]. all() -> - [start, {group,stop}, crash, call, send_request, cast, cast_fast, info, abcast, + [start, {group,stop}, crash, call, send_request, + send_request_receive_reqid_collection, send_request_wait_reqid_collection, + send_request_check_reqid_collection, cast, cast_fast, info, abcast, continue, multicall, multicall_down, call_remote1, call_remote2, calling_self, call_remote3, call_remote_n1, call_remote_n2, call_remote_n3, spec_init, @@ -598,6 +604,323 @@ send_request(Config) when is_list(Config) -> process_flag(trap_exit, OldFl), ok. +send_request_receive_reqid_collection(Config) when is_list(Config) -> + {ok, Pid1} = gen_server:start_link({local, my_test_name1}, + gen_server_SUITE, [], []), + {ok, Pid2} = gen_server:start_link({local, my_test_name2}, + gen_server_SUITE, [], []), + {ok, Pid3} = gen_server:start_link({local, my_test_name3}, + gen_server_SUITE, [], []), + send_request_receive_reqid_collection(Pid1, Pid2, Pid3), + send_request_receive_reqid_collection_timeout(Pid1, Pid2, Pid3), + send_request_receive_reqid_collection_error(Pid1, Pid2, Pid3), + unlink(Pid1), + exit(Pid1, kill), + unlink(Pid2), + exit(Pid2, kill), + unlink(Pid3), + exit(Pid3, kill), + false = is_process_alive(Pid1), + false = is_process_alive(Pid2), + false = is_process_alive(Pid3), + ok. + +send_request_receive_reqid_collection(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_server:send_request(Pid1, started_p), + + ReqIdC0 = gen_server:reqids_new(), + + ReqId1 = gen_server:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_server:reqids_add(ReqId1, req1, ReqIdC0), + 1 = gen_server:reqids_size(ReqIdC1), + + ReqIdC2 = gen_server:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + 2 = gen_server:reqids_size(ReqIdC2), + + ReqIdC3 = gen_server:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_server:reqids_size(ReqIdC3), + + {{reply, delayed}, req2, ReqIdC4} = gen_server:receive_response(ReqIdC3, infinity, true), + 2 = gen_server:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC5} = gen_server:receive_response(ReqIdC4, 5678, true), + 1 = gen_server:reqids_size(ReqIdC5), + + {{reply, delayed}, req1, ReqIdC6} = gen_server:receive_response(ReqIdC5, 5000, true), + 0 = gen_server:reqids_size(ReqIdC6), + + no_request = gen_server:receive_response(ReqIdC6, 5000, true), + + {reply, ok} = gen_server:receive_response(ReqId0, infinity), + + ok. + +send_request_receive_reqid_collection_timeout(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_server:send_request(Pid1, started_p), + + ReqIdC0 = gen_server:reqids_new(), + + ReqId1 = gen_server:send_request(Pid1, {delayed_answer,1000}), + ReqIdC1 = gen_server:reqids_add(ReqId1, req1, ReqIdC0), + + ReqIdC2 = gen_server:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + + ReqId3 = gen_server:send_request(Pid3, {delayed_answer,500}), + ReqIdC3 = gen_server:reqids_add(ReqId3, req3, ReqIdC2), + + Deadline = erlang:monotonic_time(millisecond) + 100, + + {{reply, delayed}, req2, ReqIdC4} = gen_server:receive_response(ReqIdC3, {abs, Deadline}, true), + 2 = gen_server:reqids_size(ReqIdC4), + + timeout = gen_server:receive_response(ReqIdC4, {abs, Deadline}, true), + + Abandoned = lists:sort([{ReqId1, req1}, {ReqId3, req3}]), + Abandoned = lists:sort(gen_server:reqids_to_list(ReqIdC4)), + + %% Make sure requests were abandoned... + timeout = gen_server:receive_response(ReqIdC4, {abs, Deadline+1000}, true), + + {reply, ok} = gen_server:receive_response(ReqId0, infinity), + + ok. + +send_request_receive_reqid_collection_error(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_server:send_request(Pid1, started_p), + + ReqIdC0 = gen_server:reqids_new(), + + ReqId1 = gen_server:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_server:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = gen_server:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:badarg -> ok + end, + + unlink(Pid2), + ReqIdC2 = gen_server:send_request(Pid2, stop_shutdown, req2, ReqIdC1), + ReqIdC3 = gen_server:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_server:reqids_size(ReqIdC3), + + {{error, {shutdown, _}}, req2, ReqIdC4} = gen_server:receive_response(ReqIdC3, 2000, true), + 2 = gen_server:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC4} = gen_server:receive_response(ReqIdC4, infinity, false), + + {{reply, delayed}, req1, ReqIdC4} = gen_server:receive_response(ReqIdC4, infinity, false), + + {reply, ok} = gen_server:receive_response(ReqId0, infinity), + + ok. + +send_request_wait_reqid_collection(Config) when is_list(Config) -> + {ok, Pid1} = gen_server:start_link({local, my_test_name1}, + gen_server_SUITE, [], []), + {ok, Pid2} = gen_server:start_link({local, my_test_name2}, + gen_server_SUITE, [], []), + {ok, Pid3} = gen_server:start_link({local, my_test_name3}, + gen_server_SUITE, [], []), + send_request_wait_reqid_collection(Pid1, Pid2, Pid3), + send_request_wait_reqid_collection_timeout(Pid1, Pid2, Pid3), + send_request_wait_reqid_collection_error(Pid1, Pid2, Pid3), + unlink(Pid1), + exit(Pid1, kill), + unlink(Pid2), + exit(Pid2, kill), + unlink(Pid3), + exit(Pid3, kill), + false = is_process_alive(Pid1), + false = is_process_alive(Pid2), + false = is_process_alive(Pid3), + ok. + +send_request_wait_reqid_collection(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_server:send_request(Pid1, started_p), + + ReqIdC0 = gen_server:reqids_new(), + + ReqId1 = gen_server:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_server:reqids_add(ReqId1, req1, ReqIdC0), + 1 = gen_server:reqids_size(ReqIdC1), + + ReqIdC2 = gen_server:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + 2 = gen_server:reqids_size(ReqIdC2), + + ReqIdC3 = gen_server:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_server:reqids_size(ReqIdC3), + + {{reply, delayed}, req2, ReqIdC4} = gen_server:wait_response(ReqIdC3, infinity, true), + 2 = gen_server:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC5} = gen_server:wait_response(ReqIdC4, 5678, true), + 1 = gen_server:reqids_size(ReqIdC5), + + {{reply, delayed}, req1, ReqIdC6} = gen_server:wait_response(ReqIdC5, 5000, true), + 0 = gen_server:reqids_size(ReqIdC6), + + no_request = gen_server:wait_response(ReqIdC6, 5000, true), + + {reply, ok} = gen_server:wait_response(ReqId0, infinity), + + ok. + +send_request_wait_reqid_collection_timeout(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_server:send_request(Pid1, started_p), + + ReqIdC0 = gen_server:reqids_new(), + + ReqId1 = gen_server:send_request(Pid1, {delayed_answer,1000}), + ReqIdC1 = gen_server:reqids_add(ReqId1, req1, ReqIdC0), + + ReqIdC2 = gen_server:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + + ReqId3 = gen_server:send_request(Pid3, {delayed_answer,500}), + ReqIdC3 = gen_server:reqids_add(ReqId3, req3, ReqIdC2), + + Deadline = erlang:monotonic_time(millisecond) + 100, + + {{reply, delayed}, req2, ReqIdC4} = gen_server:wait_response(ReqIdC3, {abs, Deadline}, true), + 2 = gen_server:reqids_size(ReqIdC4), + + timeout = gen_server:wait_response(ReqIdC4, {abs, Deadline}, true), + + Unhandled = lists:sort([{ReqId1, req1}, {ReqId3, req3}]), + Unhandled = lists:sort(gen_server:reqids_to_list(ReqIdC4)), + + %% Make sure requests were not abandoned... + {{reply, delayed}, req3, ReqIdC4} = gen_server:wait_response(ReqIdC4, {abs, Deadline+1500}, false), + {{reply, delayed}, req1, ReqIdC4} = gen_server:wait_response(ReqIdC4, {abs, Deadline+1500}, false), + + {reply, ok} = gen_server:receive_response(ReqId0, infinity), + + ok. + +send_request_wait_reqid_collection_error(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_server:send_request(Pid1, started_p), + + ReqIdC0 = gen_server:reqids_new(), + + ReqId1 = gen_server:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_server:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = gen_server:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:badarg -> ok + end, + + unlink(Pid2), + ReqIdC2 = gen_server:send_request(Pid2, stop_shutdown, req2, ReqIdC1), + ReqIdC3 = gen_server:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_server:reqids_size(ReqIdC3), + + {{error, {shutdown, _}}, req2, ReqIdC4} = gen_server:wait_response(ReqIdC3, 2000, true), + 2 = gen_server:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC4} = gen_server:wait_response(ReqIdC4, infinity, false), + + {{reply, delayed}, req1, ReqIdC4} = gen_server:wait_response(ReqIdC4, infinity, false), + + {reply, ok} = gen_server:wait_response(ReqId0, infinity), + + ok. + +send_request_check_reqid_collection(Config) when is_list(Config) -> + {ok, Pid1} = gen_server:start_link({local, my_test_name1}, + gen_server_SUITE, [], []), + {ok, Pid2} = gen_server:start_link({local, my_test_name2}, + gen_server_SUITE, [], []), + {ok, Pid3} = gen_server:start_link({local, my_test_name3}, + gen_server_SUITE, [], []), + send_request_check_reqid_collection(Pid1, Pid2, Pid3), + send_request_check_reqid_collection_error(Pid1, Pid2, Pid3), + unlink(Pid1), + exit(Pid1, kill), + unlink(Pid2), + exit(Pid2, kill), + unlink(Pid3), + exit(Pid3, kill), + false = is_process_alive(Pid1), + false = is_process_alive(Pid2), + false = is_process_alive(Pid3), + ok. + +send_request_check_reqid_collection(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_server:send_request(Pid1, started_p), + + receive after 100 -> ok end, + + ReqIdC0 = gen_server:reqids_new(), + + ReqIdC1 = gen_server:send_request(Pid1, {delayed_answer,400}, req1, ReqIdC0), + 1 = gen_server:reqids_size(ReqIdC1), + + ReqId2 = gen_server:send_request(Pid2, {delayed_answer,1}), + ReqIdC2 = gen_server:reqids_add(ReqId2, req2, ReqIdC1), + 2 = gen_server:reqids_size(ReqIdC2), + + ReqIdC3 = gen_server:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_server:reqids_size(ReqIdC3), + + Msg0 = next_msg(), + no_reply = gen_server:check_response(Msg0, ReqIdC3, true), + + {{reply, delayed}, req2, ReqIdC4} = gen_server:check_response(next_msg(), ReqIdC3, true), + 2 = gen_server:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC5} = gen_server:check_response(next_msg(), ReqIdC4, true), + 1 = gen_server:reqids_size(ReqIdC5), + + {{reply, delayed}, req1, ReqIdC6} = gen_server:check_response(next_msg(), ReqIdC5, true), + 0 = gen_server:reqids_size(ReqIdC6), + + no_request = gen_server:check_response(Msg0, ReqIdC6, true), + + {reply, ok} = gen_server:check_response(Msg0, ReqId0), + + ok. + +send_request_check_reqid_collection_error(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_server:send_request(Pid1, started_p), + + receive after 100 -> ok end, + + ReqIdC0 = gen_server:reqids_new(), + + ReqId1 = gen_server:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_server:reqids_add(ReqId1, req1, ReqIdC0), + + unlink(Pid2), + ReqIdC2 = gen_server:send_request(Pid2, stop_shutdown, req2, ReqIdC1), + + ReqIdC3 = gen_server:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_server:reqids_size(ReqIdC3), + + Msg0 = next_msg(), + + no_reply = gen_server:check_response(Msg0, ReqIdC3, true), + + {{error, {shutdown, _}}, req2, ReqIdC4} = gen_server:check_response(next_msg(), ReqIdC3, true), + 2 = gen_server:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC4} = gen_server:check_response(next_msg(), ReqIdC4, false), + + {{reply, delayed}, req1, ReqIdC4} = gen_server:check_response(next_msg(), ReqIdC4, false), + + {reply, ok} = gen_server:check_response(Msg0, ReqId0), + + ok. + +next_msg() -> + receive M -> M end. %% -------------------------------------- %% Test handle_continue. diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 9ddffcbb72..a34cb55e52 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -43,7 +43,9 @@ all() -> {group, sys}, hibernate, auto_hibernate, enter_loop, {group, undef_callbacks}, undef_in_terminate, {group, format_log}, - reply_by_alias_with_payload]. + reply_by_alias_with_payload, + send_request_receive_reqid_collection, send_request_wait_reqid_collection, + send_request_check_reqid_collection]. groups() -> [{start, [], tcs(start)}, @@ -2305,6 +2307,311 @@ reply_by_alias_with_payload(Config) when is_list(Config) -> ok end. +send_request_receive_reqid_collection(Config) when is_list(Config) -> + {ok,Pid1} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {ok,Pid2} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {ok,Pid3} = gen_statem:start(?MODULE, start_arg(Config, []), []), + send_request_receive_reqid_collection(Pid1, Pid2, Pid3), + send_request_receive_reqid_collection_timeout(Pid1, Pid2, Pid3), + send_request_receive_reqid_collection_error(Pid1, Pid2, Pid3), + stopped = gen_statem:call(Pid1, {stop,shutdown}), + stopped = gen_statem:call(Pid3, {stop,shutdown}), + check_stopped(Pid1), + check_stopped(Pid2), + check_stopped(Pid3), + ok. + +send_request_receive_reqid_collection(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + 1 = gen_statem:reqids_size(ReqIdC1), + + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + 2 = gen_statem:reqids_size(ReqIdC2), + + ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_statem:reqids_size(ReqIdC3), + + {{reply, delayed}, req2, ReqIdC4} = gen_statem:receive_response(ReqIdC3, infinity, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC5} = gen_statem:receive_response(ReqIdC4, 5678, true), + 1 = gen_statem:reqids_size(ReqIdC5), + + {{reply, delayed}, req1, ReqIdC6} = gen_statem:receive_response(ReqIdC5, 5000, true), + 0 = gen_statem:reqids_size(ReqIdC6), + + no_request = gen_statem:receive_response(ReqIdC6, 5000, true), + + {reply, yes} = gen_statem:receive_response(ReqId0, infinity), + + ok. + +send_request_receive_reqid_collection_timeout(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,1000}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + + ReqId3 = gen_statem:send_request(Pid3, {delayed_answer,500}), + ReqIdC3 = gen_statem:reqids_add(ReqId3, req3, ReqIdC2), + + Deadline = erlang:monotonic_time(millisecond) + 100, + + {{reply, delayed}, req2, ReqIdC4} = gen_statem:receive_response(ReqIdC3, {abs, Deadline}, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + timeout = gen_statem:receive_response(ReqIdC4, {abs, Deadline}, true), + + Abandoned = lists:sort([{ReqId1, req1}, {ReqId3, req3}]), + Abandoned = lists:sort(gen_statem:reqids_to_list(ReqIdC4)), + + %% Make sure requests were abandoned... + timeout = gen_statem:receive_response(ReqIdC4, {abs, Deadline+1000}, true), + + {reply, yes} = gen_statem:receive_response(ReqId0, infinity), + + ok. + +send_request_receive_reqid_collection_error(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = gen_statem:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:badarg -> ok + end, + + unlink(Pid2), + exit(Pid2, kill), + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_statem:reqids_size(ReqIdC3), + + {{error, {noproc, _}}, req2, ReqIdC4} = gen_statem:receive_response(ReqIdC3, 2000, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC4} = gen_statem:receive_response(ReqIdC4, infinity, false), + + {{reply, delayed}, req1, ReqIdC4} = gen_statem:receive_response(ReqIdC4, infinity, false), + + {reply, yes} = gen_statem:receive_response(ReqId0, infinity), + + ok. + +send_request_wait_reqid_collection(Config) when is_list(Config) -> + {ok,Pid1} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {ok,Pid2} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {ok,Pid3} = gen_statem:start(?MODULE, start_arg(Config, []), []), + send_request_wait_reqid_collection(Pid1, Pid2, Pid3), + send_request_wait_reqid_collection_timeout(Pid1, Pid2, Pid3), + send_request_wait_reqid_collection_error(Pid1, Pid2, Pid3), + stopped = gen_statem:call(Pid1, {stop,shutdown}), + stopped = gen_statem:call(Pid3, {stop,shutdown}), + check_stopped(Pid1), + check_stopped(Pid2), + check_stopped(Pid3), + ok. + +send_request_wait_reqid_collection(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + 1 = gen_statem:reqids_size(ReqIdC1), + + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + 2 = gen_statem:reqids_size(ReqIdC2), + + ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_statem:reqids_size(ReqIdC3), + + {{reply, delayed}, req2, ReqIdC4} = gen_statem:wait_response(ReqIdC3, infinity, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC5} = gen_statem:wait_response(ReqIdC4, 5678, true), + 1 = gen_statem:reqids_size(ReqIdC5), + + {{reply, delayed}, req1, ReqIdC6} = gen_statem:wait_response(ReqIdC5, 5000, true), + 0 = gen_statem:reqids_size(ReqIdC6), + + no_request = gen_statem:wait_response(ReqIdC6, 5000, true), + + {reply, yes} = gen_statem:receive_response(ReqId0, infinity), + + ok. + +send_request_wait_reqid_collection_timeout(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,1000}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + + ReqId3 = gen_statem:send_request(Pid3, {delayed_answer,500}), + ReqIdC3 = gen_statem:reqids_add(ReqId3, req3, ReqIdC2), + + Deadline = erlang:monotonic_time(millisecond) + 100, + + {{reply, delayed}, req2, ReqIdC4} = gen_statem:wait_response(ReqIdC3, {abs, Deadline}, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + timeout = gen_statem:wait_response(ReqIdC4, {abs, Deadline}, true), + + Unhandled = lists:sort([{ReqId1, req1}, {ReqId3, req3}]), + Unhandled = lists:sort(gen_statem:reqids_to_list(ReqIdC4)), + + %% Make sure requests were not abandoned... + {{reply, delayed}, req3, ReqIdC4} = gen_statem:wait_response(ReqIdC4, {abs, Deadline+1500}, false), + {{reply, delayed}, req1, ReqIdC4} = gen_statem:wait_response(ReqIdC4, {abs, Deadline+1500}, false), + + {reply, yes} = gen_statem:receive_response(ReqId0), + + ok. + +send_request_wait_reqid_collection_error(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = gen_statem:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:badarg -> ok + end, + + unlink(Pid2), + exit(Pid2, kill), + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_statem:reqids_size(ReqIdC3), + + {{error, {noproc, _}}, req2, ReqIdC4} = gen_statem:wait_response(ReqIdC3, 2000, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC4} = gen_statem:wait_response(ReqIdC4, infinity, false), + + {{reply, delayed}, req1, ReqIdC4} = gen_statem:wait_response(ReqIdC4, infinity, false), + + {reply, yes} = gen_statem:receive_response(ReqId0, infinity), + + ok. + +send_request_check_reqid_collection(Config) when is_list(Config) -> + {ok,Pid1} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {ok,Pid2} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {ok,Pid3} = gen_statem:start(?MODULE, start_arg(Config, []), []), + send_request_check_reqid_collection(Pid1, Pid2, Pid3), + send_request_check_reqid_collection_error(Pid1, Pid2, Pid3), + stopped = gen_statem:call(Pid1, {stop,shutdown}), + stopped = gen_statem:call(Pid3, {stop,shutdown}), + check_stopped(Pid1), + check_stopped(Pid2), + check_stopped(Pid3), + ok. + +send_request_check_reqid_collection(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + receive after 100 -> ok end, + + ReqIdC0 = gen_statem:reqids_new(), + + ReqIdC1 = gen_statem:send_request(Pid1, {delayed_answer,400}, req1, ReqIdC0), + 1 = gen_statem:reqids_size(ReqIdC1), + + ReqId2 = gen_statem:send_request(Pid2, {delayed_answer,1}), + ReqIdC2 = gen_statem:reqids_add(ReqId2, req2, ReqIdC1), + 2 = gen_statem:reqids_size(ReqIdC2), + + ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_statem:reqids_size(ReqIdC3), + + Msg0 = next_msg(), + no_reply = gen_statem:check_response(Msg0, ReqIdC3, true), + + {{reply, delayed}, req2, ReqIdC4} = gen_statem:check_response(next_msg(), ReqIdC3, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC5} = gen_statem:check_response(next_msg(), ReqIdC4, true), + 1 = gen_statem:reqids_size(ReqIdC5), + + {{reply, delayed}, req1, ReqIdC6} = gen_statem:check_response(next_msg(), ReqIdC5, true), + 0 = gen_statem:reqids_size(ReqIdC6), + + no_request = gen_statem:check_response(Msg0, ReqIdC6, true), + + {reply, yes} = gen_statem:check_response(Msg0, ReqId0), + + ok. + +send_request_check_reqid_collection_error(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + receive after 100 -> ok end, + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = gen_statem:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:badarg -> ok + end, + + unlink(Pid2), + exit(Pid2, kill), + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + + ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_statem:reqids_size(ReqIdC3), + + Msg0 = next_msg(), + + no_reply = gen_statem:check_response(Msg0, ReqIdC3, true), + + {{error, {noproc, _}}, req2, ReqIdC4} = gen_statem:check_response(next_msg(), ReqIdC3, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC4} = gen_statem:check_response(next_msg(), ReqIdC4, false), + + {{reply, delayed}, req1, ReqIdC4} = gen_statem:check_response(next_msg(), ReqIdC4, false), + + {reply, yes} = gen_statem:check_response(Msg0, ReqId0), + + ok. + +next_msg() -> + receive M -> M end. + %% %% Functionality check %% -- 2.34.1
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor