Browse Source

Add documentation of services

Yentl Van Tendeloo 8 years ago
parent
commit
eb6d8c4036
1 changed files with 158 additions and 7 deletions
  1. 158 7
      doc/services.rst

+ 158 - 7
doc/services.rst

@@ -1,24 +1,175 @@
 External Services
 =================
 
-Explain the use of services.
+Up to now, everything was contained in the Modelverse.
+Nonetheless, this is not always possible, or desired: sometimes we want to run an operation through an external tool, or a service.
+There are two main reasons why we would like this: performance, and feasability.
+
+For performance, an external service is likely to be faster than a Modelverse-based implementation.
+Not only because the action language execution of the Modelverse is comparatively slow, but also because of the optimized implementation of the external tool.
+It is clear that a specialized tool will be much faster than a naive implementation, even if the naive implementation were to be done in an efficient language.
+For example, PythonPDEVS uses many non-trivial algorithms to speed up DEVS simulation, which are not easily mimicked in a new implementation.
+While not impossible, of course, it seems wasteful to spend a significant amount of time to reimplement a tool.
+
+For feasability, an external service can offer operations that the Modelverse would otherwise not be able to execute.
+This links back to performance, but also to the algorithms required for the operation.
+For example, reimplementing a differential equation solver is wasteful, knowing that specialized tools have spent many man-years to implement and optimize it.
+We can never dream to come even close to these implementations.
+
+Both go hand-in-hand: a naive implementation is often possible, but there is always the trade-of between spending more time in developing the algorithm, and more time in using the algorithm.
+The ideal solution would be to use an existing tool, thereby relying on the expertise of other developers, and merely making it available in the Modelverse.
+
+When making this available in the Modelverse, we strife to minimize the difference between an internal and external operation.
+This can be done using services: instead of modelling the operation, we model the communication with a tool that does the operation.
+While there remains a big difference, as we cannot alter the semantics of the external tool, we come fairly close, abstracting away from the tool that is actually used for the operation.
+
+In this section, we will illustrate this operation through the use of a *Fibonacci* service, which takes a single integer, and returns the Fibonacci number corresponding to it.
 
 Modelverse-side
 ---------------
 
-Explain how services work in the Modelverse.
+On the Modelverse, users can use the *comm* operations to communicate with external tools.
+This is highly similar to communication for input and output to the designated user.
+Code on the Modelverse can set up a connection to a registered service as follows::
+
+    Integer function fibonacci(n : Integer):
+        String port
+
+        // Set up a dedicated communication port to the service
+        port = comm_connect("fibonacci")
+
+        // Send some data: the integer we got ourselves
+        comm_send(port, n)
+
+        // The external service computes the result
+
+        // The comm_get function is blocking
+        Integer result
+        result = comm_get(port)!
+
+        // Close the communication port from our side
+        comm_close(port)
+        return result!
+
+In this example, we send an integer parameter to the external service, and expect it to reply with another integer, containing the final result.
+When we got our result, we should always call *comm_close* to close the port at the Modelverse side.
+
+Multiple calls to *comm_send* and *comm_get* are possible, depending on the communication protocol devised between the Modelverse and the external service.
+For example, if we want to wait for the service to accept the result (e.g., the service can give an error on non-integer input)::
+
+    Integer function fibonacci(n : Integer):
+        String port
+        port = comm_connect("fibonacci")
+
+        comm_send(port, n)
+
+        String response
+        response = comm_get(port)
+        if (response == "OK"):
+            Integer result
+            result = comm_get(port)
+            comm_close(port)
+            return result!
+        elif (response == "Integer value expected!"):
+            // error out, as it is not an integer!
+            log("Non-integer input to fibonacci function!")
+            comm_close(port)
+            return -1!
+
+Sometimes, for example when coupled to Statecharts, we want to poll whether or not there is input from the service.
+This can be done using the *comm_poll* function, whose signature is identical to *comm_get*, though it returns a boolean and returns immediately.
+As expected, it does not consume the value itself, which can subsequently be read out using *comm_get*, which will now return immediately if *comm_poll* was True.
 
 Service-side
 ------------
 
-Explain how services work in the Python wrapper.
+The external service itself should be slightly augmented with a minimal wrapper.
+This wrapper is responsible for the communication with the Modelverse, by importing the Modelverse wrapper, deserializing the input from the Modelverse, and serializing the output from the tool.
+For our fibonacci example, where the fibonacci code is written in Python as well, we can code this as follows::
+
+    >>> def fibonacci_service(port):
+    ...     def fibonacci(n):
+    ...         if n <= 2:
+    ...             return 1
+    ...         else:
+    ...             return fibonacci(n - 1) + fibonacci(n - 2)
+    ...     mv_input = service_get(port)
+    ...     result = fibonacci(mv_input)
+    ...     service_set(port, result)
+    >>> service_register("fibonacci", fibonacci_service)
+    >>> try:
+    ...     while raw_input() != "STOP":
+    ...         pass
+    ... finally:
+    ...     service_stop()
 
-Example
--------
+In this simple piece of code, we define the actual fibonacci code in the function *fibonacci*, which is totally unrelated to the Modelverse and is just pure Python code.
+To wrap it, we define *fibonacci_service*, which is a function taking the communication port.
+This function is responsible for communication with the Modelverse, through the operations *service_get* and *service_set*.
+Both functions have the expected signature.
+The service function is invoked on a thread, and therefore runs concurrently with other invocations of this same service.
+Upon termination of the function, the thread ceases to exist.
+All threads are daemon threads, such that, if the service wants to exit, it can immediately exit without first finishing up the processing of tasks in the Modelverse.
+The Modelverse tasks should be resilient to service failure or termination anyway, possibly through the use of a statechart that contains timeouts.
 
-Give a full example of a service.
+When the service is registered, we have to specify the name of the service to be used in the Modelverse on a *comm_connect* call.
+The second parameter is the function to invoke when a connection is made, and must take one parameter: the communication port to be used.
+After the service is registered, the main thread simply continues.
+This makes it possible for the administrators of the service to maintain full control over their service.
+If the main thread terminates, all currently running services are terminated.
+Note that, after registration, this program is not able to do any other operations, until the *service_stop* operation is sent.
+
+When doing type checking first, we can do multiple *service_set* operations::
+
+    >>> def fibonacci_service(port):
+    ...     def fibonacci(n):
+    ...         if n <= 2:
+    ...             return 1
+    ...         else:
+    ...             return fibonacci(n - 1) + fibonacci(n - 2)
+    ...     mv_input = service_get(port)
+    ...     if isinstance(mv_input, int):
+    ...         service_set(port, "OK")
+    ...         result = fibonacci(mv_input)
+    ...         service_set(port, result)
+    ...     else:
+    ...         service_set(port, "Integer value expected!")
+    >>> service_register("fibonacci", fibonacci_service)
+    >>> try:
+    ...     while raw_input() != "STOP":
+    ...         pass
+    ... finally:
+    ...     service_stop()
+
+The invoked function is in this case other Python code, though this does not need to be that way.
+It would also be possible to invoke another executable, such as a *fibonacci* program, as long as the program can be used in an automated way.
+Therefore, the external services are not limited to Python in any way, and we merely use Python as a convencience, since we already have a wrapper for it.
+Using an external program happens through the *subprocess* Python module::
+
+    >>> def fibonacci_service(port):
+    ...     def fibonacci(n):
+    ...         import subprocess
+    ...         result = subprocess.check_output(["./fibonacci", str(n)])
+    ...         return int(result)
+    ...     mv_input = service_get(port)
+    ...     result = fibonacci(mv_input)
+    ...     service_set(port, result)
+    >>> service_register("fibonacci", fibonacci_service)
+    >>> try:
+    ...     while raw_input() != "STOP":
+    ...         pass
+    ... finally:
+    ...     service_stop()
 
 HUTN compiler
 -------------
 
-Explain how the HUTN compiler (for the compile_code and compile_model operations) works using services
+While it might not be obvious, some of the internal Modelverse operations are implemented using external services already.
+This is the case for the *compile_code* and *compile_model* functions.
+These functions rely on a HUTN compiler running as a service: the string is sent there, and the service sends back the list of operations to execute on the Modelverse (i.e., the compiled data).
+This was done to prevent a complete implementation of a parser in the Modelverse.
+While this would certainly be possible, at the moment it is not of high priority, and it is easy to shift it externally.
+Also, by using external services, it becomes possible to rely on trusted parsers, such as ANTLR or Ply, instead of building one from scratch as well.
+
+Users of the *compile_code* and *compile_model* functions are never exposed to the use of an external service: it looks exactly like any other function call.
+Nonetheless, there is a small difference: when the external service encounters a problem, or is simply not running, the functions will not be able to operate, even though the input is correct.