[Back to Main Page]

Service Instance
Structural

Intent

Allows multiple users to have an instance of a particular service, managing their state and enabling those instances to efficiently collaborate.

Also Known As

Virtual services

Example TinyOS Components

TimerM, MateHandlerStore, DripM

Motivation

Sometimes many components or subsystems need to use a system service, but each user wants a separate instance of that service. Additionally, the service may have state associated with each user, and needs to make decisions based on the state of all of the users.

The TinyOS Timer is an example of this problem: many components, such as network protocols, need timers for timeouts, rate-based operations, or coordination. Each of these timers must be independent from the others: a data sampler starting its timer should not cause a network protocol's timer to fire earlier or later than desired.

Although, from the users of the timers perspective, each timer is independent, their implementation requires knowing the state of all of the timers. If the implementation can easily determine which timer has to fire next, then it can schedule the underlying clock resource to fire as few interrupts as possible before meeting this lowest timer's requirement. Firing fewer interrupts reduces CPU load on the system and can allow it to sleep longer, saving energy.

Implementing each timer as a seperate component has two major problems: code copying and state coordination. Every time a component needs a timer, the programmer has to make a copy of a timer component and rename it. With nesC 1.2, the timer could just be a generic module, so there is a single copy of the nesC code text, but the final code image will still have multiple copies of the binary. Additionally, the modules need some way to communicate, in order to figure out how to set the underlying hardware clock. Just setting it at a fixed rate and maintaining a counter for each Timer is inefficient: timer fidelity requires firing at a high rate, but it's pointless to fire at 1KHz if the next timer is in four seconds.

Allocating a constant number of instances of the service (e.g., 16 timers) is either prone to failure or wasteful. If the system needs fewer than the number allocated, then RAM is wasted. If the system needs more than the number allocated, then detection of the mismatch is left until run-time, when the component implementing the service refuses a request.

The service instance pattern provides a solution to this problem. Using this pattern, each user of a service can have a virtual instance of it, but those instances share an implementation and can easily share state. Each instance has a unique name, generated with the local keyset pattern.

A component following the service instance pattern provides the desired service in a parameterized interface; each user of the service wires to a separate instance of the interface, using the unique() function (generating the local keyset). In nesC 1.1 and earlier, each user of the service must know the key to pass to the call to unique(); in nesC 1.2 and later, the call to unique() can be encapsulated in a generic configuration.

The underlying component can determine how many instances of the service exist using the uniqueCount() function. It can use this value to allocate state for each instance of the service, and the key each instance has (obtained with unique()) can index which state element to use.

Applicability

Use the service instance pattern when:

  • A component needs to provide multiple instances of a service, but does not know how many.
  • Each instance of the service appears to its user to be independent of the others.
  • The provider of the service needs to be able easily access the state of each instance.

Structure

Participants

  • ServiceProvider: allocates state for each instance of the service and coordinates underlying resources based on all of the instances.
  • ServiceUser: users of the service.
  • Resource: an underlying system resource that ServiceProvider multiplexes/demultiplexes service instances on.

Collaborations

When a ServiceUser makes calls on its instance of its service, the ServiceProvider modifies its internal state of the instance accordingly. Addittionally, it may change the operation of the underlying Resource to meet the changed needs.

Consequences

Because ServiceProvider allocates the state for every instance of the service, it can easily perform operations that require access to each instance. Additionally, the amount of state allocated is determined at compile time to meet the number of instances.

However, because the pattern scales to a variable number of instances, the cost of its operations may scale linearly with the number of users, and can't be known until an application is composed. For example, if setting the underlying clock interrupt rate in a timer ServiceProvider depends on the timer with the shortest remaining duration, ServiceProvider might determine this by scanning all of the timers, an O(n) operation.

If many users require an instance of a service, but each of those instances are used rarely, then allocating state for each one can be wasteful. The other option is to allocate a smaller amount of state and dynamically allocate it to users as need be. This can conserve RAM, but requires more RAM per real instance (client IDs need to be maintained), imposes a CPU overhead (allocation and deallocation), and can fail at run-time (if more requests come in than can be handled). The standard TinyOS task queue is similar to this dynamic approach, although it does not follow the ServiceProvider pattern: allocating state for every task is prohibitive.

Implementation

The ServiceProvider provides the service as a parameterized interface. Users that need an instance of the service wire to the parameterized interface with a unique identifier using the unique() function provided by nesC. The ServiceProvider allocates state for each client using an array of size determined by the uniqueCount() function, so that the unique identifiers can map onto elements of the array.

One common pitfall in the using a service instance is the key passed to unique(); every user of the service must be wired with the same key. For example, this code will work:

components Provider, User1, User2;

User1.Service -> Provider.Service[unique("Service")];
User2.Service -> Provider.Service[unique("Service")];

While this code will have run-time issues:

components Provider, User1, User2;

User1.Service -> Provider.Service[unique("User1")];
User2.Service -> Provider.Service[unique("User2")];

Because User1M and User2 use different keys in the second example, the calls to unique() will not return values that are unique with respect to one another. Two components may inadvertently be mapping to the same instance of the service. Additionally, the ServiceProvider component may allocate too few instances, and so access memory inappropriately.

In nesC 1.2, the call to unique() can be encapsulated in a generic configuration; this solves simple problems like typographic errors in the key or mistaken wirings.

Sample Code

Consider the TinyOS Timer abstraction. Various components in an application, from an ADC calibrator to networking protocols to application triggers, need timers to operate properly. However, motes generally have a small number of hardware timers, many fewer than the number of application timers needed. A component therefore needs to multiplex/demultiplex multiple software timers on one or more hardware timers.

The ServiceProvider, TimerM, needs to maintain state for each software timer: whether it is running, is a repeating timer, and what its remaining time is. A configuration, TimerC, exports TimerM's interface and wires it to an underlying clock resource. Individual clients wire to TimerC.Timer, using "Timer" as a key for the call to unique. The TimerM implementation:

module TimerM {
  provides interface Timer[uint8_t id];
  ...
  uses interface Clock;
}
implementation {
  enum {
    NUM_TIMERS = uniqueCount("Timer"),
  }

  struct timer_s {
    uint8_t type;           // one-short or repeat timer
    int32_t ticks;          // clock ticks for a repeat timer 
    int32_t ticksLeft;      // ticks left before the timer expires
  } mTimerList[NUM_TIMERS];


  command result_t Timer.start[uint8_t id](char type, uint32_t interval) {
    if (id >= NUM_TIMERS) return FAIL;
    if (type > TIMER_REPEAT) return FAIL;
    mTimerList[id].ticks = interval ;
    mTimerList[id].type = type;

    /* See if the underlying Clock needs to be adjusted */
    return SUCCESS;
  }

}

TimerC is a configuration that wires TimerM up to the clock and exports TimerM's Timers:

configuration TimerC {
  provides interface Timer[uint8_t id];
}

implementation {
  components TimerM, ClockC;

  TimerM.Clock -> ClockC;
  StdControl = TimerM;
  Timer = TimerM;
}
		  

Finally, clients that want a Timer wire using unique():

configuration GenericComm {
  ...
}
implementation {
  components TimerC, AMStandard;
  ...

  AMStandard.Timer -> TimerC.Timer[unique("Timer")];
  ...
}
		  

Known Uses

TimerM, as detailed above, uses a service instance pattern to manage various application timers.

The viral code propagation subsystem of the Maté framework uses a service instance to manage version metadata for code capsules. As the VM is customizable, the number of needed capsules isn't known until the VM is actually composed.

In a similar vein, the epidemic dissemination protocol Drip uses the service instance pattern to maintain epidemic state for each disseminated value.

Related Patterns

Service instances use a local keyset for counting the number of instances. If the service requires global identifiers, then a key map can map from the global to local identifiers.

A service instance may seem similar to a dispatcher at first glance, but its purpose is very different. A dispatcher allows the set of functionality a system supports to be easily extended, while a service instance allows instantiation of a particular piece of functionality. Additionally, a core part of the service instance pattern is state allocation and management.

[Back to Main Page]

Last modified: Fri Jul 30 17:09:59 PDT 2004