File 6592-Improve-response-handling-for-asynchronous-erpc-requ.patch of Package erlang

From cb67a70aca04319e83e91b1459ddb2042e49c979 Mon Sep 17 00:00:00 2001
From: Rickard Green <rickard@erlang.org>
Date: Sat, 19 Mar 2022 23:38:52 +0100
Subject: [PATCH 2/2] Improve response handling for asynchronous 'erpc'
 requests

receive_response/3, wait_response/3, and check_response/3 in 'erpc'
can now take a collection of request identifiers as argument and handle
any responses corresponding a request identifier in the collection.
---
 lib/kernel/doc/src/erpc.xml    | 701 +++++++++++++++++++++++++++------
 lib/kernel/src/erpc.erl        | 347 ++++++++++++++--
 lib/kernel/test/erpc_SUITE.erl | 332 +++++++++++++++-
 3 files changed, 1222 insertions(+), 158 deletions(-)

diff --git a/lib/kernel/doc/src/erpc.xml b/lib/kernel/doc/src/erpc.xml
index 26d1ec4aad..ecb3949eb9 100644
--- a/lib/kernel/doc/src/erpc.xml
+++ b/lib/kernel/doc/src/erpc.xml
@@ -64,12 +64,55 @@
       <name name="request_id"/>
       <desc>
         <p>
-	  An opaque type of call request identifiers. For more
-	  information see
+	  An opaque request identifier. For more information see
           <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>.
 	</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="timeout_time"/>
+      <desc>
+        <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>
@@ -81,8 +124,9 @@
       <desc>
 	<p>
 	  The same as calling
-	  <seemfa marker="#call/5"><c>erpc:call(<anno>Node</anno>,erlang,apply,[<anno>Fun</anno>,[]],<anno>Timeout</anno>)</c></seemfa>.
-	  May raise all the same exceptions as <c>erpc:call/5</c>
+	  <seemfa marker="#call/5"><c>erpc:call(<anno>Node</anno>,
+          erlang, apply, [<anno>Fun</anno>,[]], <anno>Timeout</anno>)</c></seemfa>.
+	  May raise all the same exceptions as <c>call/5</c>
 	  plus an <c>{erpc, badarg}</c> <c>error</c>
 	  exception if <c><anno>Fun</anno></c> is not a fun of
 	  zero arity.
@@ -104,9 +148,8 @@
 	  Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
           <anno>Args</anno>)</c> on node <c><anno>Node</anno></c> and returns
           the corresponding value <c><anno>Result</anno></c>.
-	  <c><anno>Timeout</anno></c> is an integer representing
-	  the timeout in milliseconds or the atom <c>infinity</c>
-	  which prevents the operation from ever timing out.
+          <c><anno>Timeout</anno></c> sets an upper time limit
+          for the <c>call</c> operation to complete.
 	</p>
 	<p>The call <c>erpc:call(<anno>Node</anno>, <anno>Module</anno>,
 	<anno>Function</anno>, <anno>Args</anno>)</c> is equivalent
@@ -118,7 +161,7 @@
 	  exceptions, the operation did not time out, and no failures
 	  occurred. In all other cases an exception is raised. The
 	  following exceptions, listed by exception class, can
-	  currently be raised by <c>erpc:call()</c>:
+	  currently be raised by <c>call()</c>:
 	</p>
 	<taglist>
 	  <tag><c>throw</c></tag>
@@ -158,7 +201,7 @@
 	    
 	    <tag><c>{exception, ErrorReason, StackTrace}</c></tag>
 	    <item><p>
-	      A runtime error occurred which raised and error
+	      A runtime error occurred which raised an error
 	      exception while applying the function,
 	      and the applied function did not catch the
 	      exception. The error reason <c>ErrorReason</c>
@@ -186,9 +229,8 @@
 		  <item><p><c><anno>Args</anno></c> is not a list.
 		  Note that the list is not verified to be
 		  a proper list at the client side.</p></item>
-		  <item><p><c><anno>Timeout</anno></c> is not the
-		  atom <c>infinity</c> or an integer in valid
-		  range.</p></item>
+		  <item><p><c><anno>Timeout</anno></c> is
+                  invalid.</p></item>
 		</list>
 	      </item>
 	      
@@ -256,7 +298,7 @@
 	  The same as calling
 	  <seemfa marker="#cast/4"><c>erpc:cast(<anno>Node</anno>,erlang,apply,[<anno>Fun</anno>,[]])</c></seemfa>.
 	</p>
-	<p><c>erpc:cast/2</c> fails with an <c>{erpc, badarg}</c>
+	<p><c>cast/2</c> fails with an <c>{erpc, badarg}</c>
 	<c>error</c> exception if:</p>
 	<list>
 	  <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
@@ -273,11 +315,11 @@
 	  Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
           <anno>Args</anno>)</c> on node
           <c><anno>Node</anno></c>. No response is delivered to the
-	  calling process. <c>erpc:cast()</c> returns immediately
+	  calling process. <c>cast()</c> returns immediately
 	  after the cast request has been sent. Any failures beside
 	  bad arguments are silently ignored.
 	</p>
-	<p><c>erpc:cast/4</c> fails with an <c>{erpc, badarg}</c>
+	<p><c>cast/4</c> fails with an <c>{erpc, badarg}</c>
 	<c>error</c> exception if:</p>
 	<list>
 	  <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
@@ -305,13 +347,13 @@
 	<p>
 	  Check if a message is a response to a <c>call</c> request
 	  previously made by the calling process using
-	  <seemfa marker="#send_request/4"><c>erpc:send_request/4</c></seemfa>.
+	  <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>.
 	  <c><anno>RequestId</anno></c> should be the value returned
-	  from the previously made <c>erpc:send_request()</c> call,
+	  from the previously made <c>send_request/4</c> call,
 	  and the corresponding response should not already have been
-	  received and handled to completion by <c>erpc:check_response()</c>,
-	  <seemfa marker="#receive_response/2"><c>erpc:receive_response()</c></seemfa>, or
-	  <seemfa marker="#wait_response/2"><c>erpc:wait_response()</c></seemfa>.
+	  received and handled to completion by <c>check_response/2</c>,
+	  <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa>, or
+	  <seemfa marker="#wait_response/2"><c>wait_response/2</c></seemfa>.
 	  <c><anno>Message</anno></c> is the message to check.
 	</p>
 	<p>
@@ -323,9 +365,9 @@
 	  corresponds to the value returned from the applied function
 	  or an exception is raised. The exceptions that can be raised
 	  corresponds to the same exceptions as can be raised by
-	  <seemfa marker="#call/4"><c>erpc:call/4</c></seemfa>.
+	  <seemfa marker="#call/4"><c>call/4</c></seemfa>.
 	  That is, no <c>{erpc, timeout}</c> <c>error</c> exception
-	  can be raised. <c>erpc:check_response()</c> will fail with
+	  can be raised. <c>check_response()</c> will fail with
 	  an <c>{erpc, badarg}</c> exception if/when an invalid
 	  <c><anno>RequestId</anno></c> is detected.
 	</p>
@@ -340,6 +382,80 @@
       </desc>
     </func>
 
+    <func>
+      <name name="check_response" arity="3" since="OTP 25.0"/>
+      <fsummary>Check if a message is a response corresponding to a
+      previously sent call request.</fsummary>
+      <desc>
+        <p>
+	  Check if a message is a response to a <c>call</c> request corresponding
+          to a request identifier saved in <c><anno>RequestIdCollection</anno></c>.
+          All request identifiers of <c><anno>RequestIdCollection</anno></c> must
+          correspond to requests that have been made using
+	  <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa> or
+	  <seemfa marker="#send_request/6"><c>send_request/6</c></seemfa>,
+          and all requests must have been made by the process calling this
+          function.
+	</p>
+        <p>
+          <c><anno>Label</anno></c> is the label associated with the request
+          identifier of the request that the response corresponds to.
+          A request identifier is associated with a label when
+          <seemfa marker="#reqids_add/3">adding a request identifier</seemfa>
+          in a <seetype marker="#request_id_collection">request identifier
+          collection</seetype>, or when sending the request using
+          <seemfa marker="#send_request/6"><c>send_request/6</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>NewRequestIdCollection</anno></c> is a possibly  modified
+          request identifier collection. The <c>error</c> exception <c>{erpc,
+          badarg}</c> is not associated with any specific request identifier,
+          and will hence not be wrapped.
+        </p>
+        <p>
+          If <c><anno>RequestIdCollection</anno></c> is empty, the atom
+          <c>no_request</c> will be returned. If <c><anno>Message</anno></c>
+          does not correspond to any of the request identifiers in
+          <c><anno>RequestIdCollection</anno></c>, the atom
+          <c>no_response</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>RequestIdCollection</anno></c> in the resulting
+          <c><anno>NewRequestIdCollection</anno></c>. If
+          <c><anno>Delete</anno></c> equals <c>false</c>,
+          <c><anno>NewRequestIdCollection</anno></c> will equal
+          <c><anno>RequestIdCollection</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_response</c>.
+        </p>
+        <p>
+          Note that a response might have been consumed uppon an <c>{erpc,
+          badarg}</c> exception and if so, will be lost for ever.
+        </p>
+      </desc>
+    </func>
+
     <func>
       <name name="multicall" arity="2" since="OTP 23.0"/>
       <name name="multicall" arity="3" since="OTP 23.0"/>
@@ -347,8 +463,9 @@
       <desc>
 	<p>
 	  The same as calling
-	  <seemfa marker="#multicall/5"><c>erpc:multicall(<anno>Nodes</anno>,erlang,apply,[<anno>Fun</anno>,[]],<anno>Timeout</anno>)</c></seemfa>.
-	  May raise all the same exceptions as <c>erpc:multicall/5</c>
+	  <seemfa marker="#multicall/5"><c>erpc:multicall(<anno>Nodes</anno>,
+          erlang, apply, [<anno>Fun</anno>,[]], <anno>Timeout</anno>)</c></seemfa>.
+	  May raise all the same exceptions as <c>multicall/5</c>
 	  plus an <c>{erpc, badarg}</c> <c>error</c>
 	  exception if <c><anno>Fun</anno></c> is not a fun of
 	  zero arity.
@@ -372,15 +489,13 @@
 	  Performs multiple <c>call</c> operations in parallel
 	  on multiple nodes. That is, evaluates
 	  <c>apply(<anno>Module</anno>, <anno>Function</anno>,
-          <anno>Args</anno>)</c> on the nodes
-	  <c><anno>Nodes</anno></c> in parallel.
-	  <c><anno>Timeout</anno></c> is an integer representing
-	  the timeout in milliseconds or the atom <c>infinity</c>
-	  which prevents the operation from ever timing out.
-	  The result is returned as a list where
-	  the result from each node is placed at the same position
-	  as the node name is placed in <c><anno>Nodes</anno></c>.
-	  Each item in the resulting list is formatted as either:
+          <anno>Args</anno>)</c> on the nodes <c><anno>Nodes</anno></c>
+          in parallel. <c><anno>Timeout</anno></c> sets an upper time
+          limit for all <c>call</c> operations to complete. The result
+          is returned as a list where the result from each node is placed
+          at the same position as the node name is placed in
+          <c><anno>Nodes</anno></c>. Each item in the resulting list is
+          formatted as either:
 	</p>
 	<taglist>
 	  <tag><c>{ok, Result}</c></tag>
@@ -394,11 +509,11 @@
 	    raised an exception of class <c>Class</c> with
 	    exception reason <c>ExceptionReason</c>. These
 	    correspond to the exceptions that
-	    <seemfa marker="#call/5"><c>erpc:call/5</c></seemfa>
+	    <seemfa marker="#call/5"><c>call/5</c></seemfa>
 	    can raise.
 	  </p></item>
 	</taglist>
-	<p><c>erpc:multicall/5</c> fails with an <c>{erpc, badarg}</c>
+	<p><c>multicall/5</c> fails with an <c>{erpc, badarg}</c>
 	<c>error</c> exception if:</p>
 	<list>
 	  <item><p><c><anno>Nodes</anno></c> is not a proper list of
@@ -416,8 +531,12 @@
 	  to the call <c>erpc:multicall(<anno>Nodes</anno>, <anno>Module</anno>,
 	  <anno>Function</anno>, <anno>Args</anno>, infinity)</c>. These
 	  calls are also equivalent to calling <c>my_multicall(Nodes, Module,
-	  Function, Args)</c> if one disregard performance and failure
-	  behavior:
+	  Function, Args)</c> below if one disregard performance and failure
+	  behavior. <c>multicall()</c> can utilize a selective receive
+          optimization which removes the need to scan the message queue from
+          the beginning in order to find a matching message. The
+	  <c>send_request()/receive_response()</c> combination can,
+          however, not utilize this optimization.
 	</p>
 	<pre>
 my_multicall(Nodes, Module, Function, Args) ->
@@ -436,12 +555,6 @@ my_multicall(Nodes, Module, Function, Args) ->
             ReqIds).
 </pre>
 
-        <p>
-	  The <c><anno>Timeout</anno></c> value in milliseconds
-	  sets an upper time limit for all <c>call</c> operations
-	  to complete.
-	</p>
-
 	<p>
 	  If an <c>erpc</c> operation fails, but it is unknown if
 	  the function is/will be applied (that is, a timeout,
@@ -473,7 +586,7 @@ my_multicall(Nodes, Module, Function, Args) ->
 	  The same as calling
 	  <seemfa marker="#multicast/4"><c>erpc:multicast(<anno>Nodes</anno>,erlang,apply,[<anno>Fun</anno>,[]])</c></seemfa>.
 	</p>
-	<p><c>erpc:multicast/2</c> fails with an <c>{erpc, badarg}</c>
+	<p><c>multicast/2</c> fails with an <c>{erpc, badarg}</c>
 	<c>error</c> exception if:</p>
 	<list>
 	  <item><p><c><anno>Nodes</anno></c> is not a proper list of atoms.</p></item>
@@ -490,11 +603,11 @@ my_multicall(Nodes, Module, Function, Args) ->
 	  Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
           <anno>Args</anno>)</c> on the nodes
           <c><anno>Nodes</anno></c>. No response is delivered to the
-	  calling process. <c>erpc:multicast()</c> returns immediately
+	  calling process. <c>multicast()</c> returns immediately
 	  after the cast requests have been sent. Any failures beside
 	  bad arguments are silently ignored.
 	</p>
-	<p><c>erpc:multicast/4</c> fails with an <c>{erpc, badarg}</c>
+	<p><c>multicast/4</c> fails with an <c>{erpc, badarg}</c>
 	<c>error</c> exception if:</p>
 	<list>
 	  <item><p><c><anno>Nodes</anno></c> is not a proper list of
@@ -515,9 +628,21 @@ my_multicall(Nodes, Module, Function, Args) ->
 	</note>
       </desc>
     </func>
-    
+
     <func>
       <name name="receive_response" arity="1" since="OTP 23.0"/>
+      <fsummary>Receive a call response corresponding to a
+      previously sent call request.</fsummary>
+      <desc>
+        <p>
+          The same as calling
+          <seemfa marker="#receive_response/2"><c>erpc:receive_response(<anno>RequestId</anno>,
+          infinity)</c></seemfa>.
+        </p>
+      </desc>
+    </func>
+
+    <func>
       <name name="receive_response" arity="2" since="OTP 23.0"/>
       <fsummary>Receive a call response corresponding to a
       previously sent call request.</fsummary>
@@ -525,34 +650,41 @@ my_multicall(Nodes, Module, Function, Args) ->
 	<p>
 	  Receive a response to a <c>call</c> request previously
 	  made by the calling process using
-	  <seemfa marker="#send_request/4"><c>erpc:send_request/4</c></seemfa>.
+	  <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>.
 	  <c><anno>RequestId</anno></c> should be the value returned from
-	  the previously made <c>erpc:send_request()</c> call, and
+	  the previously made <c>send_request/4</c> call, and
 	  the corresponding response should not already have been received
-	  and handled to completion by
-	  <seemfa marker="#check_response/2"><c>erpc:check_response()</c></seemfa>,
-	  <c>erpc:receive_response()</c>, or
-	  <seemfa marker="#wait_response/2"><c>erpc:wait_response()</c></seemfa>.
-	  <c><anno>Timeout</anno></c> is an integer representing
-	  the timeout in milliseconds or the atom <c>infinity</c>
-	  which prevents the operation from ever timing out. The <c>call</c>
-	  operation is completed once the <c>erpc:receive_response()</c> call
-	  returns or raise an exception.
-	</p>
-	<p>
-	  The call <c>erpc:receive_response(<anno>RequestId</anno>)</c> is
-	  equivalent to the call
-	  <c>erpc:receive_response(<anno>RequestId</anno>, infinity)</c>.
+	  and handled to completion by <c>receive_response()</c>,
+	  <seemfa marker="#check_response/2"><c>check_response/4</c></seemfa>,
+          or
+	  <seemfa marker="#wait_response/2"><c>wait_response/4</c></seemfa>.
+        </p>
+        <p>
+          <c><anno>Timeout</anno></c> sets an upper time limit on how
+          long to wait for a response. If the operation times out, the request
+          identified by <c><anno>RequestId</anno></c> will be abandoned, then an
+          <c>{erpc, timeout}</c> <c>error</c> exception will be raised. That is,
+          no response corresponding to the request will ever be received after a
+          timeout. If a response is received, the <c>call</c> operation is
+          completed and either the result is returned or an exception is raised. The
+          exceptions that can be raised corresponds to the same exceptions as can
+          be raised by <seemfa marker="#call/5"><c>call/5</c></seemfa>.
+	  <c>receive_response/2</c> will fail with an <c>{erpc, badarg}</c>
+	  exception if/when an invalid <c><anno>RequestId</anno></c> is detected
+	  or if an invalid <c><anno>Timeout</anno></c> is passed.
 	</p>
+
 	<p>
 	  A call to the function
 	  <c>my_call(Node, Module, Function, Args, Timeout)</c>
 	  below is equivalent to the call
 	  <seemfa marker="#call/5"><c>erpc:call(Node, Module, Function, Args,
-	  Timeout)</c></seemfa> if one disregards performance. <c>erpc:call()</c>
-	  can utilize a message queue optimization which removes the need to scan
-	  the whole message queue which the combination
-	  <c>erpc:send_request()/erpc:receive_response()</c> cannot.
+	  Timeout)</c></seemfa> if one disregards performance. <c>call()</c>
+	  can utilize a selective receive optimization which removes
+          the need to scan the message queue from the beginning in
+          order to find a matching message. The
+	  <c>send_request()/receive_response()</c> combination can,
+          however, not utilize this optimization.
 	</p>
 	<pre>
 my_call(Node, Module, Function, Args, Timeout) ->
@@ -568,15 +700,157 @@ my_call(Node, Module, Function, Args, Timeout) ->
 	  communicates with the calling process, such communication
 	  may, of course, reach the calling process.
         </p>
-	
-	<p>
-	  <c>erpc:receive_response()</c> will return or raise exceptions the
-	  same way as <seemfa marker="#call/5"><c>erpc:call/5</c></seemfa>
-	  does with the exception of <c>{erpc, badarg}</c>. An
-	  <c>{erpc, badarg}</c> exception will be raised if/when an invalid
-	  <c><anno>RequestId</anno></c> is detected or if an invalid
-	  <c><anno>Timeout</anno></c> is passed.
+      </desc>
+    </func>
+
+    <func>
+      <name name="receive_response" arity="3" since="OTP 25.0"/>
+      <fsummary>Receive a call response corresponding to a
+      previously sent call request.</fsummary>
+      <desc>
+        <p>
+	  Receive a response to a <c>call</c> request corresponding to a request
+          identifier saved in <c><anno>RequestIdCollection</anno></c>. All
+          request identifiers of <c><anno>RequestIdCollection</anno></c> must
+          correspond to requests that have been made using
+	  <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa> or
+	  <seemfa marker="#send_request/6"><c>send_request/6</c></seemfa>,
+          and all requests must have been made by the process calling this
+          function.
 	</p>
+        <p>
+          <c><anno>Label</anno></c> is the label associated with the request
+          identifier of the request that the response corresponds to.
+          A request identifier is associated with a label when
+          <seemfa marker="#reqids_add/3">adding a request identifier</seemfa>
+          in a <seetype marker="#request_id_collection">request identifier
+          collection</seetype>, or when sending the request using
+          <seemfa marker="#send_request/6"><c>send_request/6</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
+          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>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>NewRequestIdCollection</anno></c> is a possibly  modified
+          request identifier collection. The <c>error</c> exceptions <c>{erpc,
+          badarg}</c> and <c>{erpc, timeout}</c> are not associated with any
+          specific request identifiers, and will hence not be wrapped.
+        </p>
+        <p>
+          If <c><anno>RequestIdCollection</anno></c> is empty, the atom
+          <c>no_request</c> will be returned.
+        </p>
+        <p>
+          If the operation times out, all requests identified by
+          <c><anno>RequestIdCollection</anno></c> will be abandoned, then an
+          <c>{erpc, timeout}</c> <c>error</c> exception will be raised. That is,
+          no responses corresponding to any of the request identifiers in
+          <c><anno>RequestIdCollection</anno></c> will ever be received after a
+          timeout. 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 any 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>RequestIdCollection</anno></c> in the resulting
+          <c><anno>NewRequestIdCollection</anno></c>. If
+          <c><anno>Delete</anno></c> equals <c>false</c>,
+          <c><anno>NewRequestIdCollection</anno></c> will equal
+          <c><anno>RequestIdCollection</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>
+        <p>
+          Note that a response might have been consumed uppon an <c>{erpc,
+          badarg}</c> exception and if so, will be lost for ever.
+        </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>RequestId</anno></c> and associates a
+          <c><anno>Label</anno></c> with the request identifier by adding this
+          information to <c><anno>RequestIdCollection</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/4"><c>send_request/4</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="#check_response/3"><c>check_response/3</c></seemfa>,
+	  <seemfa marker="#receive_response/3"><c>receive_response/3</c></seemfa>,
+          and
+	  <seemfa marker="#wait_response/3"><c>wait_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>RequestIdCollection</anno></c>.
+        </p>
+      </desc>
+    </func>
+
+    <func>
+      <name name="reqids_to_list" arity="1" since="OTP 25.0"/>
+      <fsummary>Get a list a request identifier/label associations in a collection.</fsummary>
+      <desc>
+	<p>
+          Returns a list of <c>{<anno>RequestId</anno>, <anno>Label</anno>}</c>
+          tuples which corresponds to all request identifiers with their
+          associated labels present in the <c><anno>RequestIdCollection</anno></c>
+          collection.
+        </p>
       </desc>
     </func>
 
@@ -586,10 +860,10 @@ my_call(Node, Module, Function, Args, Timeout) ->
       <desc>
 	<p>
 	  The same as calling
-	  <seemfa marker="#send_request/4"><c>erpc:send_request(<anno>Node</anno>,erlang,apply,[<anno>Fun</anno>,[]])</c></seemfa>.
+	  <seemfa marker="#send_request/4"><c>erpc:send_request(<anno>Node</anno>,
+          erlang, apply, [<anno>Fun</anno>, []])</c></seemfa>.
 	</p>
-	<p><c>erpc:send_request/2</c> fails with an <c>{erpc, badarg}</c>
-	<c>error</c> exception if:</p>
+	<p>Fails with an <c>{erpc, badarg}</c> <c>error</c> exception if:</p>
 	<list>
 	  <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
 	  <item><p><c><anno>Fun</anno></c> is not a fun of
@@ -606,34 +880,158 @@ my_call(Node, Module, Function, Args, Timeout) ->
     </func>
     
     <func>
-      <name name="send_request" arity="4" since="OTP 23.0"/>
+      <name name="send_request" arity="4" clause_i="1" since="OTP 23.0"/>
       <fsummary>Send a request to evaluate a function call on a node.</fsummary>
       <desc>
 	<p>
 	  Send an asynchronous <c>call</c> request to the node
-	  <c><anno>Node</anno></c>. <c>erpc:send_request()</c>
+	  <c><anno>Node</anno></c>. <c>send_request/4</c>
 	  returns a request identifier that later is to be passed
-	  as argument to either
-	  <seemfa marker="#receive_response/1"><c>erpc:receive_response()</c></seemfa>,
-	  <seemfa marker="#wait_response/1"><c>erpc:wait_response()</c></seemfa>,
+	  to either
+	  <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 order to get the response of the call request. Besides passing
+          the request identifier directly to these functions, it can also be
+          added 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/2"><c>erpc:check_response()</c></seemfa>
-	  in order to get the response of the call request.
+	  <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/6"><c>send_request/6</c></seemfa>
+          instead.
+        </p>
+        <p>
+	  A call to the function
+	  <c>my_call(Node, Module, Function, Args, Timeout)</c>
+	  below is equivalent to the call
+	  <seemfa marker="#call/5"><c>erpc:call(Node, Module, Function, Args,
+	  Timeout)</c></seemfa> if one disregards performance. <c>call()</c>
+	  can utilize a selective receive optimization which removes
+          the need to scan the message queue from the beginning in
+          order to find a matching message. The
+	  <c>send_request()/receive_response()</c> combination can,
+          however, not utilize this optimization.
 	</p>
-	<p><c>erpc:send_request()</c> fails with an <c>{erpc, badarg}</c>
-	<c>error</c> exception if:</p>
+	<pre>
+my_call(Node, Module, Function, Args, Timeout) ->
+  RequestId = erpc:send_request(Node, Module, Function, Args),
+  <seemfa marker="#receive_response/2">erpc:receive_response(RequestId, Timeout)</seemfa>.
+</pre>
+	<p>Fails with an <c>{erpc, badarg}</c> <c>error</c> exception if:</p>
+	<list>
+	  <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
+	  <item><p><c><anno>Module</anno></c> is not an atom.</p></item>
+	  <item><p><c><anno>Function</anno></c> is not an atom.</p></item>
+	  <item><p><c><anno>Args</anno></c> is not a list. Note that the
+	  list is not verified to be a proper list at the client side.</p></item>
+	</list>
+	<note>
+	  <p>
+	    You cannot make <em>any</em> assumptions about the
+	    process that will perform the <c>apply()</c>. It may
+	    be a server, or a freshly spawned process.
+	  </p>
+	</note>
+      </desc>
+    </func>
+
+    <func>
+      <name name="send_request" arity="4" clause_i="2" since="OTP 25.0"/>
+      <fsummary>Send a request to evaluate a function call on a node.</fsummary>
+      <desc>
+	<p>
+	  The same as calling
+	  <seemfa marker="#send_request/6"><c>erpc:send_request(<anno>Node</anno>,
+          erlang, apply, [<anno>Fun</anno>,[]]), <anno>Label</anno>,
+          <anno>RequestIdCollection</anno>)</c></seemfa>.
+	</p>
+	<p>Fails with an <c>{erpc, badarg}</c> <c>error</c> exception if:</p>
+	<list>
+	  <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
+	  <item><p><c><anno>Fun</anno></c> is not a fun of zero arity.</p></item>
+	  <item><p><c><anno>RequestIdCollection</anno></c> is detected not to
+          be request identifier collection.</p></item>
+	</list>
+	<note>
+	  <p>
+	    You cannot make <em>any</em> assumptions about the
+	    process that will perform the <c>apply()</c>. It may
+	    be a server, or a freshly spawned process.
+	  </p>
+	</note>
+      </desc>
+    </func>
+
+    <func>
+      <name name="send_request" arity="6" since="OTP 25.0"/>
+      <fsummary>Send a request to evaluate a function call on a node.</fsummary>
+      <desc>
+	<p>
+	  Send an asynchronous <c>call</c> request to the node
+	  <c><anno>Node</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>NewRequestIdCollection</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>erpc:reqids_add</c></seemfa>(<seemfa
+          marker="#send_request/4"><c>erpc:send_request</c></seemfa><c>(<anno>Node</anno>,
+          <anno>Module</anno>, <anno>Function</anno>, <anno>Args</anno>),
+          <anno>Label</anno>, <anno>RequestIdCollection</anno>)</c>, but
+          calling <c>send_request/6</c> is slightly more efficient.
+        </p>
+
+	<p>Fails with an <c>{erpc, badarg}</c> <c>error</c> exception if:</p>
 	<list>
 	  <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
 	  <item><p><c><anno>Module</anno></c> is not an atom.</p></item>
 	  <item><p><c><anno>Function</anno></c> is not an atom.</p></item>
 	  <item><p><c><anno>Args</anno></c> is not a list. Note that the
 	  list is not verified to be a proper list at the client side.</p></item>
+	  <item><p><c><anno>RequestIdCollection</anno></c> is detected not to
+          be request identifier collection.</p></item>
 	</list>
+	<note>
+	  <p>
+	    You cannot make <em>any</em> assumptions about the
+	    process that will perform the <c>apply()</c>. It may
+	    be a server, or a freshly spawned process.
+	  </p>
+	</note>
+      </desc>
+    </func>
+
+    <func>
+      <name name="wait_response" arity="1" clause_i="1" since="OTP 23.0"/>
+      <fsummary>Poll for a call response corresponding to a previously
+      sent call request.</fsummary>
+      <desc>
+        <p>
+          The same as calling
+          <seemfa marker="#wait_response/2"><c>erpc:wait_response(<anno>RequestId</anno>,
+          0)</c></seemfa>. That is, poll for a response message to a <c>call</c>
+          request previously made by the calling process.
+        </p>
       </desc>
     </func>
 
     <func>
-      <name name="wait_response" arity="1" since="OTP 23.0"/>
       <name name="wait_response" arity="2" since="OTP 23.0"/>
       <fsummary>Wait or poll for a call response corresponding to a previously
       sent call request.</fsummary>
@@ -641,40 +1039,30 @@ my_call(Node, Module, Function, Args, Timeout) ->
 	<p>
 	  Wait or poll for a response message to a <c>call</c> request
 	  previously made by the calling process using
-	  <seemfa marker="#send_request/4"><c>erpc:send_request/4</c></seemfa>.
+	  <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>.
 	  <c><anno>RequestId</anno></c> should be the value returned from
-	  the previously made <c>erpc:send_request()</c> call, and the
+	  the previously made <c>send_request()</c> call, and the
 	  corresponding response should not already have been received and handled
 	  to completion by
-	  <seemfa marker="#check_response/2"><c>erpc:check_response()</c></seemfa>,
-	  <seemfa marker="#receive_response/2"><c>erpc:receive_response()</c></seemfa>,
-	  or <c>erpc:wait_response()</c>. <c><anno>WaitTime</anno></c> equals the
-	  time to wait in milliseconds (or the atom <c>infinity</c>) during the wait.
-	  <c><anno>WaitTime</anno></c> is an integer representing time to wait
-	  in milliseconds or the atom <c>infinity</c> which will cause
-	  <c>wait_response/2</c> to wait for a response until it appears
-	  regardless of how long time that is.
+	  <seemfa marker="#check_response/2"><c>check_response/2</c></seemfa>,
+	  <seemfa marker="#receive_response/2"><c>receive_response/2</c></seemfa>,
+	  or <c>wait_response()</c>.
 	</p>
 	<p>
-	  The call <c>erpc:wait_response(<anno>RequestId</anno>)</c> is equivalent
-	  to the call <c>erpc:wait_response(<anno>RequestId</anno>, 0)</c>. That is,
-	  poll for a response message to a <c>call</c> request previously made by
-	  the calling process.
-	</p>
-	<p>
-	  If no response is received before <c><anno>WaitTime</anno></c> milliseconds,
-	  the atom <c>no_response</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>erpc:check_response()</c>,
-	  <c>erpc:receive_response()</c>, or <c>erpc:wait_response()</c>. If a
-	  response is received, the <c>call</c> operation is completed and either
-	  the result is returned as <c>{response, Result}</c> where <c>Result</c>
-	  corresponds to the value returned from the applied function or an
-	  exception is raised. The exceptions that can be raised corresponds to the
-	  same exceptions as can be raised by
-	  <seemfa marker="#call/4"><c>erpc:call/4</c></seemfa>.
+          <c><anno>WaitTime</anno></c> sets an upper time limit on how long to wait
+          for a response. If no response is received before the
+          <c><anno>WaitTime</anno></c> timeout has triggered, the atom
+          <c>no_response</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>. If a response is received, the <c>call</c>
+          operation is completed and either the result is returned as
+          <c>{response, Result}</c> where <c>Result</c> corresponds to the value
+          returned from the applied function or an exception is raised. The
+          exceptions that can be raised corresponds to the same exceptions as can
+          be raised by <seemfa marker="#call/4"><c>call/4</c></seemfa>.
 	  That is, no <c>{erpc, timeout}</c> <c>error</c> exception can be raised.
-	  <c>erpc:wait_response()</c> will fail with an <c>{erpc, badarg}</c>
+	  <c>wait_response/2</c> will fail with an <c>{erpc, badarg}</c>
 	  exception if/when an invalid <c><anno>RequestId</anno></c> is detected
 	  or if an invalid <c><anno>WaitTime</anno></c> is passed.
 	</p>
@@ -690,6 +1078,87 @@ my_call(Node, Module, Function, Args, Timeout) ->
       </desc>
     </func>
 
+    <func>
+      <name name="wait_response" arity="3" since="OTP 25.0"/>
+      <fsummary>Wait or poll for a call response corresponding to a previously
+      sent call request.</fsummary>
+      <desc>
+        <p>
+	  Wait or poll for a response to a <c>call</c> request corresponding
+          to a request identifier saved in <c><anno>RequestIdCollection</anno></c>. All
+          request identifiers of <c><anno>RequestIdCollection</anno></c> must
+          correspond to requests that have been made using
+	  <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa> or
+	  <seemfa marker="#send_request/6"><c>send_request/6</c></seemfa>,
+          and all requests must have been made by the process calling this
+          function.
+	</p>
+        <p>
+          <c><anno>Label</anno></c> is the label associated with the request
+          identifier of the request that the response corresponds to.
+          A request identifier is associated with a label when
+          <seemfa marker="#reqids_add/3">adding a request identifier</seemfa>
+          in a <seetype marker="#request_id_collection">request identifier
+          collection</seetype>, or when sending the request using
+          <seemfa marker="#send_request/6"><c>send_request/6</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>NewRequestIdCollection</anno></c> is a possibly  modified
+          request identifier collection. The <c>error</c> exception <c>{erpc,
+          badarg}</c> is not associated with any specific request identifier,
+          and will hence not be wrapped.
+        </p>
+        <p>
+          If <c><anno>RequestIdCollection</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>no_response</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>. 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 any 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>RequestIdCollection</anno></c> in the resulting
+          <c><anno>NewRequestIdCollection</anno></c>. If
+          <c><anno>Delete</anno></c> equals <c>false</c>,
+          <c><anno>NewRequestIdCollection</anno></c> will equal
+          <c><anno>RequestIdCollection</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_response</c>.
+        </p>
+        <p>
+          Note that a response might have been consumed uppon an <c>{erpc,
+          badarg}</c> exception and if so, will be lost for ever.
+        </p>
+      </desc>
+    </func>
+    
   </funcs>
 </erlref>
 
diff --git a/lib/kernel/src/erpc.erl b/lib/kernel/src/erpc.erl
index c93b0f7ca8..be5d2b1942 100644
--- a/lib/kernel/src/erpc.erl
+++ b/lib/kernel/src/erpc.erl
@@ -32,19 +32,27 @@
 	 cast/4,
          send_request/2,
          send_request/4,
+         send_request/6,
          receive_response/1,
          receive_response/2,
+         receive_response/3,
          wait_response/1,
          wait_response/2,
+         wait_response/3,
          check_response/2,
+         check_response/3,
          multicall/2,
          multicall/3,
          multicall/4,
 	 multicall/5,
          multicast/2,
-	 multicast/4]).
+	 multicast/4,
+         reqids_new/0,
+         reqids_size/1,
+         reqids_add/3,
+         reqids_to_list/1]).
 
--export_type([request_id/0]).
+-export_type([request_id/0, request_id_collection/0, timeout_time/0]).
 
 %% Internal exports (also used by the 'rpc' module)
 
@@ -57,14 +65,15 @@
 
 %%------------------------------------------------------------------------
 
--compile({inline,[{result,4}]}). %% Nicer error stack trace...
+%% Nicer error stack trace...
+-compile({inline,[{result,4},{collection_result,6},{timeout_value,1}]}).
 
 -define(MAX_INT_TIMEOUT, 4294967295).
--define(TIMEOUT_TYPE, 0..?MAX_INT_TIMEOUT | 'infinity').
 -define(IS_VALID_TMO_INT(TI_), (is_integer(TI_)
                                 andalso (0 =< TI_)
                                 andalso (TI_ =< ?MAX_INT_TIMEOUT))).
--define(IS_VALID_TMO(T_), ((T_ == infinity) orelse ?IS_VALID_TMO_INT(T_))).
+
+-type timeout_time() :: 0..?MAX_INT_TIMEOUT | 'infinity' | {abs, integer()}.
 
 %%------------------------------------------------------------------------
 %% Exported API
@@ -81,7 +90,7 @@ call(N, Fun) ->
 -spec call(Node, Fun, Timeout) -> Result when
       Node :: node(),
       Fun :: function(),
-      Timeout :: ?TIMEOUT_TYPE,
+      Timeout :: timeout_time(),
       Result :: term().
 
 call(N, Fun, Timeout) when is_function(Fun, 0) ->
@@ -106,7 +115,7 @@ call(N, M, F, A) ->
       Module :: atom(),
       Function :: atom(),
       Args :: [term()],
-      Timeout :: ?TIMEOUT_TYPE,
+      Timeout :: timeout_time(),
       Result :: term().
 
 call(N, M, F, A, infinity) when node() =:= N,  %% Optimize local call
@@ -131,8 +140,8 @@ call(N, M, F, A, infinity) when node() =:= N,  %% Optimize local call
 call(N, M, F, A, T) when is_atom(N),
                          is_atom(M),
                          is_atom(F),
-                         is_list(A),
-                         ?IS_VALID_TMO(T) ->
+                         is_list(A) ->
+    Timeout = timeout_value(T),
     Res = make_ref(),
     ReqId = spawn_request(N, ?MODULE, execute_call, [Res, M, F, A],
                           [{reply, error_only}, monitor]),
@@ -141,7 +150,7 @@ call(N, M, F, A, T) when is_atom(N),
             result(spawn_reply, ReqId, Res, Reason);
         {'DOWN', ReqId, process, _Pid, Reason} ->
             result(down, ReqId, Res, Reason)
-    after T ->
+    after Timeout ->
             result(timeout, ReqId, Res, undefined)
     end;
 call(_N, _M, _F, _A, _T) ->
@@ -149,24 +158,34 @@ call(_N, _M, _F, _A, _T) ->
 
 %% Asynchronous call
 
--opaque request_id() :: {reference(), reference()}.
+-opaque request_id() :: nonempty_improper_list(reference(), reference()).
+-opaque request_id_collection() :: #{ reference() => [reference() | term()] }.
 
 -spec send_request(Node, Fun) -> RequestId when
       Node :: node(),
       Fun :: function(),
       RequestId :: request_id().
 
-send_request(N, F) when is_function(F, 0) ->
+send_request(N, F) when is_atom(N), is_function(F, 0) ->
     send_request(N, erlang, apply, [F, []]);
 send_request(_N, _F) ->
     error({?MODULE, badarg}).
 
+-dialyzer({no_improper_lists, send_request/4}).
+
 -spec send_request(Node, Module, Function, Args) -> RequestId when
       Node :: node(),
       Module :: atom(),
       Function :: atom(),
       Args :: [term()],
-      RequestId :: request_id().
+      RequestId :: request_id();
+                  (Node, Fun, Label, RequestIdCollection) ->
+          NewRequestIdCollection when
+      Node :: node(),
+      Fun :: function(),
+      Label :: term(),
+      RequestIdCollection :: request_id_collection(),
+      NewRequestIdCollection :: request_id_collection().
 
 send_request(N, M, F, A) when is_atom(N),
                               is_atom(M),
@@ -175,16 +194,43 @@ send_request(N, M, F, A) when is_atom(N),
     Res = make_ref(),
     ReqId = spawn_request(N, ?MODULE, execute_call, [Res, M, F, A],
                           [{reply, error_only}, monitor]),
-    {Res, ReqId};
-send_request(_N, _M, _F, _A) ->
+    [Res|ReqId];
+send_request(N, F, L, C) when is_atom(N), is_function(F, 0), is_map(C) ->
+    send_request(N, erlang, apply, [F, []], L, C);
+send_request(_, _, _, _) ->
+    error({?MODULE, badarg}).
+
+-dialyzer({no_improper_lists, send_request/6}).
+
+-spec send_request(Node, Module, Function, Args,
+                   Label, RequestIdCollection) ->
+          NewRequestIdCollection when
+      Node :: node(),
+      Module :: atom(),
+      Function :: atom(),
+      Args :: [term()],
+      Label :: term(),
+      RequestIdCollection :: request_id_collection(),
+      NewRequestIdCollection :: request_id_collection().
+
+send_request(N, M, F, A, L, C) when is_atom(N),
+                                    is_atom(M),
+                                    is_atom(F),
+                                    is_list(A),
+                                    is_map(C) ->
+    Res = make_ref(),
+    ReqId = spawn_request(N, ?MODULE, execute_call, [Res, M, F, A],
+                          [{reply, error_only}, monitor]),
+    maps:put(ReqId, [Res|L], C);
+send_request(_N, _M, _F, _A, _L, _C) ->
     error({?MODULE, badarg}).
 
 -spec receive_response(RequestId) -> Result when
       RequestId :: request_id(),
       Result :: term().
 
-receive_response({Res, ReqId} = RId) when is_reference(Res),
-                                          is_reference(ReqId) ->
+receive_response([Res|ReqId] = RId) when is_reference(Res),
+                                         is_reference(ReqId) ->
     receive_response(RId, infinity);
 receive_response(_) ->
     error({?MODULE, badarg}).
@@ -193,53 +239,118 @@ receive_response(_) ->
 
 -spec receive_response(RequestId, Timeout) -> Result when
       RequestId :: request_id(),
-      Timeout :: ?TIMEOUT_TYPE,
+      Timeout :: timeout_time(),
       Result :: term().
 
-receive_response({Res, ReqId}, Tmo) when is_reference(Res),
-                                         is_reference(ReqId),
-                                         ?IS_VALID_TMO(Tmo) ->
+receive_response([Res|ReqId], Tmo) when is_reference(Res),
+                                        is_reference(ReqId) ->
+    Timeout = timeout_value(Tmo),
     receive
         {spawn_reply, ReqId, error, Reason} ->
             result(spawn_reply, ReqId, Res, Reason);
         {'DOWN', ReqId, process, _Pid, Reason} ->
             result(down, ReqId, Res, Reason)
-    after Tmo ->
+    after Timeout ->
             result(timeout, ReqId, Res, undefined)
     end;
 receive_response(_, _) ->
     error({?MODULE, badarg}).
 
--spec wait_response(RequestId) -> {'response', Result} | 'no_response' when
+-dialyzer([{nowarn_function, receive_response/3}, no_return]).
+
+-spec receive_response(RequestIdCollection, Timeout, Delete) ->
+          {Result, Label, NewRequestIdCollection} | 'no_request' when
+      RequestIdCollection :: request_id_collection(),
+      Timeout :: timeout_time(),
+      Delete :: boolean(),
+      Result :: term(),
+      Label :: term(),
+      NewRequestIdCollection :: request_id_collection().
+
+receive_response(ReqIdCol, WT, Del) when map_size(ReqIdCol) == 0,
+                                         is_boolean(Del) ->
+    _ = timeout_value(WT),
+    no_request;
+receive_response(ReqIdCol, Tmo, Del) when is_map(ReqIdCol),
+                                          is_boolean(Del) ->
+    Timeout = timeout_value(Tmo),
+    receive
+        {spawn_reply, ReqId, error, Reason}
+          when is_map_key(ReqId, ReqIdCol), is_reference(ReqId) ->
+            collection_result(spawn_reply, ReqId, Reason, ReqIdCol, false, Del);
+        {'DOWN', ReqId, process, _Pid, Reason}
+          when is_map_key(ReqId, ReqIdCol), is_reference(ReqId) ->
+            collection_result(down, ReqId, Reason, ReqIdCol, false, Del)
+    after Timeout ->
+            collection_result(timeout, ok, ok, ReqIdCol, false, Del)
+    end;
+receive_response(_, _, _) ->
+    error({?MODULE, badarg}).
+
+-spec wait_response(RequestId) ->
+          {'response', Result} | 'no_response' when
       RequestId :: request_id(),
       Result :: term().
 
-wait_response({Res, ReqId} = RId) when is_reference(Res),
-                                       is_reference(ReqId) ->
-    wait_response(RId, 0).
+wait_response([Res|ReqId] = RId) when is_reference(Res),
+                                      is_reference(ReqId) ->
+    wait_response(RId, 0);
+wait_response(_) ->
+    error({?MODULE, badarg}).
     
 -dialyzer([{nowarn_function, wait_response/2}, no_return]).
 
 -spec wait_response(RequestId, WaitTime) ->
           {'response', Result} | 'no_response' when
       RequestId :: request_id(),
-      WaitTime :: ?TIMEOUT_TYPE,
+      WaitTime :: timeout_time(),
       Result :: term().
 
-wait_response({Res, ReqId}, WT) when is_reference(Res),
-                                     is_reference(ReqId),
-                                     ?IS_VALID_TMO(WT) ->
+wait_response([Res|ReqId], WT) when is_reference(Res),
+                                    is_reference(ReqId) ->
+    Timeout = timeout_value(WT),
     receive
         {spawn_reply, ReqId, error, Reason} ->
             result(spawn_reply, ReqId, Res, Reason);
         {'DOWN', ReqId, process, _Pid, Reason} ->
             {response, result(down, ReqId, Res, Reason)}
-    after WT ->
+    after Timeout ->
             no_response
     end;
 wait_response(_, _) ->
     error({?MODULE, badarg}).
 
+-spec wait_response(RequestIdCollection, WaitTime, Delete) ->
+          {{'response', Result}, Label, NewRequestIdCollection} |
+          'no_response' |
+          'no_request' when
+      RequestIdCollection :: request_id_collection(),
+      WaitTime :: timeout_time(),
+      Delete :: boolean(),
+      Label :: term(),
+      NewRequestIdCollection :: request_id_collection(),
+      Result :: term().
+
+wait_response(ReqIdCol, WT, Del) when map_size(ReqIdCol) == 0,
+                                       is_boolean(Del) ->
+    _ = timeout_value(WT),
+    no_request;
+wait_response(ReqIdCol, WT, Del) when is_map(ReqIdCol),
+                                      is_boolean(Del) ->
+    Timeout = timeout_value(WT),
+    receive
+        {spawn_reply, ReqId, error, Reason}
+          when is_map_key(ReqId, ReqIdCol), is_reference(ReqId) ->
+            collection_result(spawn_reply, ReqId, Reason, ReqIdCol, true, Del);
+        {'DOWN', ReqId, process, _Pid, Reason}
+          when is_map_key(ReqId, ReqIdCol), is_reference(ReqId) ->
+            collection_result(down, ReqId, Reason, ReqIdCol, true, Del)
+    after Timeout ->
+            no_response
+    end;
+wait_response(_, _, _) ->
+    error({?MODULE, badarg}).
+
 -dialyzer([{nowarn_function, check_response/2}, no_return]).
 
 -spec check_response(Message, RequestId) ->
@@ -247,21 +358,106 @@ wait_response(_, _) ->
       Message :: term(),
       RequestId :: request_id(),
       Result :: term().
-
+          
 check_response({spawn_reply, ReqId, error, Reason},
-               {Res, ReqId}) when is_reference(Res),
-                                  is_reference(ReqId) ->
+               [Res|ReqId]) when is_reference(Res),
+                                 is_reference(ReqId) ->
     result(spawn_reply, ReqId, Res, Reason);
 check_response({'DOWN', ReqId, process, _Pid, Reason},
-               {Res, ReqId}) when is_reference(Res),
-                                  is_reference(ReqId) ->
+               [Res|ReqId]) when is_reference(Res),
+                                 is_reference(ReqId) ->
     {response, result(down, ReqId, Res, Reason)};
-check_response(_Msg, {Res, ReqId}) when is_reference(Res),
-                                        is_reference(ReqId) ->
+check_response(_Msg, [Res|ReqId]) when is_reference(Res),
+                                       is_reference(ReqId) ->
     no_response;
 check_response(_, _) ->
     error({?MODULE, badarg}).
 
+-spec check_response(Message, RequestIdCollection, Delete) ->
+          {{'response', Result}, Label, NewRequestIdCollection} |
+          'no_response' |
+          'no_request' when
+      Message :: term(),
+      RequestIdCollection :: request_id_collection(),
+      Delete :: boolean(),
+      Result :: term(),
+      Label :: term(),
+      NewRequestIdCollection :: request_id_collection().
+
+check_response(_Msg, ReqIdCol, Del) when map_size(ReqIdCol) == 0,
+                                         is_boolean(Del) ->
+    no_request;
+check_response({spawn_reply, ReqId, error, Reason},
+               ReqIdCol, Del) when is_reference(ReqId),
+                                   is_map_key(ReqId, ReqIdCol),
+                                   is_boolean(Del) ->
+    collection_result(spawn_reply, ReqId, Reason, ReqIdCol, true, Del);
+check_response({'DOWN', ReqId, process, _Pid, Reason},
+               ReqIdCol, Del) when is_reference(ReqId),
+                                   is_map_key(ReqId, ReqIdCol),
+                                   is_boolean(Del) ->
+    collection_result(down, ReqId, Reason, ReqIdCol, true, Del);
+check_response(_Msg, ReqIdCol, Del) when is_map(ReqIdCol),
+                                         is_boolean(Del) ->
+    no_response;
+check_response(_, _, _) ->
+    error({?MODULE, badarg}).
+
+-spec reqids_new() ->
+          NewRequestIdCollection::request_id_collection().
+
+reqids_new() ->
+    maps:new().
+
+-spec reqids_size(RequestIdCollection::request_id_collection()) ->
+          non_neg_integer().
+reqids_size(ReqIdCollection) ->
+    try
+        maps:size(ReqIdCollection)
+    catch
+        _:_ ->
+            error({?MODULE, badarg})
+    end.
+
+-dialyzer({no_improper_lists, reqids_add/3}).
+
+-spec reqids_add(RequestId::request_id(), Label::term(),
+                 RequestIdCollection::request_id_collection()) ->
+          NewRequestIdCollection::request_id_collection().
+
+reqids_add([_|ReqId], _, ReqIdCollection) when is_reference(ReqId),
+                                               is_map_key(ReqId,
+                                                          ReqIdCollection) ->
+    error({?MODULE, badarg});
+reqids_add([Res|ReqId], Label, ReqIdCollection) when is_reference(Res),
+                                                     is_reference(ReqId),
+                                                     is_map(ReqIdCollection) ->
+    maps:put(ReqId, [Res|Label], ReqIdCollection);
+reqids_add(_, _, _) ->
+    error({?MODULE, badarg}).
+
+-dialyzer({no_improper_lists, reqids_to_list/1}).
+
+-spec reqids_to_list(RequestIdCollection::request_id_collection()) ->
+          [{RequestId::request_id(), Label::term()}].
+
+reqids_to_list(ReqIdCollection) when is_map(ReqIdCollection) ->
+    try
+        maps:fold(fun (ReqId, [Res|Label], Acc) when is_reference(ReqId),
+                                                     is_reference(Res) ->
+                          [{[Res|ReqId], Label}|Acc];
+                      (_, _, _) ->
+                          throw(badarg)
+                  end,
+                  [],
+                  ReqIdCollection)
+    catch
+        throw:badarg ->
+            error({?MODULE, badarg})
+    end;
+reqids_to_list(_) ->
+    error({?MODULE, badarg}).
+
 -type stack_item() ::
         {Module :: atom(),
          Function :: atom(),
@@ -288,7 +484,7 @@ multicall(Ns, Fun) ->
 -spec multicall(Nodes, Fun, Timeout) -> Result when
       Nodes :: [atom()],
       Fun :: function(),
-      Timeout :: ?TIMEOUT_TYPE,
+      Timeout :: timeout_time(),
       Result :: term().
 
 multicall(Ns, Fun, Timeout) when is_function(Fun, 0) ->
@@ -311,7 +507,7 @@ multicall(Ns, M, F, A) ->
       Module :: atom(),
       Function :: atom(),
       Args :: [term()],
-      Timeout :: ?TIMEOUT_TYPE,
+      Timeout :: timeout_time(),
       Result :: [{ok, ReturnValue :: term()} | caught_call_exception()].
 
 multicall(Ns, M, F, A, T) ->
@@ -320,7 +516,8 @@ multicall(Ns, M, F, A, T) ->
         true = is_atom(F),
         true = is_list(A),
         Tag = make_ref(),
-        SendState = mcall_send_requests(Tag, Ns, M, F, A, T),
+        Timeout = timeout_value(T),
+        SendState = mcall_send_requests(Tag, Ns, M, F, A, Timeout),
         mcall_receive_replies(Tag, SendState)
     catch
         error:NotIErr when NotIErr /= internal_error ->
@@ -531,6 +728,74 @@ result(timeout, ReqId, Res, _Reason) ->
             end
     end.
 
+collection_result(timeout, _, _, ReqIdCollection, _, _) ->
+    Abandon = fun (ReqId, [Res|_Label]) when is_reference(ReqId),
+                                             is_reference(Res) ->
+                      case call_abandon(ReqId) of
+                          true ->
+                              ok;
+                          false ->
+                              %% Spawn error or DOWN has arrived if
+                              %% ReqId corresponds to an outstanding
+                              %% request; fetch and drop it...
+                              receive
+                                  {spawn_reply, ReqId, error, _} ->
+                                      ok;
+                                  {'DOWN', ReqId, process, _, _} ->
+                                      ok
+                              after
+                                  0 ->
+                                      ok %% Already handled...
+                              end
+                      end;
+                  (_, _) ->
+                      %% Invalid request id collection...
+                      throw(badarg)
+              end,
+    try
+        maps:foreach(Abandon, ReqIdCollection)
+    catch
+        throw:badarg -> error({?MODULE, badarg})
+    end,
+    error({?MODULE, timeout});
+collection_result(Type, ReqId, ResultReason, ReqIdCol, WrapResponse, Delete) ->
+    ReqIdInfo = case Delete of
+                    true -> maps:take(ReqId, ReqIdCol);
+                    false -> {maps:get(ReqId, ReqIdCol), ReqIdCol}
+                end,
+    case ReqIdInfo of
+        {[Res|Label], NewReqIdCol} when is_reference(Res) ->
+            try
+                Result = result(Type, ReqId, Res, ResultReason),
+                Response = if WrapResponse -> {response, Result};
+                              true -> Result
+                           end,
+                {Response, Label, NewReqIdCol}
+            catch
+                Class:Reason ->
+                    erlang:Class({Reason, Label, NewReqIdCol})
+            end;
+        _ ->
+            %% Invalid request id collection...
+            error({?MODULE, badarg})
+    end.
+
+timeout_value(infinity) ->
+    infinity;
+timeout_value(Timeout) when ?IS_VALID_TMO_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({?MODULE, badarg});
+        TMO ->
+            TMO
+    end;
+timeout_value(_) ->
+    error({?MODULE, badarg}).
+
 deadline(infinity) ->
     infinity;
 deadline(?MAX_INT_TIMEOUT) ->
-- 
2.34.1

openSUSE Build Service is sponsored by