<< Prev | - Up - | Next >> |
This chapter shows how to program mobile agents in Mozart. We call agent any distributed computation that is organized as a set of tasks. A task is a computation that uses the resources of a single site. By resource we mean the technical definition given in Section 2.1.5, for example, file system, peripherals, networking abilities, operating system access, etc. A task can initiate tasks on other sites, with well-defined specifications of what resources they should use. The distributed behavior of the agent is therefore in first instance decided by the agent itself.
Agents are therefore just resource-aware distributed computations, which can exist on one site or be spread out over more than one site. This definition of agent might seem unnecessarily general, but it is natural in Mozart, where it is as easy for an application to run on one site or a set of sites. For example, here's an agent A
that concurrently delegates 10 tasks to remote sites and waits until all are done before continuing. The agent servers are represented by AS0
and AS9
and the work is represented by functors containing the one-argument procedures P0
to P9
. Each procedure binds its argument to the result of its calculation.
declare
A=functor
define
X0 ... X9
{AS0 functor define {P0 X0} end}
...
{AS9 functor define {P9 X9} end}
{Wait X0} ... {Wait X9}
...
end
This is efficient. As the distribution model makes clear (see Section 2.1.3), the termination of each remote task is signaled to the original task by exactly one network message, which contains the result of the task's calculation.
The Mozart vision of a universe of agents is a set of fixed places, the agent servers, and a set of evolving computational ``webs'', each potentially covering many places at once. A web is what we call an ``agent''.
Let's start with a very simple example of an agent that goes somewhere, interrogates the operating system, and then comes back. We show the example in two parts. First, we show how to install agent servers so that the agent has somewhere to go. Then, we will program the agents.
To go to a site, there has to be something at the site that accepts the agent. We call it an agent server. An agent server accepts functors (which represent agents or parts of agents) and applies them on its site. To install an agent server on a site, we use the functor AgentServer
(see Section 4.1.6, below). When AgentServer
is installed on a site, then it creates an agent server module AS
on that site along with the following operations:
The agent server is accessed by AS.server
. A calculation is started asynchronously on the agent server by invoking {AS.server F}
, where F
is a functor that specifies the calculation and the resources it needs.
The operation {AS.publishserver FN}
, which when executed creates a file FN
that contains a ticket to the site's agent server.
The operation {AS.getserver UFN ?AS}
, which when inputted an URL or file name UFN
to any agent server (even on other sites), gives as output a reference to that agent server AS
.
Let's say that we know an URL "http://www.info.ucl.ac.be/~pvr/agents.ozf"
that references the functor AgentServer
. Then the following code creates a local agent server and makes it accessible through the URL "http://www.info.ucl.ac.be/~pvr/as1"
:
declare GetServer in
local
% Get the AgentServer:
AgentServer={Pickle.load "http://www.info.ucl.ac.be/~pvr/agents.ozf"}
% Install AgentServer locally: (this creates an agent server)
[AS1]={Module.apply [AgentServer]}
% Publish the agent server:
{AS1.publishserver "/usr/staff/pvr/public_html/as1"}
in
GetServer=AS1.getserver
end
This code also creates the procedure GetServer
as defined above.
Let's create a second agent server, a remote one, and make it accessible through the URL "http://www.info.ucl.ac.be/~pvr/as2"
:
local
RF=functor import Pickle Module export done:D
define
AgentServer={Pickle.load "http://www.info.ucl.ac.be/~pvr/agents.ozf"}
[AS2]={Module.apply [AgentServer]}
{AS2.publishserver "/usr/staff/pvr/public_html/as2"}
end
RM={New Remote.manager init}
M={RM apply(RF $)}
in
skip
end
In this case, the three lines of code that do the work of installing the agent server and publishing it are put inside the functor RF
, and RF
is installed remotely.
Now let's get access to both of these agent servers. We use the procedure GetServer
that is defined in the functor AgentServer
. This procedure can get access to any agent server, not just the one on its site:
declare
Server1={GetServer "http://www.info.ucl.ac.be/~pvr/as1"}
Server2={GetServer "http://www.info.ucl.ac.be/~pvr/as2"}
Now that the agent servers are installed, we're ready to let the agents work.
A first example creates an agent on the remote site. The agent queries the operating system and returns the value of the OS.time
operation. Let's first execute the agent interactively:
declare D1 in
{Server2 functor import OS define D1={OS.time} end}
{Browse D1}
This first creates variable D1
and then executes an agent on the remote site. The agent needs only one resource, OS
, a module that gives access to some operating system functions. The agent is created asynchronously, which means that D1
will usually still be unbound when the Browse
is called. In most cases this is not a problem. The dataflow semantics of Oz mean that most operations that need D1
will wait before continuing. If the caller wants to be sure that D1
is bound before continuing, it just needs to do a {Wait D1}
.
Now let's slightly modify the first example to get a standalone agent, i.e., the agent is itself a functor that can be compiled and executed standalone or by another application. We assume that the functor AgentServer
is available at a standard place.
functor
import System AgentServer
define
GetServer=AgentServer.getserver
Server2={GetServer "http://www.info.ucl.ac.be/~pvr/as2"}
D1
{Server2 functor import OS define D1={OS.time} end}
{System.show D1}
end
This is almost the same as the previous example; it just extends it with a call to GetServer
to access the agent server. The agent first gets access to the remote agent server Server2
and then starts a calculation there.
If the functor AgentServer
is not available in a standard place, then the standalone agent has to get it explicitly:
functor
import System Pickle Module
define
AgentServer={Pickle.load "http://www.info.ucl.ac.be/~pvr/agents.ozf"}
[AS]={Module.apply [AgentServer]}
GetServer=AS.getserver
Server2={GetServer "http://www.info.ucl.ac.be/~pvr/as2"}
D1
{Server2 functor import OS define D1={OS.time} end}
{System.show D1}
end
We give an example of an agent that goes to a remote site, does a calculation there, and comes back with the result. Surprise, surprise, the calculation takes almost no local CPU time.
We give an example of an agent that visits a set of sites repeatedly. It chooses dynamically the next site to visit. In fact, during its execution it can even get access to new sites that it has never heard of before and visit them.
An agent can delegate work by creating tasks dynamically, and then wait until all tasks are done. This is easy by using logic variables to synchronize on the termination.
AgentServer
The basic tool giving agent functionality is the functor AgentServer
, which is defined as follows. When installed, this functor creates an agent server, a procedure to publish the agent server, and a procedure to get access to any published agent server.
functor
import Module Connection Pickle
export
server:AS
publishserver:PublishServer
getserver:GetServer
define
S P={NewPort S}
proc {InstallFunctors S}
case S
of F|S2 then
try
[_] = {Module.apply [F]}
catch _ then skip end
{InstallFunctors S2}
else skip end
end
thread {InstallFunctors S} end
proc {AS F} {Send P F} end
T={Connection.offerUnlimited AS}
% Make agent server available through file FN:
% Note that the agent server is asynchronous.
proc {PublishServer FN}
{Pickle.save T FN}
end
% Get access to a server that is at file/URL UFN:
proc {GetServer UFN AS}
try
T={Pickle.load UFN}
in
AS={Connection.take T}
catch _ then
raise serverUnavailable end
end
end
end
One way to use AgentServer
is to compile it with the standalone compiler and make the resulting ozf
file globally-accessible by putting it in a public_html
directory. This can also be done in the interactive user interface:
declare
AgentServer=
functor
... (body of functor definition)
end
{Pickle.save AgentServer "/usr/staff/pvr/public_html/agents.ozf"}
<< Prev | - Up - | Next >> |