OSGi Bundles collaborate using loosely coupled services registered in the OSGi service registry. This is a powerful and flexible model, and allows for the dynamic replacement of services at runtime. OSGi services are therefore a very common interaction pattern within OSGi.
As with most Java APIs and Objects, OSGi services are primarily synchronous in operation. This has several benefits; synchronous APIs are typically easier to write and to use than asynchronous ones; synchronous APIs provide immediate feedback; synchronous implementations typically have a less complex threading model.
Asynchronous APIs, however, have different advantages. Asynchronous APIs can reduce bottlenecks by encouraging more effective use of parallelism, improving the responsiveness of the application. In many cases high throughput systems can be written more simply and elegantly using asynchronous programming techniques.
The Promises Specification provides powerful primitives for asynchronous programming, including the ability to compose flows in a functional style. There are, however, many existing services that do not use the Promise API. The purpose of the Asynchronous Service is to bridge the gap between these existing, primarily synchronous, services in the OSGi service registry, and asynchronous programming. The Asynchronous Service therefore provides a way to invoke arbitrary OSGi services asynchronously, providing results and failure notifications through the Promise API.
-
Async Invocation - A single method call that is to be executed without blocking the requesting thread.
-
Client - Application code that wishes to invoke one or more OSGi services asynchronously.
-
Async Service - The OSGi service representing the Asynchronous Services implementation. Used by the Client to make one or more Async Invocations.
-
Async Mediator - A mediator object created by the Async Service which represents the target service. Used by the Client to register Async Invocations.
-
Success Callback - A callback made when an Async Invocation completes with a normal return value.
-
Failure Callback - A callback made when an Async Invocation completes with an exception.
-
Async Service - A service that can create Async Mediators and run Async Invocations.
-
Target Service - A service that is to be called asynchronously by the Client.
-
Client - The code that makes Async Invocations using the Async Service
-
Promise - A promise, representing the result of the Async Invocation.
This section is an introduction in the usage of the Async Service. It is not the formal specification, the normative part starts at Async Service. This section leaves out some of the details for clarity.
The Async Service provides a mechanism for a client to asynchronously invoke methods on a target service. The service may be aware of the asynchronous nature of the call and actively participate in it, or be unaware and execute normally. In either case the client's thread will not block, and will continue executing its next instructions. Clients are notified of the completion of their task, and whether it was successful or not, through the use of the Promise API.
Each async invocation is registered by the client making a method call on an Async Mediator, and then started by making a call to the Async Service that created the mediator. This call returns a Promise that will eventually be resolved with the return value from the async invocation.
An Async Mediator can be created by the client, either from an Object, or directly from a Service Reference. Using a service reference has the advantage that the mediator will track the underlying service. This means that if the service is unregistered before the asynchronous call begins then the Promise will resolve with a failure, rather than continuing using an invalid service object.
The general pattern for a client is to obtain the Async Service, and a service reference for the target service. The client then creates an Async Mediator for the target service, invokes a method on the mediator, then starts the asynchronous call. This is demonstrated in the following example:
private Async asyncService;
private ServiceReference<Foo> fooRef;
private Foo mediated;
@Reference
void setAsync(Async async) {
asyncService = async;
}
@Reference(service = Foo.class)
void setList(ServiceReference<Foo> foo) {
fooRef = foo;
}
@Activate
void start() {
mediated = asyncService.mediate(fooRef, Foo.class);
}
public synchronized void doStuff() {
Promise<Boolean> promise = asyncService
.call(mediated.booleanMethod(“aValue”));
...
}
This example demonstrates how simply clients can make asynchronous calls using the Async Service. The eventual result can be obtained from the promise using one of the relevant callbacks.
One important thing to note is that whilst the call to call() or
call(R) causes the async invocation to begin, the actual
execution of the underlying task may be queued until a thread is
available to run it. If the service has been unregistered before the
execution actually begins then the promise will be resolved with a
Service Exception. The type of the Service Exception will be
ASYNC_ERROR
.
The return value of the mediator method call is used to provide
type information to the Async Service. This, however, does not work for
void
methods that have no return value. In this case the
client can either pass an arbitrary object to the call(R)
method, or use the zero argument call()
method. In either case the returned promise will eventually resolve with
a value of null
. This is demonstrated in the following
example.
private Async asyncService;
private ServiceReference<Foo> fooRef;
private Foo mediated;
@Reference
void setAsync(Async async) {
asyncService = async;
}
@Reference(service = Foo.class)
void setList(ServiceReference<Foo> foo) {
fooRef = foo;
}
@Activate
void start() {
mediated = asyncService.mediate(fooRef, Foo.class);
}
public synchronized void doStuff() {
mediated.voidMethod();
Promise<?> promise = asyncService
.call();
...
}
Sometimes a client does not require any notification that an async invocation has completed. In this case the client could use one of the call() or call(R) methods and simply discard the returned Promise object. This, however, can be wasteful of resources. The act of resolving the Promise object may be expensive, for example it may involve serializing the return value over a network if the remote call was asynchronous.
If the client knows that no Promise object representing the result of the asynchronous task is needed then it can signal this to the Async Service. This allows the Async Service to better optimize the async invocation by not providing a result.
To indicate that the client wants to make a fire-and-forget style call the client invokes the mediator as normal, but then begins the asynchronous invocation using the execute() method as show below.
private Async asyncService;
private ServiceReference<Foo> fooRef;
private Foo mediated;
@Reference
void setAsync(Async async) {
asyncService = async;
}
@Reference(service = Foo.class)
void setList(ServiceReference<Foo> foo) {
fooRef = foo;
}
@Activate
void start() {
mediated = asyncService.mediate(fooRef, Foo.class);
}
public synchronized void doStuff() {
mediated.someMethod();
asyncService.execute();
...
}
Note that the execute() method does still return a Promise. This Promise is not the same as the ones returned by call() or call(R), its resolution value does not provide access to the result, but instead indicates whether the fire-and-forget call could be successfully started. If there is a failure which prevents the task from being executed then this is used to fail the returned promise.
By their very definition asynchronous tasks do not run inline, and typically they will not run on the same thread as the caller. This is not, however, a guarantee. A valid implementation of the Async Service may have only one worker thread, which may be the thread currently running in the client code. Async invocations also have the same threading model as the Promise API. This means that callbacks may run on arbitrary threads, which may, or may not, be the same as the client thread, or the thread which executed the asynchronous work.
It is important for multi-threaded clients to note that calls to the mediator and Async Service must occur on the same thread. For example it is not supported to invoke a mediator using one thread, and then to begin the async invocation by calling the call(), call(R) or execute() method on a different thread.
The Async Service is the primary interaction point between a client and the Async Service implementation. An Async Service implementation must expose a service implementing the Async interface. Clients obtain an instance of the Async Service using the normal OSGi service registry mechanisms, either directly using the OSGi framework API, or using dependency injection.
The Async Service is used to:
-
Create async mediators
-
Begin async invocations
-
Obtain Promise objects representing the result of the async invocation
The first action that a client wishing to make an async invocation
must take is to create an async mediator using one of the
mediate
methods. Once created the client invokes the method
that should be run asynchronously, supplying the arguments that should
be used. This call records the invocation, but does not start the
asynchronous task. The asynchronous task begins when the client invokes
one of the call
or execute
methods on the
Async Service. The call
methods must return a Promise
representing the async invocation. The promise must resolve with the
value returned by the async invocation, or fail with the failure thrown
by the async invocation.
If the client attempts to begin an async invocation without first
having called a method on the mediator object then the Async Service
must detect this usage error and throw an
IllegalStateException
to the client. This applies to all
methods that begin an async invocation.
There are a variety of reasons that async invocations may be
started correctly by the client, but then fail without running the
asynchronous task. In any of these cases the Promise representing the
async invocation must fail with a Service Exception. This Service
Exception must be initialized with a type of ASYNC_ERROR
.
If there is no promise representing the async invocation then there is
no way to notify the client of the failure, therefore the Service
Exception must be logged by the Async Service using all available Log
Service implementations.
The following list of scenarios is not exhaustive, but indicates failure scenarios that must result in a Service Exception with a type of async
-
If the client is using a service reference backed mediator and the client bundle's bundle context becomes invalid before looking up the target service.
-
If the client is using a service reference backed mediator and the service is unregistered before making the async invocation.
-
If the client is using a service reference backed mediator and the service lookup returns
null
-
If the Async Service is unable to accept new work, for example it is in the process of being shut down.
-
If the type of the mediator object does not match the type of the service object to be invoked.
Implementations of the Async Service must be thread safe and may be used simultaneously across multiple clients and from multiple threads within the same client. Whilst the Async Service is able to be used across multiple threads, if a client wishes to make an async invocation then the call to the mediator and the call to begin the async invocation must occur on the same thread. The returned Promise may then be shared between threads if required.
It is expected, although not required, that the Async Service implementation will use a Service Factory to create customized implementations for each client bundle. This simplifies the tracking of the relevant client bundle context to use when performing service lookups on the client bundle's behalf. Clients should therefore not share instances of the Async Service with other bundles. Instead both bundles should obtain their own instances from the service registry.
If the Async Service is being used to call an OSGi service object
and the service reference is available then the service object should be
looked up immediately before the asynchronous task begins executing.
This ensures that the service is still available at the point it is
eventually called. Any call to getService
must have a
corresponding call to ungetService
after the mediated
method invoked has returned and, if available, the promise is resolved,
but before the asynchronous task releases its thread of
execution.
Async mediators are dynamically created objects that have the same type or interface as the object being mediated, and are used to record method invocations and arguments. Mediator objects are specific to an Async Service implementation, and must only be used in conjunction with the Async Service object that they were created by.
Mediators may be created either from a ServiceReference
or from a service object. The actions and overall result are similar for
both the mediate(ServiceReference,Class) and mediate(T,Class) methods, with the primary difference being that
mediated objects created from a ServiceReference
will
validate whether the service object is still available immediately before
the asynchronous task is executed.
The client passes in a Class
indicating the type that
should be mediated. If the class object represents an interface type
then the generated mediator object must implement that interface. If the
class object represents a Java class type then the mediator object must
either be an instance of that type or extend it.
When building a mediator object the Async Service has the opportunity to detect numerous problems, for example if the referenced service to be mediated has been unregistered. Although fail-fast behavior is usually preferable, in this case it would force the client to handle errors in two places; both when creating the mediator, and for the returned Promise. To simplify client usage, error cases detected when creating a mediator must not prevent the mediator from being created and must not result in an exception being thrown. The only reason that the Async Service may fail to create a mediator is if the class object passed in cannot be mediated.
There are three reasons why the Async Service may not be able to mediate a class type:
-
The class object passed in represents a final type.
-
The class object passed in represents a type that has no zero-argument constructor.
-
The class object passed in represents a type which has one or more public final methods present in its type hierarchy (other than those declared by
java.lang.Object
).
If any of these constraints are violated and prevent the Async Service from creating a mediator then the Async Service must throw an IllegalArgumentException.
When invoked, the Async mediator must record the method call, and
its arguments, and then return rapidly and should avoid performing
blocking operations. The values returned by the mediator object are
opaque, and the client should not attempt to interpret the returned
value. The value may be null (or null-like in the case of primitives) or
contain implementation specific information. If the mediated method call
has a return type, specifically it is non-void, then this object must be
passed to the Async Service's call
method when beginning
the async invocation
Async mediators should make a best-effort attempt to detect incorrect API usage from the client. If this incorrect usage is detected then the mediator object must throw an IllegalStateException when invoked. An example of incorrect usage that must be detected is when a client makes multiple invocations on a single mediator object from the same thread without making any calls to the Async Service.
After a usage error has been detected and an IllegalStateException has been thrown the mediator object must be reset so that a subsequent invocation from the client thread can proceed normally.
Async mediators, like instances of the Async Service, are required to be thread safe. Clients may therefore share mediator objects across threads, and can safely store them as instance fields. Whilst mediators are thread safe, if a client wishes to make an async invocation then the call to the mediator and the call to call() or call(R) must occur on the same thread. The returned Promise may then be shared between threads if required.
Async mediators created from ServiceReference
objects
remain directly associated with the service reference and client bundle
after creation. Clients should therefore not share mediator objects with
other bundles. Instead each bundle should create its own
mediator.
The Async Service provides call() and call(R) methods for clients to use when they wish to receive results from asynchronous tasks. Clients that do not need the result can simply discard the returned Promise object. This, however, can be wasteful of resources. The act of resolving the Promise object may be expensive, for example it may involve serializing the return value over a network.
To address this use case the Async Service provides the execute()
method, which behaves similarly to call() and
call(R), but does not provide access to the eventual
result. Instead the execute() method returns a Promise
that
indicates whether the fire-and-forget call is able to be successfully
started.
The returned Promise must be resolved with null
if the
asynchronous task begins executing successfully. There is no
happens-before relationship required, meaning that if
the Promise resolves successfully then the task may, or may not, have
started or finished. The primary usage of the Promise is actually to
detect failures. If the fire-and-forget task cannot be executed for some
reason, for example the backing service has been unregistered, then the
returned promise must be failed appropriately using the same rules as
defined in Asynchronous Failures. If the returned
Promise is failed then the fire-and-forget task has not executed and will
not execute in the future.
Some service APIs are already asynchronous in operation, and others are partly asynchronous, in that some methods run asynchronously and others do not. There are also services which have a synchronous API, but could run asynchronously because they are a proxy to another service. A good example of this kind of service is a remote service. Remote services are local views of a remote endpoint, and depending upon the implementation of the endpoint it may be possible to make the remote call asynchronously, optimizing the thread usage of any local asynchronous call.
Services that already have some level of asynchronous support may
advertise this to clients and to the Async Service by having their service
object be an instanceof
AsyncDelegate. The service object can be cast to AsyncDelegate to be used by the Async Service implementation, or
by the client directly, to make an asynchronous call on the
service.
Because the Async Delegate behavior is transparently handled by the
Async Service, clients of the Async Service do not need to know whether
the service object is an instanceof
AsyncDelegate or not. Their usage pattern can remain
unchanged.
When making an async invocation, the Async Service must check to see
whether the service object is an instanceof
AsyncDelegate. If the service object is an
instanceof
AsyncDelegate, then the Async Service must attempt to delegate
the asynchronous call. The exact delegation operation depends on whether a
Promise result is required.
If the result of the method invocation is needed by the client, then the Async Service must attempt to delegate to the async(Method,Object[]) method. The delegation proceeds as follows:
-
If the call to the Async Delegate returns a Promise, then the Promise returned by the Async Service must be resolved with that Promise.
-
If the call to the Async Delegate throws an exception, then the Promise returned by the Async Service must be failed with the exception.
-
If the Async Delegate is unable to optimize the call and returns
null
from the async(Method,Object[]) method, the Async Service must continue processing the async invocation, treating the service as a normal service object.
If the result of the method invocation is not needed by the client, then the Async Service must attempt to delegate to the execute(Method,Object[]) method. This gives the Async Delegate implementation the opportunity to further optimize its processing. The delegation proceeds as follows:
-
If the call to the Async Delegate returns
true
, then the Promise returned by the Async Service must be resolved withnull
. -
If the call to the Async Delegate throws an exception, then the Promise returned by the Async Service must be failed with the exception.
-
If the Async Delegate is unable to optimize the call and returns
false
from the execute(Method,Object[]) method, the Async Service must continue processing the async invocation, treating the service as a normal service object.
If an Async Delegate implementation accepts an asynchronous task, via a call to either execute(Method,Object[]) or async(Method,Object[]), then it is responsible for continuing to process the work until completion. This means that if the service implementing Async Delegate is unregistered for some reason, then the task must be properly cleaned up and succeed or fail as appropriate.
If the Async Service implementation used a service reference to obtain the service, then it must release the service object after the task has been accepted. This means that if the service object is provided by a service factory, then the service object should take extra care not to destroy its internal state when released. The service object must remain valid until all executing asynchronous tasks associated with the service object are either completed or failed.
If an Async Delegate implementation rejects an asynchronous task,
by returning false
or null
, the Async Service
implementation must take over the asynchronous invocation of the method.
In this case, if the Async Service implementation used a service
reference to obtain the service, the Async Service must not release the
service object until the asynchronous task is completed.
If an Async Delegate implementation throws an exception and the Async Service implementation used a service reference to obtain the service, then the service object must be released immediately.
Implementations of the Asynchronous Service specification must provide the following capabilities.
-
A capability in the
osgi.implementation
namespace declaring the implemented specification to beosgi.async
. This capability must also declare a uses constraint for theorg.osgi.service.async
andorg.osgi.service.async.delegate
packages. For example:Provide-Capability: osgi.implementation; osgi.implementation="osgi.async"; version:Version="1.0"; uses:="org.osgi.service.async,org.osgi.service.async.delegate"
This capability must follow the rules defined for the osgi.implementation Namespace.
-
A capability in the
osgi.service
namespace representing the Async service. This capability must also declare a uses constraint for theorg.osgi.service.async
package. For example:Provide-Capability: osgi.service; objectClass:List<String>="org.osgi.service.async.Async"; uses:="org.osgi.service.async"
This capability must follow the rules defined for the osgi.service Namespace.
Asynchronous Services implementations must be careful to avoid elevating the privileges of client bundles when calling services asynchronously, and also to avoid restricting the privileges of clients that are permitted to make a call. This means that the implementation must:
-
Be granted
AllPermission
. As the Async Service will always be on the stack when invoking a service object asynchronously it must be grantedAllPermission
so that it does not interfere with security any checks made by the service object. -
Establish the caller's
AccessControlContext
in a worker thread before starting to call the service object. This prevents a bundle from being able to call a service asynchronously that it would not normally be able to call. TheAccessControlContext
must be collected during any call to call(), call(R) or execute(). -
Use a
doPrivileged
block when mediating a concrete type. A no-args constructor in a concrete type may perform actions that the client may not have permission to perform. This should not prevent the client from mediating the object, as the client is not directly performing these actions. -
If the mediator object was created using a service reference, then the Async Services implementation must use the client's bundle context when retrieving the target service. If the service lookup occurs on a worker thread, then the lookup must use the
AccessControlContext
collected during the call to call(), call(R) or execute(). This prevents the client bundle from being able to make calls on a service object that they do not have permission to obtain, and ensures that an appropriately customized object is returned if the service is implemented using a service factory.
Further security considerations can be addressed using normal OSGi
security rules. For example access to the Async Service can be controlled
using ServicePermission[...Async, GET]
.
Asynchronous Services Package Version 1.0.
Bundles wishing to use this package must list the package in the Import-Package header of the bundle's manifest. This package has two types of users: the consumers that use the API in this package and the providers that implement the API in this package.
Example import for consumers using the API in this package:
Import-Package: org.osgi.service.async; version="[1.0,2.0)"
Example import for providers implementing the API in this package:
Import-Package: org.osgi.service.async; version="[1.0,1.1)"
-
Async
- The Asynchronous Execution Service.
The Asynchronous Execution Service. This can be used to make asynchronous invocations on OSGi services and objects through the use of a mediator object.
Typical usage:
Async async = ctx.getService(asyncRef);
ServiceReference<MyService> ref = ctx.getServiceReference(MyService.class);
MyService mediator = async.mediate(ref, MyService.class);
Promise<BigInteger> result = async.call(mediator.getSumOverAllValues());
The Promise API allows callbacks to be made when asynchronous tasks complete, and can be used to chain Promises.
Multiple asynchronous tasks can be started concurrently, and will run in parallel if the Async Service has threads available.
Consumers of this API must not implement this type
<R>
The return value of the mediated call, used for type information.
Invoke the last method call registered by a mediated object as an asynchronous task. The result of the task can be obtained using the returned Promise.
Typically the parameter for this method will be supplied inline like this:
ServiceReference<I> s = ...;
I i = async.mediate(s, I.class);
Promise<String> p = async.call(i.foo());
A Promise which can be used to retrieve the result of the asynchronous task.
Invoke the last method call registered by a mediated object as an asynchronous task. The result of the task can be obtained using the returned Promise.
Generally it is preferable to use call(Object) like this:
ServiceReference<I> s = ...;
I i = async.mediate(s, I.class);
Promise<String> p = async.call(i.foo());
However this pattern does not work for void methods. Void methods can therefore be handled like this:
ServiceReference<I> s = ...;
I i = async.mediate(s, I.class);
i.voidMethod()
Promise<?> p = async.call();
A Promise which can be used to retrieve the result of the asynchronous task.
Invoke the last method call registered by a mediated object as a "fire-and-forget" asynchronous task. This method should be used by clients in preference to call() and call(Object) when no callbacks, or other features of Promise, are needed.
The advantage of this method is that it allows for greater optimization of the underlying asynchronous task. Clients are therefore likely to see better performance when using this method compared to using call(Object) or call() and ignoring the returned Promise. The Promise returned by this method is different from the Promise returned by call(Object) or call(), in that the returned Promise will resolve when the fire-and-forget task is successfully started, or fail if the task cannot be started. Note that there is no happens-before relationship and the returned Promise may resolve before or after the fire-and-forget task starts, or completes.
Typically this method is used like call():
ServiceReference<I> s = ...;
I i = async.mediate(s, I.class);
i.someMethod()
Promise<Void> p = async.execute();
A Promise representing whether the fire-and-forget task was able to start.
<T>
The service object to mediate.
The type that the mediated object should provide.
Create a mediator for the specified object. The mediator is a generated object that registers the method calls made against it. The registered method calls can then be run asynchronously using either the call(Object), call(), or execute() method.
The values returned by method calls made on a mediated object are opaque and should not be interpreted.
Normal usage:
I s = ...;
I i = async.mediate(s, I.class);
Promise<String> p = async.call(i.foo());
A mediator for the service object.
IllegalArgumentException
– If the type represented by iface cannot
be mediated.
<T>
The service reference to mediate.
The type that the mediated object should provide.
Create a mediator for the specified service. The mediator is a generated object that registers the method calls made against it. The registered method calls can then be run asynchronously using either the call(Object), call(), or execute() method.
The values returned by method calls made on a mediated object are opaque and should not be interpreted.
This method differs from mediate(Object, Class) in that it can track the availability of the specified service. This is recommended as the preferred option for mediating OSGi services as asynchronous tasks may not start executing until some time after they are requested. Tracking the validity of the ServiceReference for the service ensures that these tasks do not proceed with an invalid object.
Normal usage:
ServiceReference<I> s = ...;
I i = async.mediate(s, I.class);
Promise<String> p = async.call(i.foo());
A mediator for the service object.
IllegalArgumentException
– If the type represented by iface cannot
be mediated.
Asynchronous Services Delegation Package Version 1.0.
Bundles wishing to use this package must list the package in the Import-Package header of the bundle's manifest. This package contains only interfaces that are implemented by consumers.
Example import for consumers using the API in this package:
Import-Package: org.osgi.service.async.delegate; version="[1.0,2.0)"
-
AsyncDelegate
- This interface is used by services to allow them to optimize Asynchronous calls where they are capable of executing more efficiently.
This interface is used by services to allow them to optimize Asynchronous calls where they are capable of executing more efficiently.
This may mean that the service has access to its own thread pool, or that it can delegate work to a remote node, or act in some other way to reduce the load on the Asynchronous Services implementation when making an asynchronous call.
The method to be asynchronously invoked.
The arguments to be used to invoke the method.
Invoke the specified method as an asynchronous task with the specified arguments.
This method can be used by clients, or the Async Service, to optimize Asynchronous execution of methods.
When called, this method should invoke the supplied method using the supplied arguments asynchronously, returning a Promise that can be used to access the result.
If the method cannot be executed asynchronously by this method then
null
must be returned.
A Promise representing the asynchronous result, or null
if this method cannot be asynchronously invoked.
Exception
– An exception should be thrown only if there was a
serious error that prevented the asynchronous task from starting.
For example, the specified method does not exist on this object.
Exceptions must not be thrown to indicate that the call does not
support asynchronous invocation. Instead this method must return
null
. Exceptions must also not be thrown to indicate a
failure from the execution of the underlying method. This must be
handled by failing the returned Promise.
The method to be asynchronously invoked.
The arguments to be used to invoke the method.
Invoke the specified method as a "fire-and-forget" asynchronous task with the specified arguments.
This method can be used by clients, or the Async Service, to optimize Asynchronous execution of methods.
When called, this method should invoke the specified method using the specified arguments asynchronously. This method differs from async(Method, Object[]) in that it does not return a Promise. This method therefore allows the implementation to perform more aggressive optimizations because the end result of the invocation does not need to be returned to the caller.
If the method cannot be executed asynchronously by this method then
false
must be returned.
true
if the asynchronous execution request has been
accepted, or false
if this method cannot be
asynchronously invoked by the AsyncDelegate.
Exception
– An exception should be thrown only if there was a
serious error that prevented the asynchronous task from starting.
For example, the specified method does not exist on this object.
Exceptions must not be thrown to indicate that the call does not
support asynchronous invocation. Instead this method must return
false
. Exceptions must also not be thrown to indicate a
failure from the execution of the underlying method.