Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
2892-Improve-response-handling-for-asynchronous...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 2892-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) -> diff --git a/lib/kernel/test/erpc_SUITE.erl b/lib/kernel/test/erpc_SUITE.erl index 9d295eefef..265131b20c 100644 --- a/lib/kernel/test/erpc_SUITE.erl +++ b/lib/kernel/test/erpc_SUITE.erl @@ -29,6 +29,9 @@ send_request_wait_reqtmo/1, send_request_check_reqtmo/1, send_request_against_old_node/1, + send_request_receive_reqid_collection/1, + send_request_wait_reqid_collection/1, + send_request_check_reqid_collection/1, multicall/1, multicall_reqtmo/1, multicall_recv_opt/1, multicall_recv_opt2/1, @@ -56,7 +59,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. -all() -> +all() -> [call, call_reqtmo, call_against_old_node, @@ -69,6 +72,9 @@ all() -> send_request_wait_reqtmo, send_request_check_reqtmo, send_request_against_old_node, + send_request_receive_reqid_collection, + send_request_wait_reqid_collection, + send_request_check_reqid_collection, multicall, multicall_reqtmo, multicall_recv_opt, @@ -937,6 +943,327 @@ send_request_against_ei_node(Config) when is_list(Config) -> {skipped, "No OTP 22 available"} end. +send_request_receive_reqid_collection(Config) when is_list(Config) -> + {ok, _P, N} = ?CT_PEER(#{connection => 0}), + send_request_receive_reqid_collection_success(N), + send_request_receive_reqid_collection_timeout(N), + send_request_receive_reqid_collection_error(N), + ok. + +send_request_receive_reqid_collection_success(N) -> + + ReqId0 = erpc:send_request(N, fun () -> 0 end), + + ReqIdC0 = erpc:reqids_new(), + + ReqId1 = erpc:send_request(N, fun () -> receive after 400 -> 400 end end), + ReqIdC1 = erpc:reqids_add(ReqId1, req1, ReqIdC0), + 1 = erpc:reqids_size(ReqIdC1), + + ReqIdC2 = erpc:send_request(N, fun () -> receive after 1 -> 1 end end, req2, ReqIdC1), + 2 = erpc:reqids_size(ReqIdC2), + + ReqIdC3 = erpc:send_request(N, fun () -> receive after 200 -> 200 end end, req3, ReqIdC2), + 3 = erpc:reqids_size(ReqIdC3), + + {1, req2, ReqIdC4} = erpc:receive_response(ReqIdC3, infinity, true), + 2 = erpc:reqids_size(ReqIdC4), + + {200, req3, ReqIdC5} = erpc:receive_response(ReqIdC4, 7654, true), + 1 = erpc:reqids_size(ReqIdC5), + + {400, req1, ReqIdC6} = erpc:receive_response(ReqIdC5, 5000, true), + 0 = erpc:reqids_size(ReqIdC6), + + no_request = erpc:receive_response(ReqIdC6, 5000, true), + + 0 = erpc:receive_response(ReqId0), + + ok. + +send_request_receive_reqid_collection_timeout(N) -> + + ReqId0 = erpc:send_request(N, fun () -> 0 end), + + ReqIdC0 = erpc:reqids_new(), + + ReqId1 = erpc:send_request(N, fun () -> receive after 1000 -> 1000 end end), + ReqIdC1 = erpc:reqids_add(ReqId1, req1, ReqIdC0), + + ReqIdC2 = erpc:send_request(N, fun () -> receive after 1 -> 1 end end, req2, ReqIdC1), + + ReqId3 = erpc:send_request(N, fun () -> receive after 500 -> 500 end end), + ReqIdC3 = erpc:reqids_add(ReqId3, req3, ReqIdC2), + + Deadline = erlang:monotonic_time(millisecond) + 100, + + {1, req2, ReqIdC4} = erpc:receive_response(ReqIdC3, {abs, Deadline}, true), + 2 = erpc:reqids_size(ReqIdC4), + + try not_valid = erpc:receive_response(ReqIdC4, {abs, Deadline}, true) + catch error:{erpc, timeout} -> ok + end, + + Abandoned = lists:sort([{ReqId1, req1}, {ReqId3, req3}]), + Abandoned = lists:sort(erpc:reqids_to_list(ReqIdC4)), + + %% Make sure requests were abandoned... + try not_valid = erpc:receive_response(ReqIdC4, {abs, Deadline+1000}, false) + catch error:{erpc, timeout} -> ok + end, + + 0 = erpc:receive_response(ReqId0, infinity), + + ok. + +send_request_receive_reqid_collection_error(N) -> + + ReqId0 = erpc:send_request(N, fun () -> 0 end), + + ReqIdC0 = erpc:reqids_new(), + + ReqId1 = erpc:send_request(N, fun () -> receive after 600 -> 600 end end), + ReqIdC1 = erpc:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = erpc:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:{erpc, badarg} -> ok + end, + + ReqIdC2 = erpc:send_request(N, fun () -> receive after 800 -> erlang:halt() end end, req2, ReqIdC1), + ReqIdC3 = erpc:send_request(N, fun () -> receive after 200 -> error(errored) end end, req3, ReqIdC2), + ReqIdC4 = erpc:send_request(N, fun () -> exit(exited) end, req4, ReqIdC3), + ReqIdC5 = erpc:send_request(N, fun () -> receive after 400 -> throw(thrown) end end, req5, ReqIdC4), + + 5 = erpc:reqids_size(ReqIdC5), + + ReqIdC6 = try not_valid = erpc:receive_response(ReqIdC5, infinity, true) + catch exit:{{exception, exited}, req4, RIC6} -> RIC6 + end, + + 4 = erpc:reqids_size(ReqIdC6), + + try not_valid = erpc:receive_response(ReqIdC6, 2000, false) + catch error:{{exception, errored, _Stk}, req3, _} -> ok + end, + + try not_valid = erpc:receive_response(ReqIdC6, infinity, false) + catch throw:{thrown, req5, _} -> ok + end, + + {600, req1, ReqIdC6} = erpc:receive_response(ReqIdC6, infinity, false), + + try not_valid = erpc:receive_response(ReqIdC6, 5000, false) + catch error:{{erpc, noconnection}, req2, ReqIdC6} -> ok + end, + + 0 = erpc:receive_response(ReqId0), + + ok. + +send_request_wait_reqid_collection(Config) when is_list(Config) -> + {ok, _P, N} = ?CT_PEER(#{connection => 0}), + send_request_wait_reqid_collection_success(N), + send_request_wait_reqid_collection_timeout(N), + send_request_wait_reqid_collection_error(N), + ok. + +send_request_wait_reqid_collection_success(N) -> + + ReqId0 = erpc:send_request(N, fun () -> 0 end), + + ReqIdC0 = erpc:reqids_new(), + + ReqId1 = erpc:send_request(N, fun () -> receive after 400 -> 400 end end), + ReqIdC1 = erpc:reqids_add(ReqId1, req1, ReqIdC0), + 1 = erpc:reqids_size(ReqIdC1), + + ReqIdC2 = erpc:send_request(N, fun () -> receive after 1 -> 1 end end, req2, ReqIdC1), + 2 = erpc:reqids_size(ReqIdC2), + + ReqIdC3 = erpc:send_request(N, fun () -> receive after 200 -> 200 end end, req3, ReqIdC2), + 3 = erpc:reqids_size(ReqIdC3), + + {{response, 1}, req2, ReqIdC4} = erpc:wait_response(ReqIdC3, infinity, true), + 2 = erpc:reqids_size(ReqIdC4), + + {{response, 200}, req3, ReqIdC5} = erpc:wait_response(ReqIdC4, 7654, true), + 1 = erpc:reqids_size(ReqIdC5), + + {{response, 400}, req1, ReqIdC6} = erpc:wait_response(ReqIdC5, 5000, true), + 0 = erpc:reqids_size(ReqIdC6), + + no_request = erpc:wait_response(ReqIdC6, 5000, true), + + {response, 0} = erpc:wait_response(ReqId0), + + ok. + +send_request_wait_reqid_collection_timeout(N) -> + + ReqId0 = erpc:send_request(N, fun () -> 0 end), + + ReqIdC0 = erpc:reqids_new(), + + ReqIdC0 = erpc:reqids_new(), + + ReqId1 = erpc:send_request(N, fun () -> receive after 1000 -> 1000 end end), + ReqIdC1 = erpc:reqids_add(ReqId1, req1, ReqIdC0), + + ReqIdC2 = erpc:send_request(N, fun () -> receive after 1 -> 1 end end, req2, ReqIdC1), + + ReqId3 = erpc:send_request(N, fun () -> receive after 500 -> 500 end end), + ReqIdC3 = erpc:reqids_add(ReqId3, req3, ReqIdC2), + + Deadline = erlang:monotonic_time(millisecond) + 100, + + {{response, 1}, req2, ReqIdC4} = erpc:wait_response(ReqIdC3, {abs, Deadline}, true), + 2 = erpc:reqids_size(ReqIdC4), + + no_response = erpc:wait_response(ReqIdC4, {abs, Deadline}, true), + + Unhandled = lists:sort([{ReqId1, req1}, {ReqId3, req3}]), + Unhandled = lists:sort(erpc:reqids_to_list(ReqIdC4)), + + %% Make sure requests were not abandoned... + {{response, 500}, req3, ReqIdC4} = erpc:wait_response(ReqIdC4, {abs, Deadline+1500}, false), + {{response, 1000}, req1, ReqIdC4} = erpc:wait_response(ReqIdC4, {abs, Deadline+1500}, false), + + {response, 0} = erpc:wait_response(ReqId0, infinity), + + ok. + +send_request_wait_reqid_collection_error(N) -> + + ReqId0 = erpc:send_request(N, fun () -> 0 end), + + ReqIdC0 = erpc:reqids_new(), + + ReqId1 = erpc:send_request(N, fun () -> receive after 600 -> 600 end end), + ReqIdC1 = erpc:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = erpc:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:{erpc, badarg} -> ok + end, + + ReqIdC2 = erpc:send_request(N, fun () -> receive after 800 -> erlang:halt() end end, req2, ReqIdC1), + ReqIdC3 = erpc:send_request(N, fun () -> receive after 200 -> error(errored) end end, req3, ReqIdC2), + ReqIdC4 = erpc:send_request(N, fun () -> exit(exited) end, req4, ReqIdC3), + ReqIdC5 = erpc:send_request(N, fun () -> receive after 400 -> throw(thrown) end end, req5, ReqIdC4), + + 5 = erpc:reqids_size(ReqIdC5), + + ReqIdC6 = try not_valid = erpc:wait_response(ReqIdC5, infinity, true) + catch exit:{{exception, exited}, req4, RIC6} -> RIC6 + end, + + 4 = erpc:reqids_size(ReqIdC6), + + try not_valid = erpc:wait_response(ReqIdC6, 2000, false) + catch error:{{exception, errored, _Stk}, req3, _} -> ok + end, + + try not_valid = erpc:wait_response(ReqIdC6, infinity, false) + catch throw:{thrown, req5, _} -> ok + end, + + {{response, 600}, req1, ReqIdC6} = erpc:wait_response(ReqIdC6, infinity, false), + + try not_valid = erpc:wait_response(ReqIdC6, 5000, false) + catch error:{{erpc, noconnection}, req2, ReqIdC6} -> ok + end, + + {response, 0} = erpc:wait_response(ReqId0), + + ok. + +send_request_check_reqid_collection(Config) when is_list(Config) -> + {ok, _P, N} = ?CT_PEER(#{connection => 0}), + send_request_check_reqid_collection_success(N), + send_request_check_reqid_collection_error(N), + ok. + +send_request_check_reqid_collection_success(N) -> + + ReqId0 = erpc:send_request(N, fun () -> 0 end), + + ReqIdC0 = erpc:reqids_new(), + + ReqIdC1 = erpc:send_request(N, fun () -> receive after 600 -> 600 end end, req1, ReqIdC0), + 1 = erpc:reqids_size(ReqIdC1), + + ReqId2 = erpc:send_request(N, fun () -> receive after 200 -> 200 end end), + ReqIdC2 = erpc:reqids_add(ReqId2, req2, ReqIdC1), + 2 = erpc:reqids_size(ReqIdC2), + + ReqIdC3 = erpc:send_request(N, fun () -> receive after 400 -> 400 end end, req3, ReqIdC2), + 3 = erpc:reqids_size(ReqIdC3), + + Msg0 = next_msg(), + + no_response = erpc:check_response(Msg0, ReqIdC3, true), + + {{response, 200}, req2, ReqIdC4} = erpc:check_response(next_msg(), ReqIdC3, true), + 2 = erpc:reqids_size(ReqIdC4), + + {{response, 400}, req3, ReqIdC5} = erpc:check_response(next_msg(), ReqIdC4, true), + 1 = erpc:reqids_size(ReqIdC5), + + {{response, 600}, req1, ReqIdC6} = erpc:check_response(next_msg(), ReqIdC5, true), + 0 = erpc:reqids_size(ReqIdC6), + + no_request = erpc:check_response(Msg0, ReqIdC6, true), + + {response, 0} = erpc:check_response(Msg0, ReqId0), + + ok. + +send_request_check_reqid_collection_error(N) -> + + ReqId0 = erpc:send_request(N, fun () -> 0 end), + + ReqIdC0 = erpc:reqids_new(), + + ReqId1 = erpc:send_request(N, fun () -> receive after 600 -> 600 end end), + ReqIdC1 = erpc:reqids_add(ReqId1, req1, ReqIdC0), + + ReqIdC2 = erpc:send_request(N, fun () -> receive after 800 -> erlang:halt() end end, req2, ReqIdC1), + + ReqIdC3 = erpc:send_request(N, fun () -> receive after 200 -> error(errored) end end, req3, ReqIdC2), + + ReqIdC4 = erpc:send_request(N, fun () -> exit(exited) end, req4, ReqIdC3), + + ReqIdC5 = erpc:send_request(N, fun () -> receive after 400 -> throw(thrown) end end, req5, ReqIdC4), + + 5 = erpc:reqids_size(ReqIdC5), + + Msg0 = next_msg(), + + no_response = erpc:check_response(Msg0, ReqIdC5, true), + + ReqIdC6 = try not_valid = erpc:check_response(next_msg(), ReqIdC5, true) + catch exit:{{exception, exited}, req4, RIC6} -> RIC6 + end, + + try not_valid = erpc:check_response(next_msg(), ReqIdC6, false) + catch error:{{exception, errored, _Stk}, req3, ReqIdC6} -> ok + end, + + try not_valid = erpc:check_response(next_msg(), ReqIdC6, false) + catch throw:{thrown, req5, ReqIdC6} -> ok + end, + + {{response, 600}, req1, ReqIdC6} = erpc:check_response(next_msg(), ReqIdC6, false), + + try not_valid = erpc:check_response(next_msg(), ReqIdC6, false) + catch error:{{erpc, noconnection}, req2, ReqIdC6} -> ok + end, + + {response, 0} = erpc:check_response(Msg0, ReqId0), + + ok. + multicall(Config) -> {ok, Node1} = start_peer_node(Config), {ok, Node2} = start_peer_node(Config), @@ -1743,6 +2070,9 @@ f2() -> timer:sleep(500), halt(). +next_msg() -> + receive M -> M end. + flush_msgq() -> flush_msgq(0). flush_msgq(N) -> -- 2.34.1
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor