Session Types with Runtime Adaptation: Overview and Examples

Session Types with Runtime Adaptation: Overview and Examples


In recent work, we have developed a session types discipline for a calculus that features the usual constructs for session establishment and communication, but also two novel constructs that enable communicating processes to be stopped, duplicated, or discarded at runtime. The aim is to understand whether known techniques for the static analysis of structured communications scale up to the challenging context of context-aware, adaptable distributed systems, in which disciplined interaction and runtime adaptation are intertwined concerns. In this short note, we summarize the main features of our session-typed framework with runtime adaptation, and recall its basic correctness properties. We illustrate our framework by means of examples. In particular, we present a session representation of supervision trees, a mechanism for enforcing fault-tolerant applications in the Erlang language.


The type-theoretic foundations provided by session types have proved successful in the analysis of complex communication scenarios. This is witnessed by, e.g., the several extensions and enhancements of early session types proposals [11] with common concepts/idioms in practical distributed programming. Interestingly, while often such extensions have appealed to increasingly sophisticated type structures (which feature, e.g., subtyping [10], dependent/indexed types [5], and kinding [4]), the associated modeling language —the polyadic -calculus— has remained essentially the same. This “asymmetric” development of session types and process languages should not appear as a surprise: because of its canonicity and expressiveness, the -calculus represents a rather natural choice for representing concurrent systems which interact by following precise —yet intricate— behavioral patterns.

We are interested in one such patterns, namely runtime adaptation: it allows to represent the suspension, restarting, replacement, or abortion of running processes. As such, it can be useful to model and reason about important mechanisms in modern distributed systems, such as code mobility, online software update, failure recovery (as in, e.g., constructs for exceptions/compensations), and scaling (i.e., the ability of acquiring/releasing computing resources based on runtime demand). These mechanisms typically have a global effect over the system and are meant to be executed atomically. Hence, they appear difficult to implement as -calculus specifications: it is not obvious how name passing/scope extrusion —the central abstraction vehicles of the -calculus— can be used to model this kind of global reconfiguration primitives while retaining atomicity and an adequate level of abstraction for reasoning.

Based on these limitations, and with the aim of setting a formal framework for reasoning about communicating systems with dynamic reconfiguration, with Bravetti and Zavattaro we have developed a framework of adaptable processes [2]. Adaptable processes extend usual process calculi with located processes and update processes , where is a process and is a context, i.e, a process with zero or more occurrences of a process variable . While located processes explicitly represent distributed behavior, update processes provide a built-in adaptation mechanism for located processes. In fact, by synchronizing on name , processes and may reduce to —the process which results from substituting all free occurrences of in with . This way, we obtain a mechanism for dynamic process reconfiguration which is performed atomically in a single reduction.

In recent work [7], we have investigated the integration of a session-typed discipline into a -calculus with located and update processes. The intention is to understand whether session types scale up to the challenging context of context-aware, adaptable distributed systems, in which disciplined communication and dynamic reconfiguration are intertwined concerns. Indeed, processes in our language may evolve either by the usual forms of synchronization but also by performing evolvability actions. Hence, besides showing that well-typed processes enjoy safety (i.e. absence of communication errors at runtime), we have proved that typing entails consistency, a property that guarantees that an update action for a located process is only enabled if such a process is not already involved in an active session. In this way, consistency ensures a disciplined interplay of communicating and evolvability actions. Our work borrows inspiration from the approach of Garralda et al [9] on (binary) session types for Ambients, and extends it to the case of adaptable processes which run in arbitrary, nested distributed locations.

In this presentation, we summarize the framework in [7] and illustrate it by presenting a process representation of supervision trees, a mechanism used in the Erlang language to enforce fault-tolerant programs [1]. Intuitively, supervision trees define a hierarchical structure of program components, in which workers perform computation and supervisors monitor the behavior of one or more workers. In case of failure or unexpected termination of a worker, its supervisor should restart its behavior. The supervisor module in Erlang defines different restarting strategies for supervisors. For instance, in the one for one restarting strategy, only the affected worker is restarted, and so supervision realizes a form of local adaptation. In contrast, in the one for all strategy, the supervisor restarts both the affected worker and also all of its sibling in the supervision tree, and so supervision enforces a form of global adaptation. We show how both these strategies may be represented in our typed process framework. We believe the resulting models are useful to appreciate the main features of our intended model of runtime adaptation.

Disclaimer and Organization. This paper is intended as a brief overview presentation to the approach and results first presented in [7]. As such, our focus here is in intuitions and examples rather than on technical details; the reader is referred to [7] (and to its extended version [8]) for extended details.

The rest of the paper is structured as follows. In Sect. Section 2 we present a motivating example for our intended model. Then, Sect. Section 3 gives a overview of our process framework for runtime adaptation with session types, and describes its main properties. In Sect. Section 4 we present our model of supervision trees as session-typed processes —both this model and the example in Sect. Section 2 are original to this presentation. Finally, in Sect. Section 5 we collect some concluding remarks and discuss directions for future work.

2A Motivating Example

We begin by discussing a simple model of a workflow application, which extends the one in [2]. Our model combines the main features of adaptable processes (nested locations and update processes) with delegation, the well-known mechanism for representing reconfiguration in session-typed processes.

Briefly, a workflow is a conceptual unit that describes how a number of activities coordinate to achieve a given task. A workflow-based application usually consists of a workflow runtime engine that contains a number of workflows running concurrently on top of it; a workflow base library on which activities may rely on; and of a number of runtime services, which are typically application dependent. Exploiting nested located processes, in [2] we propose the following high-level process representation of a workflow application:

This way, the application is modeled as a located process which contains a workflow engine (located at ) and a number of runtime services . In turn, the workflow engine contains a number of workflows , a process (which represents the engine’s behavior and is left unspecified), and a located process representing the base library (also left unspecified). Each workflow (with ) contains a process (representing the workflow’s logic) and is composed of activities. Each activity is formalized as an adaptable process and an execution environment . The intention is that while the execution environment hosts the current (session) behavior of the activity (denoted below), location contains an adaptation routine for location (denoted below). More precisely, our process representation for is the following:

Intuitively, is an unspecified session behavior that may interact with other activities, either inside or outside workflow . Process is meant to establish a session on name with a dual behavior contained in . The purpose of such a session is very simple: will send to a boolean expression which may determine runtime adaptation for location . In fact, if such an expression reduces to then an update process at is released and the session is closed. The purpose of such an update is to replace the current behavior of with process . Otherwise, if the boolean reduces to , then the session between and is closed and no modification on is performed.

Although the possibility of adapting the behavior of session-typed processes at runtime is quite appealing, arbitrary update actions may jeopardize the communication protocols described by session types. For instance, if the adaptation that may perform takes place while is in the middle of an active session, then a communication error would arise from the resulting unmatched communication steps. Similarly, if contains replicated server definitions which does not implement, then overall service availability at the workflow level would be affected. It then becomes important to add a certain discipline to update actions, in such a way that (i) prescribed communication behaviors are not disrupted by them, and (ii) service definitions are always uniformly updated. In our session-typed process framework with runtime adaptation (summarized in the following section), we address these two natural requirements for disciplining update actions.

3A Framework of Disciplined, Adaptable Processes

3.1Syntax and Semantics

Our process language corresponds to the extension of the usual polyadic -calculus for binary session types (cf. [11]) with located and update processes. Besides disjoint base sets for names, ranged over ; labels, ranged over ; and constants (integers, booleans, names), ranged over , we consider locations, ranged over ; integers, ranged over ; and process variables, ranged over . Then, processes, ranged over and expressions, ranged over are given by the grammar in Fig. ?. In the figure, we use to denote names and (polarized) channels [10], ranged over (with ). Channels are runtime entities, as formalized by our operational semantics (see below).

Our process syntax contains an almost standard session -calculus, with session passing (delegation) [12] and replicated session acceptance constructs. Observe that we consider an explicit prefix for session closing, denoted , which is useful to track the active sessions in a located process. Also, we consider only restriction over channels . Constructs related to runtime adaptation are given in the second line of the grammar for processes. We have located processes and update processes , where denotes a process with one or more occurrences of a process variable . Locations are transparent: hence, in process may evolve autonomously until it is updated by an update action on . One objective of our type system is then to discipline such update actions, so as to avoid updating processes which contain open sessions.

The operational semantics for our process language is defined as a reduction relation —Fig. ? reports the required reduction rules. The semantics generates and maintains runtime annotations for locations: annotated located processes are written , where integer denotes the number of active sessions in . These annotations are key in avoiding update actions over located processes which are currently engaged in active sessions. Reduction rules rely on (syntactic) contexts (ranged over ), which represent the nested structure of located processes. Such contexts are defined by the following syntax:

This way, given a context and a process , we write to denote the process obtained by filling in the holes in with . The intention is that may reduce inside , thus reflecting the transparent containment realized by location nesting. The semantics relies on a structural congruence relation (omitted), which handles scope extrusion for channels in the usual way. Rules and , which formalize session establishment and termination, resp., use two operations over contexts, denoted and . Informally, (resp. ) denotes the context obtained from by increasing (resp. decreasing) the annotation in all of its located processes. Rule captures the essence of our notion of consistency: a (located) process can be updated only if it contains no active sessions. The other rules are completely standard, generalized to our setting of nested, transparent locations.

3.2Type System

We have deliberately aimed at retaining a standard session type structure; see Fig. ?. We assume a notion of duality over session types , noted , defined as usual. We now comment on our notion of typing judgment. We extend usual judgments with an explicit interface, denoted . Intuitively, interfaces are assignments from names to session types which describe the services declared in a given process. We also consider typings and environments and . As typical in session types disciplines, typing collects assignments from channels from session types, therefore describing currently active sessions. In we also include bracketed assignments, denoted , which represent active but restricted sessions. As we discuss in [8], bracketed assignments arise in the typing of channel restriction, and are key to keep a precise count of the active sessions in a given location. is a first-order environment which maps expressions to basic types and names to pairs of qualified session types. Within the interface, session types can be linear () or unrestricted (). While is used for those session types to be used just once, annotates those session types intended to feature a persistent behavior. The higher-order environment collects assignments of process variables and locations to interfaces. While the former kind of assignments are relevant to update processes, the latter concern located processes. Given these environments, a type judgment is of form

meaning that, under environments and , process has active sessions declared in and interface .

We only present typing rules for adaptable and update processes; see [8] for a full account.

Rule types located processes and performs two checks. First, the runtime annotation is computed by counting the assignments (standard and bracketed) declared in . Second, the rule checks that the interface of the located process is less or equal than the declared interface of the given location. In the rule, denotes a preorder over interfaces, defined in [8]; hence, the premise in the rule informally ensures that the process behavior does not “exceed” the expected behavior within the location. It is worth observing how a typed located processes has the exact same typing and interface of its contained process: this is how transparency of locations arises in typing. Rule types update processes. Observe how the interface associated to the process variable of the given runtime adaptation routine should match with the declared interfaces for the given location.

3.3Safety and Consistency

We now summarize the main properties of well-typed processes. They are thoroughly developed in [8]. We say a typing is balanced iff for all (resp. ) then also (resp. ). Then we have the following subject reduction theorem:

In order to state runtime safety, we require some auxiliary definitions. Process is said to be a -process if it sends (resp. receives) a value/session on , if it makes a selection (resp. offers a choice), or if it closes a session on . Then, two -processes constitute a -redex if they are complementary to each other, possibly enclosed in suitable contexts: i.e., if one sends, the other receives on ; if one makes a selection, the other chooses; or if they are closing the same session . Then, is an error if, up to structural congruence, it contains either exactly two -processes that do not form a -redex or three or more -processes. Our first result is that well-typed process do not lead to communication errors:

We now state and proof consistency, the property that ensures that update actions do not disrupt session behavior. We require an auxiliary notation. Let stand for an update action, i.e., a reduction inferred using rule , possibly with the interplay of (structural) congruence rules. We have:

Our type system ensures that both annotations enabling update actions and interface assignments to locations are correctly assigned and maintained along reduction. Indeed, as our second result we have:

4A Session Model of Supervision Trees

In this section, we present encodings of supervision trees in our session language with located and update processes. As supervision trees (as provided by the supervisor module of Erlang [1]) define a basic principle for designing and programming fault-tolerant programs, our encodings are useful to understand how these our two constructs for runtime adaptation (a) may integrate into communicating processes and (b) relate to actual programming mechanisms.

As mentioned in the Introduction, supervision trees organize a program’s behavior into two classes: workers and supervisors. Workers are the processes that actually perform computation —in a communication-centric setting, workers may well correspond to servers which expect and fulfill clients’ requests. Supervisors are meant to monitor the behavior of workers by relying on a hierarchical, tree-like structure. This way, a supervisor may monitor the behavior of a number of children; the supervisor may itself be supervised by another supervisor at a higher level in the hierarchy. As an example, Figure 1 shows a supervisor that monitors workers and ; in turn, is supervised by who is also a supervisor for worker . In Erlang, a supervisor can implement several restart strategies which, given a termination signal (e.g. a failure), offer different possibilities for restarting the supervised worker(s). Such strategies are fully detailed in [1]; in what follows, we concentrate on the following two:

  1. One for one

    strategy: if one of the children fails and terminates then only that child is restarted.

  2. One for all

    strategy: if one of the children fails and terminates then all the siblings must be terminated and restarted.

Figure 1: A supervision tree: supervisors are depicted as rectangles; workers are represented by circles.
Figure 1: A supervision tree: supervisors are depicted as rectangles; workers are represented by circles.

It is interesting to observe how the hierarchical model for fault-tolerance realized by supervision trees in Erlang is in line with our intended model for runtime adaptation: in addition to the loose analogy between located processes as workers and update processes as supervisors, we find useful similarities in terms of hierarchical organization of behavior, and in the fact that both supervision trees and our update processes are in essence external recovery mechanisms for precisely delimited behaviors. These intuitions are our starting point for the process representations of supervision trees which we analyze in the rest of this section. For simplicity, we restrict to representing the hierarchy given in Figure 1, assuming that both and realize the same restarting strategy; the representation, however, can be easily generalized to more complex settings. We represent the tree structure by the nesting of located processes, as follows:

In our models, we distinguish between localities for supervisors (denoted ) and localities for workers (denoted ). In the following, we describe two different implementations of supervisors, each realizing a different restarting strategy.

4.1Modeling One for one Supervisors

As suggested by the above informal description, the one for one strategy accounts for a form of local supervision: whenever a child enters in an error state or fails, the child activity must be terminated and the server restarted. This way, for instance, in the scenario of Figure 1, if worker fails then its siblings and should be unaffected by (and unaware of) the restarting process that is expected to initiate.

The one for one strategy can be encoded in a quite natural way within our framework. Indeed, by assuming that workers correspond to servers which are connected to clients, we may represent failures as the reception of an invalid value from the client; the restarting process would then correspond to an update action that simply recreates the located process containing the server. More precisely, we assume worker is an “arithmetic server” which calculates the division between two natural numbers which are communicated by a client after establishing a session. In this context, failure corresponds to division by zero. Then, notifies whether it has found a failure (i.e., the denominator being equal to zero). Subsequently, should decide what to do next: (i) if the denominator sent by the client was zero, then there is indeed a failure and session between and must terminate immediately; (ii) otherwise, if there is not a failure, then should allow to perform the division and communicate the result to . Observe how in this “local” scenario, supervisor does not need to interact with its supervisor .

To implement the supervision model described above, every time a worker (such as ) establishes a session with a client (such as ), it also connects to its supervisor. This way, supervisor and worker have a unique communication mechanism. In our case, this mechanism is realized by a session request from to on name . By extending the process in (Equation 1) with a client , we obtain the following system:

where , , and are defined as follows (the other processes are left unspecified):

Observe how process defines an update action on if communicates to him that the invalid value zero was received. Moreover, using our update construct we can implement more interesting supervisors which go beyond service restarting. For instance, consider the following process:

Note how improves over by placing inside a different server behavior, represented by . Suppose that represents an upgraded version of in which the arithmetic server no longer relies on a supervisor in order to deal with failures/invalid values. This way, whenever communicates a failure to , the resulting update action will lead to an improved server that will be able to handle the failure locally, without appealing to an external entity.

Finally, we briefly discuss the typing of previous processes. Given session types

the processes above are well-typed, given the following environments:

In fact, processes , and can be typed as follows:

Notice that as a consequence of being well-typed, the supervisor has to wait that all sessions have been closed before proceeding with an update of its worker.

4.2Modeling One for all Supervisors

While the one for one restarting strategy implements a local relation between a worker and his supervisor, in the one for all strategy supervisors need to globally control their children. Hence, whenever one of the children enters in an error state, the child and its siblings have to be terminated and restarted. For instance, in the scenario of Figure 1, if fails then both and all the workers contained in the subtree rooted in need to be terminated and restarted. This event should be communicated by to , which should handle the termination of its children. On the other hand, if fails then its supervisor only has to stop , and nothing needs to be communicated to .

The required global control that supervisors should exercise over children in the one for all strategy turns out to be slightly unnatural to express in our framework of binary communication. Nevertheless, it is possible to devise a protocol that achieves the desired synchronization sequences. An alternative to represent global synchronization among services and supervisors is described by the following informal protocol, in which communication between workers and clients (and between supervisors and workers) is managed via session establishment:

  1. Each worker receives a data value from a client. The value is checked for validity (via a boolean expression ). The result of this check is sent to the worker’s supervisor.

  2. Each supervisor waits to receive the result of the validity check from all its children.

  3. Each supervisor (apart from the root node ) sends a value to its own supervisor. This is to have a uniform communication pattern, and is related to the fact that supervisors themselves cannot fail.

  4. Starting from the root of the tree, a communication cascade takes place: each supervisor communicates to its children if they can continue with their own service or if they have to terminate. Intuitively, a single worker can continue if and only if (i) none of its siblings have failed and (ii) the supervisor has not received from its own supervisor a failure signal.

  5. If a worker (server) receives a failure signal, then it stops the communications with its clients; otherwise, it can continue with its intended behavior.

  6. Finally, when a worker/server stops it is recreated by its supervisor. In the case of a forced termination this corresponds to the actual restarting strategy; in the case of natural termination, this enables server persistency. In the process below, this is realized via an update. Notice that will need to recreate only , while will be responsible for the restart of its own children and .

Notice that Figure 1 already shows in the edges the names of the services established between each pair. As before, we consider a process structure as in (Equation 2). Here we just give the encoding of worker —the other workers have analogous representations. Moreover, we suppose that can establish a session with client . Notice that each worker can establish a session with just one client at the time. We have:

Above, , , and denote the rest of the behavior of workers, clients and supervisors; as such, we assume that and and are the dual of each other. Observe how as soon as worker has established a session on with the client, it contacts its own supervisor through and implements step (1) of the protocol above, then it waits for the decision of the supervisor. Similarly for its supervisor : after it has established a session for each worker ( and ) through services and respectively, it contacts via and implements steps (2) and (3) of the protocol. Finally, the root supervisor will take a decision and communicate to its workers whether they have to terminate or continue. Then it will pass the information to (child) supervisor , that in turn will rule the behavior of its children (cf. steps (4) and (5)). As a last step (point (6)) each supervisor will take care of recreating of its own children.

The system above can be shown to be well-typed, analogously as we did for the process representation for the one for one strategy. Here again, typing ensures that no session is disrupted while communication with clients is still active.

5Concluding Remarks and Future Work

In this short note, we have presented the main elements of our work in [7], where we have investigated the integration of constructs for runtime adaptation into a session types discipline. Our starting point was our own work on adaptable processes [2], a process calculi framework for specifying interacting processes which may be suspended, restarted, updated, or discarded at runtime. Our work in [7] appears to be the first attempt in addressing the integration of dynamic evolution issues in models of communicating systems in which interaction is described (and disciplined) by session types.

The original contributions of this presentation are examples which illustrate how the constructs for runtime adaptation fit in a session-typed, communication-centric setting. We presented and discussed a process representation of Erlang’s supervision trees, an existing mechanism for designing and programming fault-tolerant applications. Similarly as update processes, supervisors are external entities which monitor the behavior of one or more workers. Supervisors are parametric in the restarting strategies that are spawned when a failure occurs in a worker. We described representations of supervisors into our session process language, and observed that although a model of local supervision (in which a supervisor monitors only one worker) is better suited to the communication primitives in our model, an alternative model of global supervision (in which a supervisor may monitor more than one worker) can also be encoded. Interestingly, in both cases our session language with runtime adaptation capabilities is able to represent more insightful restarting strategies than those provided by the Erlang language.

The framework in [7], summarized in this note, paves the way for several avenues of future work:

  1. We would like to investigate progress (deadlock freedom) for adaptable, session-typed processes. This includes adapting to our setting known session type systems for ensuring progress [6], but also understanding whether the information added by such systems (e.g., orderings on sessions) can be integrated into update actions so as to prevent/overcome deadlocked sessions at runtime.

  2. Our nested, transparent locations may be too “open” for some applications. One may need to enforce secure updates, so as to protect located processes from unintended evolvability actions —as in, for instance, update actions triggered by unauthorized users, or update actions based on permissions for replacing, extending, or destroying the behavior of a located process.

  3. In this presentation, we have refrained from enriching the typing system with information related to updates. We think such an extension would not be difficult, and could offer a way to better control update actions. This way, for instance, one could decree (and statically check) that update actions occur only in selected parts of a communication protocol.

Acknowledgments. We are grateful to the anonymous reviewers: their comments and remarks were most useful in improving the presentation of this short note. Our interest in exploring how supervision trees in Erlang could be modeled in our framework was triggered by a suggestion from Philip Wadler during PLACES’13 to whom we are grateful. This research was supported by the Portuguese Foundation for Science and Technology (FCT) under grants SFRH/BPD/84067/2012 and CITI, and by French project ANR BLAN 0307 01 - SYNBIOTIC.



    Ericsson AB (2013): Erlang/OTP - System Documentation: Supervisor Module. .
  2. Logical Methods in Computer Science 8(4).
    Mario Bravetti, CinziaDi Giusto, Jorge A. Pérez & Gianluigi Zavattaro (2012): Adaptable Processes. Available at
  3. In Simon Bliudze, Roberto Bruni, Davide Grohmann & Alexandra Silva, editors: ICE, EPTCS 38, pp. 13–27.
    Marco Carbone & Søren Debois (2010): A Graphical Approach to Progress for Structured Communication in Web Services. Available at
  4. In Maciej Koutny & Irek Ulidowski, editors: CONCUR, Lecture Notes in Computer Science 7454, Springer, pp. 272–286.
    Romain Demangeon & Kohei Honda (2012): Nested Protocols in Session Types. Available at
  5. Logical Methods in Computer Science 8(4).
    Pierre-Malo Deniélou, Nobuko Yoshida, Andi Bejleri & Raymond Hu (2012): Parameterised Multiparty Session Types. Available at
  6. In: TGC 2007, Lecture Notes in Computer Science 4912, Springer, pp. 257–275.
    Mariangiola Dezani-Ciancaglini, Ugo de’Liguoro & Nobuko Yoshida (2007): On Progress for Structured Communications. Available at
  7. In Sung Y. Shin & José Carlos Maldonado, editors: SAC, ACM, pp. 1913–1918.
    CinziaDi Giusto & Jorge A. Pérez (2013): Disciplined structured communications with consistent runtime adaptation. Available at
  8. Technical Report, CITI - FCT UNL.
    CinziaDi Giusto & Jorge A. Pérez (2013): Disciplined Structured Communications with Consistent Runtime Adaptation (Extended Version). Available at
  9. In: PPDP, ACM, pp. 61–72.
    Pablo Garralda, Adriana B. Compagnoni & Mariangiola Dezani-Ciancaglini (2006): BASS: boxed ambients with safe sessions. Available at
  10. Acta Inf. 42(2-3), pp. 191–225.
    Simon J. Gay & Malcolm Hole (2005): Subtyping for session types in the pi calculus. Available at
  11. In: ESOP, LNCS 1381, Springer, pp. 122–138.
    Kohei Honda, Vasco Thudichum Vasconcelos & Makoto Kubo (1998): Language Primitives and Type Discipline for Structured Communication-Based Programming. Available at
  12. Electr. Notes Theor. Comput. Sci. 171(4), pp. 73–93.
    Nobuko Yoshida & Vasco Thudichum Vasconcelos (2007): Language Primitives and Type Discipline for Structured Communication-Based Programming Revisited: Two Systems for Higher-Order Session Communication. Available at
Comments 0
Request Comment
You are adding the first comment!
How to quickly get a good reply:
  • Give credit where it’s due by listing out the positive aspects of a paper before getting into which changes should be made.
  • Be specific in your critique, and provide supporting evidence with appropriate references to substantiate general statements.
  • Your comment should inspire ideas to flow and help the author improves the paper.

The better we are at sharing our knowledge with each other, the faster we move forward.
The feedback must be of minimum 40 characters and the title a minimum of 5 characters
Add comment
Loading ...
This is a comment super asjknd jkasnjk adsnkj
The feedback must be of minumum 40 characters
The feedback must be of minumum 40 characters

You are asking your first question!
How to quickly get a good answer:
  • Keep your question short and to the point
  • Check for grammar or spelling errors.
  • Phrase it like a question
Test description