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.