Figure 3: The Forms Interface using Active Modules
Despite its power, the cgi-bin interface also has some shortcomings. The most serious is perhaps the fact that the handler is started and expected to terminate for each interaction. This has two disadvantages. First, no state is preserved from one query to the next. However, as mentioned before, this can be fixed by passing the state through the form or also by saving it in a temporary file at the server side. Second, and more importantly, starting and stopping the application may be inefficient. For example, if the idea is to query a large database or a natural language understanding system, it may take a long time to start and stop the system. In order to avoid this we propose an alternative architecture for cgi-bin applications (a similar idea, although not based on the idea of active modules, has been proposed independently by Ken Bowen [3]).
The basic idea is illustrated in Figure 3. The operation is identical to that of standard form handlers, as illustrated in Figure 2, up to step 3. In this step, the handler started is not the application itself, but rather an interface to the actual application, which is running continuously and thus contains state. Thus, only the interface is started and stopped with every transaction. The interface simply passes the form input received from the server (4) to the running application (5) and then forwards the output from the application (6) to the server before terminating, while the application itself continues running. Both the interface and the application can be written in LP/CLP, using the predicates presented. The interface can be a simple script, while the application itself will be typically compiled.
An interesting issue is that of communication between interface and
application. This can of course be done through sockets. However, as a
cleaner and much simpler alternative, the concept of active modules
[4] can be used to advantage in this
application. An active module (or an active object, if modularity is
implemented via objects) is an ordinary module to which computational
resources are attached (for example, a process on a UNIX machine), and
which resides at a given (socket) address on the network. Compiling an
active module produces an executable which, when running, acts as a
server for a number of relations, which are the predicates exported by
the module. The relations exported by the active module can be
accessed by any program on the network by simply ``loading'' the
module and thus importing such ``remote relations.'' The idea is that
the process of loading an active module does not involve transferring
any code, but rather setting up things so that calls in the local
module are executed as remote procedure calls to the active module,
possible over the network. Except for saving it in a special way, an
active module is identical from the programmer point of view to an
ordinary module. Also, a program using an active module imports it and
uses it in the same way as any other module, except that it uses
``use_active_module'' rather than ``use_module'' (see
below). Also, an active module has an address (network address) which
must be known in order to use it. The address can be announced by the
active module when it is started via a file or a name server (which
would be itself another active module with a fixed address).
We now present the constructs used by active modules. Note that for concreteness and compatibility in the description of modules we mainly follow the same scheme as SICStus Prolog.
Note that this scheme is very flexible. For example, the predicate module_address/2 itself could be imported, thus allowing a configurable standard way of locating active modules. One could, for example, use a directory accessible by all the involved machines to store the addresses of the active modules in them, and this predicate would examine this directory to find the required data. A more elegant solution would be to implement a name server, that is, an active module with a known address that records the addresses of active modules and supplies this data to the modules that actively import it.
From the implementation point of view, active modules are essentially daemons: Prolog executables which are started as independent processes at the operating system level. In the CIAO system library, communication with active modules is implemented using sockets (thus, the address of an active module is a UNIX socket in a machine). Requests to execute goals in the module are sent through the socket by remote programs. When such a request arrives, the process running the active module takes it and executes it, returning through the socket the computed results. These results are then taken by the remote processes.
Thus, when the compiler finds a use_active_module declaration, it defines the imported predicates as remote calls to the active module. For example, if the predicate P is imported from the active module M, the predicate would be defined as
P :- module_address(M,A), remote_call(A,P)
The predicate save_active_module/3 saves the current code like save/1, but when the execution is started a socket is created whose address is the second argument of the predicate, and the expression in the third argument is executed. Then, the execution goes into a loop of reading execution requests from the socket, executing them, and returning the solutions back through the socket.
Compiling the following program creates an executable phone_db which, when started as a process (for example, by typing ``phone_db &'' at a UNIX shell prompt) saves its address (i.e., that of its socket) in file phone_db.addr and waits for queries from any module which ``imports'' this module (it also provides a predicate to dynamically add information to the database):
:- module(phone_db,[response/2,add_phone/2]). response(Name, Response) :- form_empty_value(Name) -> Response = 'You have to provide a name.' ; phone(Name, Phone) -> Response = ['Telephone number of ',b(Name),': ',Phone] ; Response = ['No telephone number available for ',b(Name),'.']. add_phone(Name, Phone) :- assert(phone(Name, Phone)). :- dynamic phone/2. phone(daniel, '336-7448'). phone(manuel, '336-7435'). phone(sacha, '543-5316'). :- save_active_module(phone_db,Address, (tell('phone_db.addr'), write(Address),told)).
The following simple script can be used as a cgi-bin executable which will be the active module interface for the previous active module. When started, it will process the form input, issue a call to response/2 (which will be automatically handled by the phone_db active module), and produce a new form before terminating. It will locate the address of the phone_db active module via the module_address/2 predicate it defines.
#!/usr/local/bin/lpshell :- use_active_module(phone_db,[response/2]). :- use_module('/usr/local/src/pillow/pillow.pl'). main(_) :- get_form_input(Input), get_form_value(Input,person_name,Name), response(Name,Response), output_html([ cgi_reply, start, title('Telephone database'), image('phone.gif'), heading(2,'Telephone database'), --, Response, $, start_form, 'Click here, enter name of clip member, and press Return:', \\, input(text,[name=person_name,size=20]), end_form, end]). module_address(phone_db,Address) :- see('phone_db.addr'), read(Address), seen.
There are many enhancements to this simple schema which, for brevity,
are only sketched here. One is to add concurrency to the active module
(or whatever means of handling the client-server interaction is being
used), in order to handle queries from different clients concurrently.
This is easy to do in systems that support concurrency natively, such
as &-Prolog/CIAO, BinProlog/-Prolog, AKL, Oz, and KL1. We
feel that &-Prolog/CIAO can offer advantages in this area because it
offers compatibility with Prolog and CLP systems while at the same
time efficiently supporting concurrent execution of clause goals via
local or distributed threads. Such goals can communicate at different
levels of abstraction: sockets/ports, blackboard, or shared variables.
BinProlog/
-Prolog also supports threads, although the
communication mechanisms are somewhat different. Finally, as shown in
[28], it is also possible to exploit the concurrency
present in or-parallel Prolog systems such as Aurora for implementing
a multiprocessing server.
It is also interesting to set up things so that a single active module can handle different forms. This can be done even dynamically (i.e., the capabilities of the active module are augmented on the fly, being able to handle a new form), by designating a directory in which code to be loaded by the active module would be put, the active module consulting the directory periodically to increase its functionalities. Finally, another important issue that has not been addressed is that of providing security, i.e., ensuring that only allowed clients connect to the active module. As in the case of remote code downloading, standard forms of authentication based on codes can be used.
An implementation of active modules as described is included in the CIAO library which provides the concurrent and distributed execution facilities mentioned above [4]. As the PiLLoW library, this library only uses standard features of LP/CLP systems, although it does require attributed variables [19] if shared-variable communication is to be used [16]. However, this is not necessary for implementing active modules.