1. Marco de referencia
2.4 Análisis y aspectos nacionales de la situación de dengue
The motivation to adapt or change the runtime environment of different types of application in different domains has been established in Section 2.2. Consequently, this section will describe how Ensemble supports adaptation or reconfiguration at runtime from within the language.
Spawn
Spawning an actor is the process of creating a new actor at an explicitly specified stage; this stage can be the current stage, or a different stage. Spawning a new actor is similar to creating an actor with the new keyword, with the addition of a reference to the stage at which it should be created. As with new, the constructor of the desired actor should be supplied; this includes constructors which require any values or variables. Listing 3.19 shows different variations of actors being spawned.
1 behaviour{
2 stages = findStages(example_query); 3 if(stages.length > 0) then{
4 // create a baby actor without a reference
5 spawn baby() at stages[0];
6
7 spawn baby(1, "hello", example_proc()) at stages[0];
8
9 // create a baby actor with a reference
10 reference = spawn baby() at stages[0]; 11 }
12 }
Listing 3.19: Spawn in Ensemble
1 behaviour{
2 stages = findStages(example_query); 3 if(stages.length > 0) then{
4 // migrate the actor to the stage referenced by stages[0]
5 migrate stages[0];
6 }
7 }
Listing 3.20: Migrate in Ensemble
Actors may be spawned either with or without a reference to the new actor, Listing 3.19 lines 10 and 5, respectively. Actors which are spawned with a reference are semantically equivalent to an actor created with the new keyword, the only difference is that actors created with new can only be local, whereas actors created with spawn can be either local or remote. It is also important to note that actors which represent OpenCL kernels (Section 3.3) can also be spawned.
Should a stage be unreachable while trying to spawn, the StageNotFoundException will be thrown. If the actor cannot be created at the stage, an appropriate exception will be thrown in the actor invoking the spawn to reflect the problem. The most common exception is the ConnectionFailureException exception. If an exception is generated, the actor will not be spawned.
Migration
Unlike other actor-based languages, Ensemble natively supports actor migration without the need for extra-language constructs. Migration is an explicit action in the language which pauses the execution of the invoking actor, moves the actor, its channels, and its state to
1 behaviour{
2 // replace the actor referenced by baby
3 // with a new child actor
4 replace baby with child();
5 }
Listing 3.21: Replacement in Ensemble
a different stage, and continues the execution of the actor immediately after the migration operation, Listing 3.20.
Migration is an actor specific operation, and can only be invoked by the calling actor; one actor cannot explicitly tell another actor to migrate. This follows the spirit of actor encap- sulation and again serves to simplify the programming model. This also removes the need to have language primitives which will fix or unfix an actor to a particular stage, as in Emerald [109], which was required to prevent actors migrating each other. Such functional- ity could be provided at the application level if required.
Actors who have references to files or other resources which are located on a particular physical machine are not allowed to be migrated. This is enforced at compiletime as the compiler is aware of the relevant types. This limitation can be overcome by using actors and channels to abstract such location-dependent types. An example of this is seen in the media player application, Section 5.3.1. Here, file access is abstracted by an actor which accepts file names and returns byte streams. This approach could be extended by having all I/O interaction being abstracted by system actors. This would have the added advantage that such actors can be discovered at runtime. Currently, there are a number of system actors which abstract hardware access, however direct access is left to provide the developer with choice.
As well as all of an actor’s code and state, any actor connected by channels before it per- forms a migration operation, will still be connected after a migration from either actor’s perspective. Should an error occur during migration, the actor will not be moved to the new stage, instead continuing on the original stage, and an appropriate exception will be gener- ated. This is a similar situation to spawning a new actor. Note that it is legal to migrate an actor to the stage it is currently on, however it is not legal to migrate an actor which represents an OpenCL kernel, this is discussed in Section 4.5.3.
Replacement
As well as the ability to move actors between stages, it was also desired that actors be re- placeableat runtime. This is useful in situations where an actor is not operating as desired due to incorrect or outdated software. This situation is particularly observed in WSNs, where
hardware is often difficult to access after deployment [110]. In such situations, it is prefer- able to update remotely via software, rather than retrieve the device, reprogram, and then re-deploy; this is generally known as over-the-air programming. Although not yet fully im- plemented, the following is a description of the semantics of replacement.
The replacement statement requires a reference to the actor to be replaced, and the construc- tor of the new actor, as shown in Listing 3.21. As with spawn, constructors with arguments may be used. The new actor will inherit all the channels and connections of the actor it replaces. Consequently, the new actor must present at least the same number and type of channels as the actor being replaced. This assertion can be partially guaranteed by the compiler at compiletime, as the channels of the new and old actor can be compared; if they do not match an error is generated. However, actors which are located via the dis- covery mechanism may present more channels than are specified in the interface which is used to locate them (Section 3.4.3). In this case, it is not possible to ensure correctness at compiletime. Hence, it is the responsibility of the runtime to ensure correctness. Should the new actor not be compatible, the ActorNotCompatibleException will be gen- erated. As the language is designed to be used by non-experts or non-computing scientists, the use of reflection or runtime analysis to either indicate why the actors are incompatible or fix the issue was undesirable. Instead, a new way of discovering actors was introduced: findReplaceable(). This function is identical to findActors() except that only actors where all of the specified and examined channels match are considered as being eligi- ble to be returned. In this way, findReplaceable() provides a mechanism to avoid the ActorNotCompatibleException.
While ensuring that the same channels are present in both the new and old actors provides type-safety and functional correctness, it does not guarantee that correct and safe interaction with other actors. This is especially true as the new actor does not need to have the same state, procedure or query definitions. To minimise this, an actor is conceptually replaced at the beginning of an iteration of its behaviour clause. This means that an actor must wait until all internal operations and external interactions with other actors have completed before being replaced. This provides a clear point at which to reason about the logic of replacement. Languages like Erlang and Scala support the idea of runtime code replacement, or “hot- swapping” of code, however this refers to logic, rather than the combination of logic and state.
Currently, the replace statement and the findReplaceable() operation are sup- ported, hence there are no modifications required to the language to enable replacement. Also, the VM supports the discovery of replaceable actors, however, the final implementa- tion of replacing an actor is not yet complete.