|
|
|
Advertisement
|
How to use Terminatable
Here's what I wrote in the Service UI proposal about Terminatable:
Terminatableis an interface implemented by service objects that need to prepare themselves for garbage collection.Service objects may be pulled into Jini client programs that were written by programmers who did not have (or did not use) full prior knowledge about the interface of the service object. Eventually, such clients may wish to offer up such service objects to their garbage collector by releasing all references to the service object.
If a service object, during the course of its lifetime on the client, creates and holds onto no finite nonmemory resources (such as file handles or sockets) and starts no threads, the client can safely release all references to the service. The garbage collector will, at its leisure, reclaim the memory occupied by the service object[...] But if a service object opens a socket connection back to the host from which it came, or opens a file locally on the client, or fires off a thread, then the client can't just let go of all references to the service object. The client must first "terminate" the service -- tell the service to prepare itself for garbage collection.
Any service object that needs to be prepared for garbage collection for any reason should implement
Terminatable. Client programs, before they release the last reference to a service object, should check to see if it implementsTerminatable. If so, it should invoketerminateService()on the service object before releasing the last reference to the object.If a service object need not be prepared for garbage collection, it should not implement
Terminatable. Thus,Terminatableshould never be mentioned in Jini Service APIs.Terminatableshould be implemented by individual service object implementation classes, if their instances need to be prepared for garbage collection.To be client-friendly, service objects should not "start" (for example, fire off threads, allocate big chunks of memory, open a socket connection, and so forth) when they are deserialized into the client's address space. Services should in general use a lazy starting approach, in which they don't start until they are first used. If a client never uses a particular service object, then it won't ever start. This in turn implies that if a client never used a
Terminatableservice object, then that client wouldn't actually need to invoketerminateService()on the object before releasing the last reference to the object. However, given the possibility that some service objects may decide to ignore the rules of good citizenship and start when they are deserialized, client programs should always invoketerminateService()onTerminatableservice objects before releasing the last reference.
In a different part of the Service UI proposal, I described the behavior of
clients with regard to Terminatable:
If the client is finished with the service object, it can also release all references to the service object. Before releasing the last reference to the service object, however, a client should check to see whether the service object's class implementsnet.jini.service.Terminatable. If so, the client should invoketerminateServiceon the service object before releasing the last reference.Once
terminateService()has been invoked on aTerminatableservice, the service object should be considered defunct. [...] Thus, if a client wants to grant a user access to the same service again after invokingterminateService()on the service object, or even just use the service directly, the client program will have to retrieve a fresh copy of the service item from the lookup service. (Put another way,terminateService()should only be called on a service object just before releasing all references to the service object.)
Concerns about Terminatable
At the design review, I mentioned how a Jini client could ensure that a
network-mobile object released any nonmemory resources it held at the
client, before the client released the last reference to the object.
When I presented the Terminatable interface, the Jini
architects didn't like it. Their reaction didn't surprise me. I
brought up the issue primarily because I wanted to hear the architects'
discussion about it, a discussion I found quite interesting.
First, the Jini architects believed an interface such as
Terminatable didn't belong in the Service UI API, because
cleaning up nonmemory resources used by network-mobile objects wasn't a
problem specific to Jini Service UIs. In fact, as Jim Waldo said,
"Resource reclamation is not even a problem specific to Jini programs,
it's a general problem of Java programs." As a result, they concluded
the Jini Service API was not the proper place to address the
issue. Terminatable was given a thumbs down.
What I also found interesting, however, was that I heard no consensus on
another potential solution. Various ideas were tossed about, including
having finite nonmemory resources leased on the client side, or
extending the garbage collector's contract so that it would promise to
run if file handles or sockets ran out. But in the end, it was merely
decided that since the solution was unclear, we shouldn't attempt
to define something like Terminatable in the Service UI
API.
John McClain, a member of Sun's Jini team and participant in the ServiceUI design review, summarized this portion of the design review this way:
Resource recovery is a hard problem. To come up with a good
solution would require both many people-hours, much calendar time,
and probably require changes to software and specifications the Jini
team
does not control (for example, the Java spec and/or the JDK).
Providing a partial solution could well be worse than providing no
solution at all, especially since in many circumstances there are
existing
ways to avoid the problems Terminatable addresses.
Given a choice between holding up the service UI spec to
work up a solution (a task that may well take years), providing a
partial solution, and doing nothing, it seemed that discretion was the
better part of valor.
One concern I had about Terminatable that the Jini architects did not bring up was the
"defunct" state that invoking terminateService() could create. I mentioned the defunct state
in my description of the interface contract:
OnceterminateService()has been invoked on aTerminatableservice, the service object should be considered defunct.
My concern was that this defunct state could render invalid other contracts offered by other interfaces
and classes that make up the object. This behavior violates the principle that all the supertypes of an
object's class should be compatible with each other. For example, imagine you have an object whose
class implements Laughable and Toastable. Laughable declares
two methods, laugh() and lastLaugh(). Toastable declares one
method, toast(). You should be able to pass this object to a method that expects a
Toastable, and that code should be able to operate on the object knowing only about
Toastable.
Now imagine that invoking laugh() interfered with the object's ability to fulfill the
Toastable contract when toast() is invoked. And that invoking
lastLaugh() would enable the object to once again fulfill the Toastable contract.
In effect, invoking laugh() places the object in an "untoastable" state, and invoking
lastLaugh() places the object back into a "toastable" state. Given this behavior, code
that knows only about Toastable would only be able to use the object as a Toastable when the object happened to be in a "toastable" state. Another
way to look at this is: if the object isn't always toastable, its class really shouldn't
implement Toastable. Thus, Laughable and
Toastable aren't compatible interfaces,
and both shouldn't be implemented by the same class.
Now consider the following language
from my description of Terminatable's contract:
If a service object need not be prepared for garbage collection, it should not implementTerminatable. Thus,Terminatableshould never be mentioned in Jini Service APIs.Terminatableshould be implemented by individual service object implementation classes, if their instances need to be prepared for garbage collection.
Thus, terminateService() is a bit like the laugh() method of the previous example,
in that laugh() was not part of the contract of Toastable, but
laugh() nevertheless affected the object's ability to fulfill the Toastable contract.
As soon as you invoke terminateService on a Terminatable object, it can break
the contracts of all the other classes and interfaces that make up the object. Invoking
terminateService() puts an object in a "defunct" state, and
the rest of the object is guaranteed to work as advertised only if it's in the "nondefunct" state. In my
mind, this feature of Terminatable makes it incompatible with most other interfaces and
classes, so I was very uneasy about actually adopting Terminatable as the standard way to
deal with mobile objects holding onto finite nonmemory resources
at the client.
|
Sponsored Links
|