Ambient references are a novel type of remote object references. Remote object references are “pointers across the network” and are a frequently recurring abstraction in both distributed OO languages and distributed middleware. Ambient references are designed to refer to objects in mobile networks. What exactly constitutes a mobile network and how it differs from traditional, stationary networks is described elsewhere.
One may wonder why new referencing abstractions are required for mobile networks. In order to motivate the need for new referencing abstractions at the language level, we list a number of desirable properties of remote references for mobile networks which current remote referencing abstractions do not offer:
Ambient references have undergone several design iterations. We summarize here the latest design in AmbientTalk/2, as it is fully described in my PhD dissertation.
Ambient references unify two concepts: they are both a peer-to-peer discovery channel and an asynchronous communication channel to a remote object. In AmbientTalk/2, they are represented as a special kind of far references. This implies that ambient references, like far references, are object references that can only carry asynchronously sent messages.
An ambient reference is initialized with a type tag: a network-wide name that describes a service known to the distributed peers. At any point in time, an ambient reference designates the set of proximate services whose type matches its type tag. For example:
def printers := ambient: Printer;
The variable printers
refers to an ambient reference which, at any point in time, designates all nearby Printer
objects. An ambient reference designates a volatile set of proximate objects: the set may grow or shrink as devices are moving in and out of wireless communication range.
It is possible to restrict the set of service objects which an ambient reference may designate even further, by using so-called filter queries. For example, suppose one only wants to send documents to a printer that supports a resolution greater than 400 dpi. This can be expressed as:
def printers := ambient: Printer where: { |p| p.dpi > 400 };
An ambient reference can be used in two modes: as a point-to-point communication channel to any object in its designated set, or as a one-to-many communication channel to all objects in its designated set.
Here is how to send a point-to-point message to any matching printer:
printers<-print(document)@One;
The ←
syntax denotes an asynchronous message send in AmbientTalk/2. The @
syntax is used to annotate an asynchronous message with additional metadata. In the above code, the @One
annotation signifies to the ambient reference that the print
message should be sent to only one of the nearby Printer
services.
AmbientTalk/2 supports future-type message passing, which basically allows the programmer to access the return value of the print
method via a future. Futures are a very well known concept that unify asynchronous message passing with return values. More specifically, the futures employed in AmbientTalk/2 are based on the non-blocking futures of the E programming language. For more details, we refer to the papers below, but as a teaser, here's how one can notify the user when the print job has been sent:
def answer := printers<-print(document)@One; when: answer becomes: { |ack| system.println("document successfully transmitted"); }
Ambient references seamlessly support roaming via point-to-point messaging: multiple messages sent to the same ambient reference may be delivered to different printing services. The programmer need not to worry about rebinding or reassigning the pointer to another Printer
once the previously used Printer
has moved out of range.
We will discuss later what happens if there are no matching Printer
services at the time the message is sent.
The following piece of code illustrates how to broadcast a receiveTextMessage
message to all proximate ChatPeer
objects in a virtual chat room:
def peers := ambient: ChatPeer; ... peers<-receiveTextMessage("hello world")@All;
Note the use of the @All
annotation to signify to the ambient reference that the message should be broadcast to all nearby chat peers, not just to one of them.
Ambient references extend AmbientTalk/2's support for futures such that they can be used for broadcast messages. The issue with broadcast messages is that they may be received by more than one receiver object. As a result, they may return more than one return value. To deal with this, we introduce multifutures: futures that may be resolved multiple times. Multifutures have their own kind of listeners that can be used to observe state changes in the multifuture. For example, to gather replies to a text message, one may write:
def multifuture := peers<-receiveTextMessage("hello world")@All; whenEach: multifuture becomes: { |reply| // process the reply }
The above block closure { |reply| … }
is applied for each return value sent back to the multifuture associated with the broadcast receiveTextMessage
message.
Ambient references have been designed for Mobile ad hoc networks in which transient network partitions are commonplace. Hence, ambient references support a form of message passing which is resilient to temporary network failures.
Any message sent to an ambient reference may be annotated with @Expires(timeout)
. The timeout
parameter is a time period that describes how long the message will remain available for reception by proximate objects. We refer to it as the message's expiration period. When a message is sent to an ambient reference whose designated set of objects is empty, the message is not necessarily “lost”. The message will remain buffered within the ambient reference for its expiration period.
When an ambient reference discovers a receiver for its buffered messages in the ad hoc network, the messages are forwarded to the receiver(s). At this point, the message's remaining expiration period serves as a timeout for the outgoing message: if no reply to the message is received within the remaining timeout period, the message's associated future is ruined with a TimeoutException
. Here is a modified version of the Printer
example that deals with disconnected operation and failures:
def reply := printers<-print(document)@[One,Expires(minutes(2))]; when: reply becomes: { |ack| system.println("document successfully transmitted"); catch: TimeoutException using: { |e| system.println("request timed out"); }
If there are no matching Printer
service objects at the time the print
message is sent, the message will be buffered for at least 2 minutes within the ambient reference. If the message could not be sent to any printer, or it was sent to a printer but no reply was received within the 2 minute expiration period, the future associated with the message is ruined with a TimeoutException
.
What is important to recall is that an ambient reference is a remote object reference that allows programmers to intensionally designate a volatile set of proximate objects. Ambient references support asynchronous point-to-point or one-to-many communication styles. Programmers can tweak a message's expiration period to make message delivery more resilient to temporary failures, and to perform failure handling.
Also see the Posters section for a poster describing ambient references graphically.