Skip to content

Commit

Permalink
[docs] Add tutorials on parallel execution and mas initialization.
Browse files Browse the repository at this point in the history
close #817

Signed-off-by: Stéphane Galland <galland@arakhne.org>
  • Loading branch information
gallandarakhneorg committed Jul 26, 2018
1 parent cd02ed3 commit a5cc3da
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 5 deletions.
5 changes: 5 additions & 0 deletions docs/io.sarl.docs.markdown/src/main/documentation/index.md
Expand Up @@ -24,6 +24,11 @@
* [Agent Communication with the Ping Pong Agents](./tutorials/PingPong.md)
* [Agent Communication in Sub-Space with the Ping Pong Agents](./tutorials/PingPongSpace.md)

### Parallel Execution

* [Parallel execution within the agents](./tutorials/ParallelExecution.md)
* [Initialization of a multiagent system](./tutorials/MASInitialization.md)

### Organizational Patterns

* [English Auction with Holons](./tutorials/HolonicAuction.md)
Expand Down
@@ -0,0 +1,152 @@
# Initialization of a Multiagent System

[:Outline:]

This document describes the key elements and the best practices for initializing and starting up a multiagent system.
Before reading this document, it is recommended reading
the [General Syntax Reference](../reference/GeneralSyntax.md).

Initialization of a multiagent system is a hard problem, and specifically with SARL in which the
[agent spawning is run in parallel](./ParallelExecution.md).

The problem is: how ensuring that all the agents on the system are alive before starting to run their
standard behaviors?

## Example of a problematic system

First, let consider a SARL code that is not working well regarding the initialization of the system.
In the following code, the type of agent [:myagentname:] is defined.
This type of agent emits an [:myeventname:] event when the application starts, i.e. when the agent is initialized.
It also logs the message [:msg1:] when the agent is initialized.

[:Success:]
package io.sarl.docs.tutorials.masinitialization
import io.sarl.core.DefaultContextInteractions
import io.sarl.core.Logging
import io.sarl.core.Initialize
[:On]
event [:myeventname](MyEvent)

agent [:myagentname](MyAgent) {
uses DefaultContextInteractions, Logging
[:initializeblock](on Initialize) {
[:emit](emit)(new MyEvent)
}

[:myeventblock](on MyEvent) {
info([:msg1]("Event received"))
}
}
[:End:]

For starting the system, we could define a booting agent that starts 100 agents of the previously defined type:

[:Success:]
package io.sarl.docs.tutorials.masinitialization
import io.sarl.core.Initialize
import io.sarl.core.Lifecycle
event MyEvent
agent MyAgent {
}
[:On]agent BootAgent {
uses Lifecycle
on Initialize {
for (i : 1..100) {
[:spawnfct](spawn)(typeof(MyAgent))
}
killMe
}
}
[:End:]

There is no warranty that the following sentences are true:
* The order of the agent initialization is the same as the order of the [:spawnfct:] calls;
* When the spawning loop is finished, all agents are spawned.
* The number of logged messages is always equal to 100 * 100 = 10,000, i.o.w. 100 spawned agents that are receiving the events from itself and the other agents.

But, according to the operational semantic of the spawning loop, the number of received messages
is defined by the mathematical suite f(n) = f(n-1) + n = (n(n+1))/2, illustrated in the table below.
For 100 agents, the number of logged messages should be 5,050, not 10,000.


| Number of spawned agents | 1 | 2 | 3 | 4 | 5 |
|-----------------------------|---|---|---|----|----|
| Number of received messages | 1 | 3 | 6 | 10 | 15 |


Moreover, According to the expected [agent spawning's parallel execution](./ParallelExecution.md), the calls to the
[:spawnfct:] function form a sequence of 100 calls; And, each call to the [:spawnfct:] function starts a spawning task that is run within a separated thread.
Consequently, the general behavior of the system is not deterministic.
We cannot infer the number of messages that will be logged because some event may be fired by agents when several
other agents are still waiting for their spawns. The only one fact is that the number of logged messages is lower than or equal to f(100).

## Solution: waiting for the agent spawning

A possible solution to the previously mentionned problem is to split the starting up of the application into two steps:
1. Spawning of the agents: agents are spawn, and the system waits until all the agents are alive and ready.
2. Application start: a specific event is given to all the agents for notifying them that they could start their "standard" behaviors.

Consequently, the agent's code may be redefined as follow:

[:Success:]
package io.sarl.docs.tutorials.masinitialization
import io.sarl.core.DefaultContextInteractions
import io.sarl.core.Logging
event MyEvent
[:On]
event [:startappevent](StartApplication)

agent MyAgent {
uses DefaultContextInteractions, Logging
[:initializeblock](on StartApplication) {
[:emit](emit)(new MyEvent)
}

[:myeventblock](on MyEvent) {
info([:msg2]("Event received"))
}
}
[:End:]
The agent emits the [:myeventname:] event only when the application has started.
This application-start event is represented by the [:startappevent:] event.

The booting agent becomes:

[:Success:]
package io.sarl.docs.tutorials.masinitialization
import io.sarl.core.Initialize
import io.sarl.core.Lifecycle
import io.sarl.core.AgentSpawned
import io.sarl.core.DefaultContextInteractions
event StartApplication
agent MyAgent {
}
[:On]agent BootAgent {
uses Lifecycle, DefaultContextInteractions
var count = 0
on Initialize {
for (i : 1..100) {
spawn(typeof(MyAgent))
}
}
on [:agentspawnedevent](AgentSpawned) [!it.agentIdentifiers.contains(ID)] {
count++
if (count == 100) {
emit(new StartApplication) [it.UUID != ID]
killMe
}
}
}
[:End:]

The two major steps of the multiagent system initialization are implemented.
First, when the boot agent starts its life, it is spawning all the agents.
Each time an agent is spawned, the booting agent is notified with an [:agentspawnedevent:] event.
When the number of spawned agents reaches 100, the booting agent notifies about the application start
and commits a suicide.

[:Include:](../legal.inc)
@@ -0,0 +1,169 @@
# Parallel execution within the agents

[:Outline:]

This document describes the key features of SARL that are run in parallel on the SARL Runtime Environment (SRE).
Before reading this document, it is recommended reading
the [General Syntax Reference](../reference/GeneralSyntax.md).

Each SRE provides a support for running the agents. Depending on the specifications of the SRE, the
parallel execution of the agent's components may be used at different places.
Nevertheless, according the SARL language's specifications, several features are assumed to be run in parallel.
They are briefly explained below.

## Event Firing, Dispatching and Handling

In SARL, event-based communication is the interaction mechanism that is provided by default.
Firing an event is done within an interaction space by calling one of the dedicated function that are defined within
the [`[:defaultcontextinteractions](DefaultContextInteractions)`](../reference/bic/DefaultContextInteractions.md),
[`[:defaultcontextinteractions](ExternalContextAccess)`](../reference/bic/ExternalContextAccess.md) and
[`[:defaultcontextinteractions](InnerContextAccess)`](../reference/bic/InnerContextAccess.md) capacities.

The event firing mechanism could be divided into three steps:
1. Event firing: the event is fired by a source object;
2. Event dispatching: the event is routed to the agents that should receive the event; and
3. Event handling: the event is provided to each agent, and the defined event handlers (named behavior units) are run.

Each of these steps are basically run in different threads.
Let the following code:

[:Success:]
package io.sarl.docs.tutorials.parallelexecution
import io.sarl.core.DefaultContextInteractions
import io.sarl.core.Logging
import io.sarl.core.Initialize
agent PongAgent { }
agent PingAgent { }
event MyEvent
[:On]agent MyAgent {
uses DefaultContextInteractions, Logging

[:initializeblock](on Initialize) {
[:emit](emit)(new MyEvent)
info([:msg1]("Event sent"))
}

[:myeventblock](on MyEvent) {
info([:msg2]("Event received"))
}
}
[:End:]

The call to [:emit:] is run within the thread of the calling block, i.e. [:initializeblock:].
The event is provides to the SRE, that is routing this event within a dedicated "hidden" thread.
Consequently, the call to [:emit:] returns quickly. And, there is no warranty that the event's routing
is started nor terminated when the function returns.

In order to allow the parallel treatment of the events by an agent, each event handler, e.g. [:myeventblock:]
is run in a dedicated thread.

<caution>In the previous example, there is no warranty about the order of printing of the two messages. Because of the parallel execution
of the threads, the [:msg2:] message may be displayed before the [:msg1:] message.</caution>

## Agent Spawning

Agent spawning is the action to create and start an agent from another agent.
The spawning function is provided by the
[`[:Lifecycle](Lifecycle)`](../reference/bic/Lifecycle.md) capacity.

The agent spawning process is divided into several steps:
1. Call of the spawning function;
2. Creation of the agent within the computer memory;
3. SRE-specific initialization of the agent capacities and internal fields;
4. Synchronous execution of the [:initializeblock] of the agent;
5. Firing of the [:agentspawnedevent](AgentSpawned) event.

Step 1 is run within the thread of the caller.
Steps 2 to 5 are run within an internal thread of the SRE.

Let the following code:

[:Success:]
package io.sarl.docs.tutorials.parallelexecution
import io.sarl.core.Lifecycle
import io.sarl.core.Logging
import io.sarl.core.AgentSpawned
import io.sarl.core.Initialize
agent PongAgent { }
agent PingAgent { }
event MyEvent
[:On]agent MyAgent {
uses Lifecycle, Logging

[:initializeblock](on Initialize) {
spawn(typeof(MyAgent2))
info([:msg1]("Spawn query called"))
}

[:agentspawnedblock](on AgentSpawned) {
info([:msg2]("Agent was spawned"))
}
}
agent MyAgent2 {
uses Logging

[:initializeblock!] {
info([:msg3]("Do initialization"))
}
}
[:End:]

The [:msg3:] message is always logged before the [:msg2:] message because the executed code corresponds to
steps 5 and 4, respectively. These steps are run on the same thread.

But, there is no warranty about when the [:msg3:] message is logged. According to the parallel execution,
it may be logged at any time. Consequently, the possible output cases are:
* [:msg1:], [:msg3:], [:msg2:]
* [:msg3:], [:msg1:], [:msg2:]
* [:msg3:], [:msg2:], [:msg1:]


## Agent Killing

Agent killing is the action to stop and destroy an agent.
The killing function is provided by the
[`[:Lifecycle](Lifecycle)`](../reference/bic/Lifecycle.md) capacity.

The agent killing process is divided into several steps:
1. Call of the killing function;
2. Synchronous execution of the [:destroyblock] of the agent;
3. SRE-specific destruction of the agent;
4. Firing of the [:agentdestroyevent](AgentKilled) event.

Step 1 is run within the thread of the caller.
Steps 2 to 4 are run within an internal thread of the SRE.

Let the following code:

[:Success:]
package io.sarl.docs.tutorials.parallelexecution
import io.sarl.core.Lifecycle
import io.sarl.core.Logging
import io.sarl.core.AgentKilled
import io.sarl.core.Initialize
import io.sarl.core.Destroy
agent PongAgent { }
agent PingAgent { }
event MyEvent
[:On]agent MyAgent {
uses Lifecycle, Logging

[:initializeblock](on Initialize) {
killMe
}

[:destroyblock](on Destroy) {
info([:msg1a]("Do destruction"))
}

[:agentspawnedblock](on AgentKilled) {
info([:msg2b]("Agent was killed"))
}
}
[:End:]

The [:msg2b:] message is always logged after the [:msg1a:] message because the executed code corresponds to
steps 4 and 3, respectively. These steps are run on the same thread.


[:Include:](../legal.inc)
Expand Up @@ -49,15 +49,15 @@ public class PureOperationNameValidator implements IPureOperationNameValidator {
*/
public static final String[] SPECIAL_PURE_FUNCTION_NAME_PATTERNS = {
"clone", //$NON-NLS-1$
"contains(?:[A-Z1-9].*)?", //$NON-NLS-1$
"contains(?:[A-Z1-9_][a-zA-Z1-9_]*)?", //$NON-NLS-1$
"equals", //$NON-NLS-1$
"get(?:[A-Z1-9].*)?", //$NON-NLS-1$
"has[A-Z1-9].*", //$NON-NLS-1$
"get(?:[A-Z1-9_][a-zA-Z1-9_]*)?", //$NON-NLS-1$
"has[A-Z1-9_][a-zA-Z1-9_]*", //$NON-NLS-1$
"hashCode", //$NON-NLS-1$
"is[A-Z].*", //$NON-NLS-1$
"is[A-Z1-9_][a-zA-Z1-9_]*", //$NON-NLS-1$
"iterator", //$NON-NLS-1$
"length", //$NON-NLS-1$
"to[A-Z1-9].*", //$NON-NLS-1$
"to[A-Z1-9_][a-zA-Z1-9_]*", //$NON-NLS-1$
"size", //$NON-NLS-1$
};

Expand Down

0 comments on commit a5cc3da

Please sign in to comment.