erpc(3)
π¦ erpc β Enhanced Remote Procedure Call π¦
okay bestie, gather round π this is erpc(3), straight out of the Erlang Module Definition section of the manual. it's the glow-up of remote procedure calls and honestly? it ate. let's get into it.
πΈ NAME πΈ
erpc β Enhanced Remote Procedure Call. no cap, that's the whole name, that's the whole vibe. π
β¨ DESCRIPTION β¨
so, this module provides services similar to Remote Procedure Calls. a remote procedure call is a method to call a function on a remote node and collect the answer. it's what you use for collecting information on a remote node, or for running a function with some specific side effects on the remote node. literally the group chat but for nodes. π
this is an enhanced subset of the operations provided by the rpc module. enhanced in the sense that it makes it possible to distinguish between returned value, raised exceptions, and other errors. erpc also has better performance and scalability than the original rpc implementation. however, the current rpc module will utilize erpc in order to also provide these properties when possible. so like, rpc is wearing erpc's outfit when it can. iconic behavior.
in order for an erpc operation to succeed, the remote node also needs to support erpc. typically only ordinary Erlang nodes as of OTP 23 have erpc support. π
note that it is up to the user to ensure that correct code to execute via erpc is available on the involved nodes. that one's on you, bestie, not on me. π
NOTE: for some important information about distributed signals, see the Blocking Signaling Over Distribution section in the Processes chapter of the Erlang Reference Manual. Blocking signaling can, for example, cause timeouts in erpc to be significantly delayed. not me finding out my timeout was queued behind a whole signal backlog π
π DATA TYPES π
call_options()
call_options() ::
#{timeout => Timeout :: timeout_time(),
always_spawn => AlwaysSpawn :: boolean()}.
options to be used in call/3,5 and multicall/3,5 functions. here's the tea on each key:
timeoutβ upper time limit for call operations to complete, seetimeout_time/0. Default:infinity. (yes, forever. main character energy. β¨)always_spawnβ iftrue, theapply()will always be performed in a freshly spawned process. iffalse, the calling process may be used instead, if possible. Default:false.
caught_call_exception()
caught_call_exception() ::
{throw, Throw :: term()} |
{exit, {exception, Reason :: term()}} |
{error,
{exception, Reason :: term(), StackTrace :: [stack_item()]}}
|
{exit, {signal, Reason :: term()}} |
{error, {erpc, Reason :: term()}}.
request_id()
-opaque request_id()
an opaque request identifier. for more information see send_request/4. it's giving "don't look at me, i'm opaque" π
request_id_collection()
-opaque request_id_collection()
an opaque collection of request identifiers (request_id/0) where each request identifier can be associated with a label chosen by the user. for more information see reqids_new/0. it's a whole friend group of request ids, each with a lil name tag. π«Ά
stack_item()
stack_item() ::
{Module :: atom(),
Function :: atom(),
Arity :: arity() | (Args :: [term()]),
Location ::
[{file, Filename :: string()} |
{line, Line :: pos_integer()}]}.
timeout_time()
timeout_time() :: 0..4294967295 | infinity | {abs, integer()}.
the timeout time used by erpc functions. the value can be:
0..4294967295β timeout relative to current time in milliseconds.infinityβ infinite timeout. that is, the operation will never time out. she's committed. π{abs, Timeout}β an absolute Erlang monotonic time timeout in milliseconds. that is, the operation will time out whenerlang:monotonic_time(millisecond)returns a value larger than or equal toTimeout.Timeoutis not allowed to identify a time further into the future than4294967295milliseconds. 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 (request_id_collection/0), since you do not have to recalculate the relative time until the deadline over and over again. one deadline, one value, zero math homework. periodt. β
π₯ FUNCTIONS π₯
call/3 β call(Node, Fun, TimeoutOrOptions) -> Result
call(Node, Fun, TimeoutOrOptions) -> Result
when
Node :: node(),
Fun :: function(),
TimeoutOrOptions :: timeout_time() | call_options(),
Result :: term().
equivalent to:
erpc:call(Node, erlang, apply, [Fun,[]], #{timeout => Timeout})
may raise all the same exceptions as call/5 plus an {erpc, badarg} error exception if Fun is not a fun of zero arity. so like, pass her a zero-arity fun or catch these hands (and by hands i mean a badarg error). π
call/5 β call(Node, Module, Function, Args, TimeoutOrOptions) -> Result
call(Node, Module, Function, Args, TimeoutOrOptions) -> Result
when
Node :: node(),
Module :: atom(),
Function :: atom(),
Args :: [term()],
TimeoutOrOptions :: timeout_time() | call_options(),
Result :: term().
evaluates apply(Module, Function, Args) on node Node and returns the corresponding value Result.
TimeoutOrOptions can be either a timeout time or a call options map (since OTP 28.0).
the call() function only returns if the applied function successfully returned without raising any uncaught exceptions, the operation did not time out, and no failures occurred. in all other cases an exception is raised. so if you get a return value, bestie, you truly made it. β¨
the following exceptions, listed by exception class, can currently be raised by call():
throwβ the applied function calledthrow(Value)and did not catch this exception. the exception reasonValueequals the argument passed tothrow/1.exitβ exception reason:{exception, ExitReason}β the applied function calledexit(ExitReason)and did not catch this exception. the exit reasonExitReasonequals the argument passed toexit/1.{signal, ExitReason}β the process that applied the function received an exit signal and terminated due to this signal. the process terminated with exit reasonExitReason.
errorβ exception reason:{exception, ErrorReason, StackTrace}β a runtime error occurred which raised an error exception while applying the function, and the applied function did not catch the exception. the error reasonErrorReasonindicates the type of error that occurred.StackTraceis formatted as when caught in atry/catchconstruct. theStackTraceis limited to the applied function and functions called by it.{erpc, ERpcErrorReason}β theerpcoperation failed. the followingERpcErrorReasons are the most common ones:badargβ if any one of these are true:Nodeis not an atom.Moduleis not an atom.Functionis not an atom.Argsis not a list. note that the list is not verified to be a proper list at the client side.Timeoutis invalid.
noconnectionβ the connection toNodewas lost or could not be established. the function may or may not be applied. πsystem_limitβ theerpcoperation failed due to some system limit being reached. this typically due to failure to create a process on the remote nodeNode, but can be other things as well.timeoutβ theerpcoperation timed out. the function may or may not be applied. the classic "did she text back or not" situation, ngl.notsupβ the remote nodeNodedoes not support thiserpcoperation.
if the erpc operation fails, but it is unknown if the function is/will be applied (that is, a timeout or a connection loss), the caller will not receive any further information about the result if/when the applied function completes. if the applied function explicitly communicates with the calling process, such communication may, of course, reach the calling process. (that's a lot of "maybe". very "we'll see" of the universe. π§)
NOTE: if thealways_spawnoption isfalse(which is the default), you cannot make any assumptions about the process that will perform theapply(). it may be the calling process itself, or a freshly spawned process. so don't be out here assuming identities. π
cast/2 β cast(Node, Fun) -> ok
cast(Node, Fun) -> ok when Node :: node(), Fun :: function().
equivalent to:
erpc:cast(Node,erlang,apply,[Fun,[]])
cast/2 fails with an {erpc, badarg} error exception if:
Nodeis not an atom.Funis not a a fun of zero arity.
(yes, "a a". i said what the manual said, bestie, character for character. π )
cast/4 β cast(Node, Module, Function, Args) -> ok
cast(Node, Module, Function, Args) -> ok
when
Node :: node(),
Module :: atom(),
Function :: atom(),
Args :: [term()].
evaluates apply(Module, Function, Args) on node Node. no response is delivered to the calling process. cast() returns immediately after the cast request has been sent. any failures beside bad arguments are silently ignored. she said "anyway β¨" and walked off. no receipts, no read receipts. π
cast/4 fails with an {erpc, badarg} error exception if:
Nodeis not an atom.Moduleis not an atom.Functionis not an atom.Argsis not a list. note that the list is not verified to be a proper list at the client side.
check_response/2 β check_response(Message, RequestId) -> {response, Result} | no_response
check_response(Message, RequestId) ->
{response, Result} | no_response
when
Message :: term(),
RequestId :: request_id(),
Result :: term().
check if a message is a response to a call request previously made by the calling process using send_request/4.
RequestId should be the value returned from the previously made send_request/4 call, and the corresponding response should not already have been received and handled to completion by check_response/2, receive_response/2, or wait_response/2. Message is the message to check.
if Message does not correspond to the response, the atom no_response is returned. if Message corresponds to the response, the call operation is completed and either the result is returned as {response, Result} where Result 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 call/4. that is, no {erpc, timeout} error exception can be raised. check_response() will fail with an {erpc, badarg} exception if/when an invalid RequestId is detected. π
If the erpc operation lowkey fails, but we don't actually know whether the function is/will ever be applied (aka, connection loss π), then the caller gets no further info about the result if/when the applied function finishes. If the applied function explicitly talks to the calling process, then sure, that communication can still reach the calling process. No cap.
π check_response/3
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().
Okay so this one checks whether a message is a response to a call request that corresponds to a request identifier saved in RequestIdCollection. And heads up bestie π«Ά β all the request identifiers in RequestIdCollection have to correspond to requests that were made using send_request/4 or send_request/6, and all of those requests have to have been made by the process calling this function. Fr fr, no sneaking in other processes' request ids.
Label is the label associated with the request identifier of the request that the response corresponds to. A request identifier gets a label associated with it either when you add a request identifier into a request identifier collection, or when you send the request using send_request/6.
Compared to check_response/2, the returned result for a specific request identifier β or an exception for a specific request identifier β gets wrapped in a 3-tuple. β¨ The first element of that tuple equals the value check_response/2 would have produced, the second element equals the Label associated with that specific request identifier, and the third element NewRequestIdCollection is a possibly modified request identifier collection. The error exception {erpc, badarg} is not associated with any specific request identifier, so it is not wrapped. Periodt.
If RequestIdCollection is empty, the atom no_request is returned. If Message doesn't correspond to any of the request identifiers in RequestIdCollection, the atom no_response is returned.
If Delete equals true, then the association with Label will have been deleted from RequestIdCollection in the resulting NewRequestIdCollection. If Delete equals false, then NewRequestIdCollection will equal RequestIdCollection. And just so you know β deleting an association is not for free π, and a collection that already contains handled requests can still be used by later calls to check_response/3, receive_response/3, and wait_response/3. BUT. Without deleting handled associations, those calls won't be able to detect when there are no more outstanding requests to handle, so you'll have to keep track of that some other way instead of relying on a no_request return. And one more thing: if you pass a collection that only contains associations of already handled or abandoned requests to check_response/3, it will always return no_response. Slay, but also, be careful.
Note that a response might have been consumed upon an {erpc, badarg} exception and, if so, will be lost forever. π Gone. Not coming back.
π¦ multicall/3
multicall(Nodes, Fun, TimeoutOrOptions) -> Result
when
Nodes :: [atom()],
Fun :: function(),
TimeoutOrOptions ::
timeout_time() | call_options(),
Result :: term().
Equivalent to erpc:multicall(Nodes, erlang, apply, [Fun,[]], #{timeout => Timeout}).
May raise all the same exceptions as multicall/5, plus an {erpc, badarg} error exception if Fun isn't a fun of zero arity. π
π multicall/5
multicall(Nodes, Module, Function, Args, TimeoutOrOptions) ->
Result
when
Nodes :: [atom()],
Module :: atom(),
Function :: atom(),
Args :: [term()],
TimeoutOrOptions ::
timeout_time() | call_options(),
Result ::
[{ok, ReturnValue :: term()} |
caught_call_exception()].
Performs multiple call operations in parallel on multiple nodes. π₯ That is, it evaluates apply(Module, Function, Args) on the nodes Nodes in parallel.
TimeoutOrOptions can be either a timeout time or a call options map (since OTP 28.0).
The result comes back as a list where the result from each node sits at the same position as the node name is placed in Nodes. Each item in the resulting list is formatted as one of these two:
{ok, Result}β the call operation for this specific node returnedResult.{Class, ExceptionReason}β the call operation for this specific node raised an exception of classClasswith exception reasonExceptionReason. These correspond to the exceptions thatcall/5can raise.
multicall/5 fails with an {erpc, badarg} error exception if:
Nodesis not a proper list of atoms. Heads up though β some requests may already have been sent when the failure happens. That is, the function may or may not be applied on some nodes. π¬Moduleis not an atom.Functionis not an atom.Argsis not a list. Note that the list is not verified to be a proper list on the client side.
The call erpc:multicall(Nodes, Module, Function, Args) is equivalent to the call erpc:multicall(Nodes, Module, Function, Args, infinity). These calls are also equivalent to calling my_multicall(Nodes, Module, Function, Args) below, if you disregard performance and failure behavior. multicall() can utilize a selective receive optimization which removes the need to scan the message queue from the beginning to find a matching message. The send_request()/receive_response() combo, however, cannot utilize that optimization. π
my_multicall(Nodes, Module, Function, Args) ->
ReqIds = lists:map(fun (Node) ->
erpc:send_request(Node, Module, Function, Args)
end,
Nodes),
lists:map(fun (ReqId) ->
try
{ok, erpc:receive_response(ReqId, infinity)}
catch
Class:Reason ->
{Class, Reason}
end
end,
ReqIds).
If an erpc operation fails, but it's unknown whether the function is/will be applied (that is, a timeout, connection loss, or an improper Nodes list), then the caller gets no further information about the result if/when the applied function completes. If the applied function communicates with the calling process, that communication may, of course, still reach the calling process.
NOTE: If thealways_spawnoption isfalse(which is the default), you cannot make any assumptions about the processes that will perform theapply()s. It might be the calling process itself, or freshly spawned processes, or a mix of both. π€·βοΈ
πΈ multicast/2
multicast(Nodes, Fun) -> ok
when Nodes :: [node()], Fun :: function().
Equivalent to erpc:multicast(Nodes,erlang,apply,[Fun,[]]).
multicast/2 fails with an {erpc, badarg} error exception if:
Nodesis not a proper list of atoms.Funis not a a fun of zero arity.
π multicast/4
multicast(Nodes, Module, Function, Args) -> ok
when
Nodes :: [node()],
Module :: atom(),
Function :: atom(),
Args :: [term()].
Evaluates apply(Module, Function, Args) on the nodes Nodes. No response is delivered to the calling process. multicast() returns immediately after the cast requests have been sent. Any failures beside bad arguments are silently ignored. π
multicast/4 fails with an {erpc, badarg} error exception if:
Nodesis not a proper list of atoms. Note that some requests may already have been sent when the failure occurs. That is, the function may or may not be applied on some nodes.Moduleis not an atom.Functionis not an atom.Argsis not a list. Note that the list is not verified to be a proper list on the client side.
β receive_response/2
receive_response(RequestId, Timeout) -> Result
when
RequestId :: request_id(),
Timeout :: timeout_time(),
Result :: term().
Receive a response to a call request that the calling process previously made using send_request/4. π«Ά
RequestId should be the value returned from the previously made send_request/4 call, and the corresponding response should not already have been received and handled to completion by receive_response(), check_response/4, or wait_response/4.
Timeout sets an upper time limit on how long to wait for a response. If the operation times out, the request identified by RequestId will be abandoned, and then an {erpc, timeout} error exception will be raised. That is, no response corresponding to the request will ever be received after a timeout. Periodt. π
If a response is received, the call operation is completed and either the result is returned or an exception is raised. The exceptions that can be raised correspond to the same exceptions that call/5 can raise. receive_response/2 will fail with an {erpc, badarg} exception if/when an invalid RequestId is detected, or if an invalid Timeout is passed.
If the erpc op fails but we don't know whether the function is or will be applied β think a timeout, or the connection just dipping π β the caller gets no further info about the result if/when that applied function eventually finishes. And yeah, if the applied function explicitly messages the calling process, that message might still land. fs, but that's how it is.
π«Ά receive_response/3 β the collection girlie
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().
Okay so this one receives a response to a call request that matches a request identifier saved in RequestIdCollection. Rules bestie: all request identifiers in RequestIdCollection must correspond to requests made with send_request/4 or send_request/6, and all those requests must have been made by the process calling this function. No outsourcing π
Label is the label associated with the request identifier of the request that the response corresponds to. A request identifier gets a label when you add it to a request identifier collection, or when you send the request using send_request/6.
Compared to receive_response/2, the result (or exception) tied to a specific request identifier gets wrapped in a 3-tuple. First element = the value receive_response/2 would've given you. Second element = the Label for that specific request identifier. Third element NewRequestIdCollection = a possibly modified request identifier collection. The error exceptions {erpc, badarg} and {erpc, timeout} are not associated with any specific request identifiers, so they do not get wrapped. periodt.
If RequestIdCollection is empty, you get the atom no_request back. Cute, low maintenance π
If the operation times out, all requests identified by RequestIdCollection are abandoned, and then an {erpc, timeout} error exception is raised. Translation: no responses for any of those request identifiers will ever show up after a timeout. The glow-up difference between receive_response/3 and wait_response/3? receive_response/3 abandons the requests at timeout so any potential future responses get ignored, while wait_response/3 does not. Know which one you're dating.
If Delete equals true, the association with Label will have been deleted from RequestIdCollection in the resulting NewRequestIdCollection. If Delete equals false, NewRequestIdCollection equals RequestIdCollection. And no, deleting an association is not for free πΈ β but a collection containing already-handled requests can still be used by later calls to receive_response/3, check_response/3, and wait_response/3.
β οΈ Heads up though: without deleting handled associations, those calls above can't detect when there are no more outstanding requests to handle. So you'll have to keep track some other way instead of relying on ano_requestreturn. Also: if you pass a collection that only contains associations of already-handled or abandoned requests toreceive_response/3, it will always block until a timeout determined byTimeoutfires. Not a vibe. π And one more: a response might have been consumed on an{erpc, badarg}exception, and if so, it's lost forever. Gone. Into the void.
π§Ί reqids_add/3 β save that id, attach a label
reqids_add(RequestId :: request_id(),
Label :: term(),
RequestIdCollection :: request_id_collection()) ->
NewRequestIdCollection :: request_id_collection().
Saves RequestId and associates a Label with that request identifier by adding this info to RequestIdCollection, then returns the resulting request identifier collection. It's giving organized β¨
π± reqids_new/0 β fresh, empty, ready
reqids_new() -> NewRequestIdCollection :: request_id_collection().
Returns a new empty request identifier collection. You use a request identifier collection when you wanna handle multiple outstanding requests at once. Multi-tasking queen π
Request identifiers of requests made by send_request/4 can be saved in a request identifier collection using reqids_add/3. Later, that collection of request identifiers can be used to get one response corresponding to a request in the collection β just pass the collection as argument to check_response/3, receive_response/3, and wait_response/3.
You can use reqids_size/1 to find out how many request identifiers are in a request identifier collection.
π reqids_size/1 β how many you got?
reqids_size(RequestIdCollection :: request_id_collection()) ->
non_neg_integer().
Returns the amount of request identifiers saved in RequestIdCollection. Counting era π’
π reqids_to_list/1 β the whole roster
reqids_to_list(RequestIdCollection :: request_id_collection()) ->
[{RequestId :: request_id(), Label :: term()}].
Returns a list of {RequestId, Label} tuples corresponding to all request identifiers with their associated labels present in the RequestIdCollection collection. Everyone accounted for π
π¨ send_request/2 β fire and forget (the fun edition)
send_request(Node, Fun) -> RequestId
when
Node :: node(),
Fun :: function(),
RequestId :: request_id().
Equivalent to erpc:send_request(Node, erlang, apply, [Fun, []]).
Fails with an {erpc, badarg} error exception if:
Nodeis not an atom.Funis not a fun of zero arity.
π¬ send_request/4 + the collection flavor
send_request(Node, Module, Function, Args) -> RequestId
when
Node :: node(),
Module :: atom(),
Function :: atom(),
Args :: [term()],
RequestId :: request_id();
(Node, Fun, Label, RequestIdCollection) ->
NewRequestIdCollection
when
Node :: node(),
Fun :: function(),
Label :: term(),
RequestIdCollection ::
request_id_collection(),
NewRequestIdCollection ::
request_id_collection().
Sends an asynchronous call request to the node Node. Sending it, not getting it back β that's the whole point π
send_request/4 returns a request identifier that you later pass to either receive_response/2, wait_response/2, or check_response/2 to get the response of the call request. Besides passing the request identifier straight to those functions, you can also add it into a request identifier collection using reqids_add/3. Then that collection can be passed as argument to receive_response/3, wait_response/3, or check_response/3 to get one response corresponding to a request in the collection. And if you're about to save the request identifier in a request identifier collection, you may wanna consider using send_request/6 instead. iykyk π
A call to the function my_call(Node, Module, Function, Args, Timeout) below is equivalent to the call erpc:call(Node, Module, Function, Args, Timeout) if one disregards performance. call() 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 send_request()/receive_response() combo, however, cannot use this optimization. no cap.
my_call(Node, Module, Function, Args, Timeout) ->
RequestId = erpc:send_request(Node, Module, Function, Args),
erpc:receive_response(RequestId, Timeout).
Fails with an {erpc, badarg} error exception if:
Nodeis not an atom.Moduleis not an atom.Functionis not an atom.Argsis not a list. Note that the list is not verified to be a proper list at the client side.
Equivalent to erpc:send_request(Node, erlang, apply, [Fun,[]]), Label, RequestIdCollection).
Fails with an {erpc, badarg} error exception if:
Nodeis not an atom.Funis not a fun of zero arity.RequestIdCollectionis detected not to be a request identifier collection.
π― send_request/6 β the full send, labeled and saved
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().
ok so this is the tail end of send_request/6 and honestly it's kinda iconic π
send_request(Node, Module, Function, Args, Label, RequestIdCollection)β fires off an async call request at node Node π«. The Label gets glued onto the request identifier for that operation and then dropped into the returned request identifier collection NewRequestIdCollection. Later you can pass that whole collection toreceive_response/3,wait_response/3orcheck_response/3to grab one response that corresponds to one of the requests in the collection. Matcha girlies stay matching π΅β¨
It's literally equivalent to erpc:reqids_add(erpc:send_request(Node, Module, Function, Args), Label, RequestIdCollection), except calling send_request/6 is slightly more efficient β same vibe, less overhead π
.
It fails with an {erpc, badarg} error exception if:
- Node is not an atom. π«
- Module is not an atom. π«
- Function is not an atom. π«
- Args is not a list. Heads up bestie: the list is not verified to be a proper list at the client side, so don't come crying later π
- RequestIdCollection is detected not to be a request identifier collection. π
wait_response(RequestId) -> {response, Result} | no_response
when RequestId :: request_id(), Result :: term().
Equivalent to erpc:wait_response(RequestId, 0). So basically: poll for a response message to a call request that this same calling process made earlier. No waiting around, just a lil vibe check π
wait_response(RequestId, WaitTime) ->
{response, Result} | no_response
when
RequestId :: request_id(),
WaitTime :: timeout_time(),
Result :: term().
Wait or poll for a response message to a call request that the calling process previously made using send_request/4. π«Ά
RequestId should be the value that the earlier send_request() call returned, and the matching response should not already have been received and handled to completion by check_response/2, receive_response/2, or wait_response(). No double-dipping, periodt β
WaitTime sets an upper time limit on how long we're waiting for that response. If nothing shows up before the WaitTime timeout triggers, you get the atom no_response back. And it's totally valid to keep waiting for a response as many times as you need, right up until a response has been received and completed by check_response(), receive_response(), or wait_response() β the saga continues πΈ
If a response does arrive, the call operation is complete and either the result comes back as {response, Result} where Result is whatever the applied function returned, or an exception gets raised. The exceptions you can get are the same ones call/4 can raise. That is: no {erpc, timeout} error exception can be raised here, bestie, so don't wait for one π. wait_response/2 will fail with an {erpc, badarg} exception if/when an invalid RequestId is detected, or if an invalid WaitTime is passed. Bad arg? Bad vibes. π
If the erpc operation fails, but it's unknown whether the function is/will be applied (like, a too large wait time value, or a connection loss), the caller will not receive any further information about the result if/when the applied function completes. And if the applied function explicitly communicates with the calling process, that communication may, of course, still reach the calling process. The lore might not reach you, but the side-chat might π€·ββοΈ
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 or poll for a response to a call request that corresponds to a request identifier saved in RequestIdCollection. Every request identifier in RequestIdCollection must correspond to requests made using send_request/4 or send_request/6, and all those requests must have been made by the process calling this function. No borrowing your friend's request ids π€
Label is the label associated with the request identifier of the request that the response corresponds to. A request identifier gets associated with a label when you add it into a request identifier collection, or when you send the request using send_request/6. β¨
Compared to wait_response/2, the returned result associated with a specific request identifier β or an exception associated with a specific request identifier β gets wrapped in a 3-tuple. The first element of that tuple equals the value wait_response/2 would have produced, the second element equals the Label associated with that specific request identifier, and the third element NewRequestIdCollection is a possibly modified request identifier collection. Note that the error exception {erpc, badarg} is not associated with any specific request identifier, so it will hence not be wrapped. It's giving unfiltered π
If RequestIdCollection is empty, no_request is returned. If no response is received before the WaitTime timeout triggers, the atom no_response 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 check_response(), receive_response(), or wait_response(). The difference between receive_response/3 and wait_response/3 is that receive_response/3 abandons requests at timeout so any potential future responses are ignored, while wait_response/3 does not. Ghosting vs. not ghosting, that's the whole plot twist π»
If Delete equals true, the association with Label will have been deleted from RequestIdCollection in the resulting NewRequestIdCollection. If Delete equals false, NewRequestIdCollection will equal RequestIdCollection. And note that deleting an association is not for free, and a collection containing already handled requests can still be used by subsequent calls to wait_response/3, check_response/3, and receive_response/3. But without deleting handled associations, those calls won't be able to detect when there are no more outstanding requests to handle β so you'll have to keep track of that some other way instead of relying on a no_request return. No free lunch, no free cleanup π§Ήπ
Also note: if you pass a collection that only contains associations of already handled or abandoned requests towait_response/3, it will always block until a timeout determined by WaitTime is triggered, and then returnno_response. It's just gonna sit there. Staring at you. β³
And one more, lowkey important: a response might have been consumed uppon an {erpc, badarg} exception, and if so, will be lost for ever. Gone. Deleted. Not coming back ππ
and that's a wrap on erpc(3), bestie!! π this page is from Ericsson AB, and it belongs to kernel version 11.0.3 β the erpc(3) manual page itself. go forth and make your distributed calls, mwah πβ¨π¦π
π¦ where this page comes from
- ubuntu stonking: erlang-doc 1:29.0.6+dfsg-1 Β· Apache-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms