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