<div dir="ltr"><div>A lot of this seems neat and useful.</div><div>I am however getting a bit of an odd feeling from this section:</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">* `monitor/3`. The `monitor/3` BIF is an extension of the `monitor/2` BIF<br> where the third argument is an option list. As of its introduction the<br> only accepted option is '{alias, unalias | demonitor | reply_demonitor}'.<br> The first element of the two tuple indicates that we want the returned<br> monitor reference to also work as an alias. The second element determines<br> how the alias should be deactivated:<br> * `unalias` - The alias will remain until it has been deactivated by the<br> `unalias/1` BIF<br> * `demonitor` - The alias will be deactivated when the monitor is<br> deactivated. That is, either when the `demonitor()` BIF is called on<br> the monitor, or when the monitor is automatically deactivated by the<br> reception of a `'DOWN'` message. The alias can still be deactivated<br> before this happens by calling the `unalias/1` BIF.<br> * `reply_demonitor` - The alias will be deactivated when either the<br> monitor is deactivated or a message that has been passed using the<br> alias is received. If the alias is deactivated due to a message passed<br> using the alias, the monitor is also deactivated as if the `demonitor()`<br> BIF had been called.</blockquote><div><br></div><div>so the tuple is `{alias, TeardownMethod}`. How is `reply_demonitor` supposed to work with regards to "received"? Is it removed when the message is inserted in the mailbox (and therefore can be faster than demonitor + [flush]), when it is processed by a receive expression, or when it exactly matches? Would it actually be possible to extend this mechanism to regular monitors?<br></div><div><br></div><div>Wouldn't it make sense for `erlang:demonitor(Ref, Opts)` to have the option list contain `unalias` instead of specifying that when setting up the monitor?<br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, Sep 2, 2020 at 1:37 AM Rickard Green <<a href="mailto:rickard@erlang.org">rickard@erlang.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"> Author: Rickard Green <rickard(at)erlang(dot)org><br> Status: Draft<br> Type: Standards Track<br> Erlang-Version: 24.0<br> Created: 01-Sept-2019<br> Post-History:<br>****<br>EEP 53: Process aliases preventing late replies reaching clients<br>----<br><br><br>Abstract<br>========<br><br>Currently there exists no lightweight mechanism for preventing late replies<br>from a server to a client after a timeout or connection loss has occurred.<br>The only way to prevent late replies today is to make the request via<br>a proxy process.<br><br>The proposed process alias feature is a lightweight mechanism that solves<br>the above problem. A process alias is similar to a registered name that<br>is used temporarily while a request is outstanding. If the request times<br>out or the connection to the server is lost, the alias is deactivated which<br>prevents a late reply from reaching the client.<br><br>Copyright<br>=========<br><br>This document has been placed in the public domain.<br><br><br>Specification<br>=============<br><br>An alias is of the Erlang type `reference()` and can be used as destination<br>when sending using the `!` operator, or when sending using the `erlang:send()`<br>and `erlang:send_nosuspend()` BIFs. An alias can be used both on local<br>node and on remote nodes in a distributed system. The alias identifies a<br>process that exist, or has existed, on the node with the node name returned<br>by `node(Alias)`.<br><br>All references will, as of this, be accepted as destination in the message send<br>operations listed above. If the reference is not an alias or a previous alias<br>that has been deactivated, the message will silently be dropped.<br><br>These new BIFs are introduced:<br><br>* `alias/0`, `alias/1`. The `alias()` BIF creates and returns an alias which<br> can be used when sending messages to the process that called the `alias()`<br> BIF.<br>* `unalias/1`. The `unalias(Alias)` BIF deactivates an alias that identifies<br> the calling process. The BIF returns `true` if the alias `Alias` identified<br> the calling process and thus was deactivated; otherwise, no change of the<br> alias state was made and `false` is returned.<br>* `monitor/3`. The `monitor/3` BIF is an extension of the `monitor/2` BIF<br> where the third argument is an option list. As of its introduction the<br> only accepted option is '{alias, unalias | demonitor | reply_demonitor}'.<br> The first element of the two tuple indicates that we want the returned<br> monitor reference to also work as an alias. The second element determines<br> how the alias should be deactivated:<br> * `unalias` - The alias will remain until it has been deactivated by the<br> `unalias/1` BIF<br> * `demonitor` - The alias will be deactivated when the monitor is<br> deactivated. That is, either when the `demonitor()` BIF is called on<br> the monitor, or when the monitor is automatically deactivated by the<br> reception of a `'DOWN'` message. The alias can still be deactivated<br> before this happens by calling the `unalias/1` BIF.<br> * `reply_demonitor` - The alias will be deactivated when either the<br> monitor is deactivated or a message that has been passed using the<br> alias is received. If the alias is deactivated due to a message passed<br> using the alias, the monitor is also deactivated as if the `demonitor()`<br> BIF had been called.<br><br>The `spawn_opt()` and `spawn_request()` BIFs have also been extended to<br>accept an option `{monitor, MonitorOpts}` where `MonitorOpts` correspond to<br>the option list of the `monitor/3` BIF.<br><br>Full documentation of these BIFs and options can be found via<br>[pull request #2735](<a href="https://github.com/erlang/otp/pull/2735" target="_blank">https://github.com/erlang/otp/pull/2735</a>)<br>containing the reference implementation.<br><br>It is not possible to retrieve the process identifier of the process<br>identified by an alias, and it is not possible to test if a reference is an<br>alias or not.<br><br><br>Motivation<br>==========<br><br>As previously stated it is possible to prevent late replies by using a<br>proxy process that forwards the reply to the client. By spawning the proxy<br>process and send its process identifier to the server instead of the<br>clients own process identifier, the proxy can be terminated when the<br>operation times out or the connection is lost. Since the proxy process<br>is not alive, a reply will be silently dropped and no stray message<br>will reach the previous client of the request. This however both makes<br>the code more complicated and less efficient than it needs to be. The<br>inefficiency comes from both the need to create, schedule, execute, and<br>terminate the proxy process and the extra copying of data over the proxy<br>process.<br><br>When the author of the client code has full control over the client process<br>such late replies can be handled without a proxy since the code can be<br>aware of these potential stray messages and drop them when received. This<br>is, however, not possible when implementing library code. You then either<br>need to use a proxy process, as done by the `gen_statem` behavior, or<br>accept that the client process may get stray messages after a call, as<br>done by `gen_server` behavior.<br><br>Process aliases solves these issues with a very small overhead.<br><br><br>Rationale<br>=========<br><br>Why use the reference data type for alias?<br>------------------------------------------<br><br>This is more or less what the reference data type is there for. A data type<br>that can identify a huge amount of different entities. References are unique<br>and contain a node identifier identifying the the node it originates from.<br>This makes it easy to identify a specific process on a specific node while<br>also identifying different aliases created by the same process. The embedded<br>node identifier makes it easy to provide distribution transparency.<br><br>Why not make alias an opaque data type?<br>---------------------------------------<br><br>The expected most common use case is in a client server request. Such as<br>`gen_server:call()`. Client server requests in Erlang are typically made<br>while monitoring the server from the client. In order to minimize the data<br>produced and sent in the request we want to reuse the reference created for<br>identification of the monitor to also function as an alias. Since the monitor<br>identifier is documented as a reference and is not opaque (which one can<br>argue was a design mistake when introducing monitors), it becomes hard not<br>to document the type of an alias as a reference as well.<br><br>Why not allow references as registered names in the already existing API?<br>-------------------------------------------------------------------------<br><br>There are two reasons. Distribution transparency and scalability.<br><br>Distribution transparency is really desirable since the user can use the<br>functionality the same way regardless of whether it is a node local operation<br>or node remote operation. The name registration API is not distribution<br>transparent.<br><br>Regarding scalability. Due to how the name registration API has been designed<br>we need some sort of table in order to implement the API. This table will be<br>written to and read from by processes that are executing in parallel. In the<br>use case we are focusing on, names (aliases) are expected to be temporary and<br>created in huge amounts. That is, there will be large amounts of modifications<br>of this table from processes executing on different processors. This will<br>make it challenging to implement such a table that scales well.<br><br>In the proposed solution the information needed to route the message to the<br>correct place is saved in the alias itself, the reference. The information<br>needed to determine if the message passed via the alias should be dropped or<br>passed along is saved in the process identified by the alias. That is, all<br>information needed is distributed to where it is needed instead of being<br>centralized in a node global table. This approach of distributed information<br>introduce no new synchronization points at all when it has been fully<br>implemented (more on that below) which will scale extremely well. An<br>implementation based on a node global table can *never* compete scalability<br>wise with that.<br><br>The already existing functionality for registered names cannot be implemented<br>using this distributed information approach, but needs this centralized<br>storage of names. That is, the already existing API cannot be used.<br><br>Besides node identifier a reference today contains three 32-bit words of data<br>or in other words 96-bits of data. Of these 96 bits only 82 bits are allowed<br>to be passed over the distribution to another node. This for historical<br>reasons. While a reference resides locally it can however contain more or<br>less unlimited amount of data. 82-bits are not enough to make a reference<br>unique on the local node and at the same time uniquely identify a node local<br>process. In order to be able to store all information needed in alias, the<br>reference data type needs to be extended.<br><br>In the proposed solution references used as aliases are extended to use<br>five 32-bit words on 64-bit architectures and four 32-bit words on 32-bit<br>architectures. Since that much data in a reference cannot be passed over<br>the distribution today, the reference implementation saves aliases that<br>are alive in a node global table. When a node local alias enters the local<br>node over the distribution one needs to look it up in this table in order to<br>be able to restore it to its actual value. While aliases are passed around<br>locally there is no need for look-ups in this table.<br><br>The reference implementation also modifies the distribution protocol to<br>allow references with up to five 32-bit values. For backwards compatibility<br>reasons this modification of the distribution protocol cannot be used at once<br>when aliases are introduced. This since we need to be able to communicate with<br>older nodes from previous releases for a while. When this has been living in<br>the system for enough time (expected to be OTP 26) we can begin sending<br>references with up to five 32-bit words and remove the usage of the table<br>mapping references over the distribution to aliases. That is, it is not until<br>this happens that the alias implementation is fully complete.<br><br>Why is it not possible to get the PID of the process that an alias refers to?<br>-----------------------------------------------------------------------------<br><br>Most importantly there is no need to know the PID of the process that an<br>alias refers to in order to solve the problems that alias are intended<br>to solve. The user is expected to utilize alias in a protocol where one knows<br>whether a reference is an alias or not and should not need to know the PID of<br>the process that it refers to.<br><br>Besides the above there are also other issues with such functionality. The<br>content of a reference is just a large integer. In order to keep distribution<br>transparency one would either have to specify how this integer should be<br>interpreted or require synchronous signaling with the node where the<br>identified process resides. The synchronous signal-ling will be very<br>expensive. By specifying how the reference integer should be interpreted we<br>would prevent future changes to how the integer of the reference should be<br>interpreted which might prevent future optimizations, improvements and new<br>features. Up until the time when large references with five 32-bit words can<br>be passed over the distribution, synchronous communication is also the only<br>option on how to implement such functionality.<br><br>If we should mimic the `whereis()` function of the registered name API where<br>you also can see if a name is currently registered, no other option than<br>synchronous signaling with the process identified by the alias is possible.<br><br>Why is it not possible to test if a reference is an alias?<br>----------------------------------------------------------<br><br>The same reason as to why it is not possible to get the PID of the<br>process that is referred to by an alias.<br><br>Why not allow registration of arbitrary Erlang terms instead?<br>-------------------------------------------------------------<br><br>Such a feature could solve the same issue that aliases are intended to<br>solve, but there are problems with such an approach.<br><br>Terms other than pids, ports, and references do not have a node identifier<br>embedded into the data type. For such data types you need some other way<br>to identify the node of where the name is registered. In the current case<br>of atoms as registered names, this is done by wrapping the name in a<br>two-tuple that contains the node name. Something like this is needed for<br>all other terms than just plain pids, ports, and references. This also<br>introduce a problem. Is a two-tuple just a name or a name plus a node<br>identifier?<br><br>Should it be possible to register a PID as a name for another process?<br>This would force all send operations to first lookup the PID in the<br>table of registered names before performing the operation. This will<br>cost performance in all send operations. The same is true for ports.<br><br>We don't think registration of arbitrary terms should be implemented<br>due to the problems that arise. Current registration feature that only<br>allows atoms can however be a bit too limiting when you need to register<br>a number of processes for the same service. An option could be to allow<br>registration of two-tuples containing an atom and an integer. Perhaps<br>other terms such as strings should also be allowed, but arbitrary terms<br>should not be allowed.<br><br>Allowing references as registered names implies scalability bottlenecks<br>not present in the alias API. That is, this would be an inferior solution<br>to the problem we set out to solve.<br><br>One probably wants to extend name registration with more allowed terms<br>than just atoms, but this for solving other problems than what aliases<br>are intended to solve. The name registration API does not fit aliases<br>so we don't see that aliases should be combined with such an extension<br>of the registration API. The alias solution solves the problem we set out<br>to solve, so this eep is limited to that.<br><br><br>Backwards Compatibility<br>=======================<br><br>The alias feature is a pure extension, so there are no real backwards<br>compatibility issues.<br><br>In order to be able to communicate aliases over Erlang nodes from<br>previous releases we cannot pass large references over the distribution<br>and therefore need to keep information about aliases in a node global<br>table. The implementation benefits from being able to pass larger<br>references over the distribution, but will not do so until we can make<br>it mandatory to be able to handle such large references. Both OTP 24<br>and OTP 25 will be able to handle large references over the distribution<br>and since we only guarantee distribution compatibility with the two<br>closest releases backwards and forwards we can then make large<br>references mandatory in OTP 26.<br><br>This node global table for alias introduce an overhead when utilizing<br>aliases compared to sending using the PID of the process. This due<br>to allocation and manipulation of table structures. Comparing to the<br>existing solution of utilizing a proxy process in order to<br>prevent stray messages the overhead of this node global table for<br>aliases is small. Fortunately this node global table also only need to<br>be present temporarily and can be removed in OTP 26.<br><br><br>Reference Implementation<br>========================<br><br>The reference implementation is provided by<br>[pull request #2735](<a href="https://github.com/erlang/otp/pull/2735" target="_blank">https://github.com/erlang/otp/pull/2735</a>).<br><br>Beside implementation of the alias feature. The pull request also contain<br>usage of aliases in the gen behaviors such as gen_server. Due to this it is<br>now also possible to implement `receive_response()` functionality similar to<br>`erpc:receive_response()` which also have been implemented:<br><br>* `gen_server:receive_response/2`<br>* `gen_statem:receive_response/2`<br>* `gen_event:receive_response/2`<br><br><br>[EmacsVar]: <> "Local Variables:"<br>[EmacsVar]: <> "mode: indented-text"<br>[EmacsVar]: <> "indent-tabs-mode: nil"<br>[EmacsVar]: <> "sentence-end-double-space: t"<br>[EmacsVar]: <> "fill-column: 70"<br>[EmacsVar]: <> "coding: utf-8"<br>[EmacsVar]: <> "End:"<br>[VimVar]: <> "vim: set fileencoding=utf-8 expandtab shiftwidth=4 softtabstop=4:"<br><div><br></div>-- <br><div dir="ltr">Rickard Green, Erlang/OTP, Ericsson AB</div></div>
_______________________________________________<br>
eeps mailing list<br>
<a href="mailto:eeps@erlang.org" target="_blank">eeps@erlang.org</a><br>
<a href="http://erlang.org/mailman/listinfo/eeps" rel="noreferrer" target="_blank">http://erlang.org/mailman/listinfo/eeps</a><br>
</blockquote></div>