Processes

A process can be considered as the executable artifact which animates a session. In this section, we will discuss those statements that rule the execution of processes within the engine. In particular, we will show how to address concurrent execution of processes and synchronization.

Execution modality

The execution modality permits to specify the way a process must be executed within the engine. An process can be executed in three modalities:

  • single
  • sequential
  • concurrent

The syntax of the execution modality is:

execution: single | concurrent | sequential

single is the default execution modality (so the execution construct can be omitted), which runs the program behaviour once. sequential, instead, causes the program behaviour to be made available again after the current instance has terminated. This is useful, for instance, for modelling services that need to guarantee exclusive access to a resource. Finally, concurrent causes a program behaviour to be instantiated and executed whenever its first input statement can receive a message.

In the `sequential` and `concurrent` cases, the behavioural definition inside the main procedure must be an input statement, thus the executed process always animates a session. Single modality is usually exploited for running scripts because they require to be triggered by command instead of a message reception.

A crucial aspect of processes is that each of them has its own private state, determining variable scoping. This lifts programmers from worrying about race conditions in most cases.

For instance, let us recall the server program given at the end of Communication Ports section. The execution modality of the NewsPaper is concurrent thus it can support multiple requests from both the script author.ol and user.ol.

from NewsPaperInterface import NewsPaperInterface


service NewsPaper {
    execution: concurrent

    inputPort NewsPaperPort {
        location:"auto:ini:/Locations/NewsPaperPort:file:locations.ini"
        protocol: sodep
        interfaces: NewsPaperInterface
    }

    main {
            [ getNews( request )( response ) {
                    response.news -> global.news
            }]

            [ sendNews( request ) ] { global.news[ #global.news ] << request }
    }
}

main{} and init{}

main and init define the behaviour scope and the initializating one respectively. All the operations of the service must be implemented within the scope main, whereas the scope init is devoted to execute special procedures for initialising a service before it makes its behaviours available. All the code specified within the init{} scope is executed only once, when the service is started. The scope init is not affected by the execution modality. On the contrary, the code defined in the scope main is executed following the execution modality of the service.

As an example let us consider the newspaper service reported above enriched with a simple init scope where a message is printed out on the console:

from NewsPaperInterface import NewsPaperInterface
from console import Console

service NewsPaper {
    execution: concurrent

    embed Console as Console

    inputPort NewsPaperPort {
        location:"auto:ini:/Locations/NewsPaperPort:file:locations.ini"
        protocol: sodep
        interfaces: NewsPaperInterface
    }

    init {
        println@Console("The service is running...")()
    }

    main {
        [ getNews( request )( response ) {
            response.news -> global.news
        }]

        [ sendNews( request ) ] { global.news[ #global.news ] << request }
    }
}

When run the service will print out the following message in the console:

The service is running...

Global variables

Jolie also provides global variables to support sharing of data among different processes. These can be accessed using the global prefix:

global.myGlobalVariable = 3 // Global variable
myLocalVariable = 1 // Local to this behaviour instance

In the example reporters at this link it is shown the difference between a global variable and a local variable. The server is defined as it follows:

from ServiceInterface import ServiceInterface
from console import Console

service MyService {

    embed Console as Console
    execution: concurrent

    inputPort Test {
        location: "socket://localhost:9000"
        protocol: sodep
        interfaces: ServiceInterface
    }

    main {
        test( request)( response ) {
            global.count++
            count++
            println@Console("global.count:" + global.count )()
            println@Console("count:" + count )()
            println@Console()()
        }
    }
}

In the body of the request-response test the global variable global.count and the local variable count are incremented by one for each received message, then their value are printed out on the console. If we call such service ten times the output is:

global.count:1
count:1

global.count:2
count:1

global.count:3
count:1

global.count:4
count:1

global.count:5
count:1

global.count:6
count:1

global.count:7
count:1

global.count:8
count:1

global.count:9
count:1

global.count:10
count:1

It is worth noting that the global variable keeps its value independently from the executiing instance, thus it can be incremented each time a new session is executed. On the other hand the local variable is fresh each time.

Synchronisation

Concurrent access to global variables can be restricted through synchronized blocks, similarly to Java:

synchronized( id ){
        //code
}

The synchronisation block allows only one process at a time to enter any synchronized block sharing the same id.

As an example, let us consider the service reported at this link

The register service has a concurrent execution and exposes the register request-response operation. register increments a global variable, which counts the number of registered users, and sends back a response to the client. A sleep call to time service, simulates the server side computation time.

RegisterInterface.ol/

type response {
    message: string
}

interface RegisterInterface {
    RequestResponse: register( void )( response )
}

register.ol/

from RegisterInterface import RegisterInterface
from time import Time


service Register {

    execution: concurrent

    embed Time as Time

    inputPort Register {
        location: "socket://localhost:2000"
        protocol: sodep
        interfaces: RegisterInterface
    }

    init
    {
        global.registered_users=0;
        response.message = "Successful registration.\nYou are the user number "
    }

    main
    {
        register()( response ){
            /* the synchronized section allows to access syncToken scope in mutual exclusion */
            synchronized( syncToken ) {
                response.message = response.message + ++global.registered_users;
                sleep@Time( 2000 )()
            }
        }
    }
}

The client executes five parallel calls to the service in order to register five different users.

main
{
    {
        register@Register()( response1 );
        println@Console( response1.message )()
    }
    |
    {
        register@Register()( response2 );
        println@Console( response2.message )()
    }
    |
    {
        register@Register()( response3 );
        println@Console( response3.message )()
    }
    |
    {
        register@Register()( response4 );
        println@Console( response4.message )()
    }
    |
    {
        register@Register()( response5 );
        println@Console( response5.message )()
    }
}

If executed, it is possible to observe that the parallel calls of the client are serialized by the service thanks to the primitive synchronized which implements a mutual access of the scope syncToken.