[Date Prev][Date Next][Thread Prev][Thread Next][Author Index][Date Index][Thread Index]
How to Restart an Object
- To: <xtech>
- Subject: How to Restart an Object
- From: Mark S. Miller <mark>
- Date: Sun, 24 Jun 90 00:32:36 PDT
Abstract
Because not all instance variables of a "COPY(Foo):" class are
necessarily saved and restored, some mechanism must be provided to
cause the rest to be re-initialized on object creation. We need a
hook so that an optional user provided restart method will be called
by the stubble-generated constructor. There was an old scheme for
"restart" which had problems both with needless extra complexity in
the translator, and with inconsistency in behavior between Smalltalk
and C++. The new scheme and its usage is described, as well as the
needed changes in "tofux.hxx", "Stubble.lexex", "StubbleForm.f", and
the translator. Finally, a further extension is described such that
restarts can smoothly subsume the SELF_COPY mechanism, which I suggest
we declare passe.
Apologies for explaining many things you already know. Writing mail
messages is a good way for me to generate documentation.
Introduction
Let's say we have a class Foo:
CLASS(Foo,Heaper) {
PUBLIC:
Foo ();
COPY(Foo):
IntegerVar i;
protected:
IntegerVar j;
};
Foo::Foo ()
{
i = 0;
j = 0;
}
The "COPY(Foo):" section instructs stubble that this is a "pass-by-
copy" class--meaning that when an instance which is a kind of this
class is sent over a transceiver, a representation of the instance
should be sent so that an equivalent instance may be reconstructed at
the other end. We currently have two cases: 1) A CommTransceiver,
where the other end is another address space (perhaps on another
machine) and the object is being sent as part of an argument or return
value in a remote procedure call (as in a FeBe connection). 2) A
SnarfTransceiver, where the object is being sent to the disk, and the
other end where the object is to be reconstructed is simply later in
time for this very backend (whether is has stayed up in the interim or
not).
Furthermore, the fact that "i" is in the "COPY(Foo):" section, and "j"
is not instructs stubble that "i" is the only part of the contents of
the instance which has to be transmitted to the other end. The
"COPY(Foo)" macro expands approximately to (leaving out garbage
collection and friends):
CLASS(Foo,Heaper) {
PUBLIC:
Foo ();
protected:
virtual void sendSelfTo (Transceiver * trans);
Foo (Transceiver * trans, TCSJ);
protected:
IntegerVar i;
protected:
IntegerVar j;
};
Foo::Foo ()
{
i = 0;
j = 0;
}
And the stubble generated code is approximately:
void Foo::sendSelfTo (Transceiver * trans)
{
trans->assignNumber (this);
trans->send (i);
}
Foo::Foo (Transceiver * trans, TCSJ)
{
trans->assignNumber (this);
trans->receive (cat_IntegerVar, sizeof i, &i);
}
(The assignNumber call is to be able to send and reconstruct correctly
structures containing multiple paths and loops. It can be ignored for
purposes of this discussion.)
The "sendSelfTo" method is used in sending a representation of this
object (by sending each of the instance variables in the COPY(Foo)
section), and the constructor is used to reconstruct an equivalent new
instance on the other side, by initializing each of these same
variable. Given that i's value is 7, our TextyCommTransceiver for
example will actually end up transmitting and then parsing
approximately the string:
Foo(7)
Notice though that the resulting reconstructed instance contains an
unintialized "j". Without some kind of restart mechanism, the only
choices are:
1) Make the object SELF_COPY. This instructs stubble not to
generate the sendSelfTo and constructor methods so that you can write
them yourself. In the case where most of the instance variables are
like "i", and only a few are like "j", this would seem to be a pain.
2) Send all the instance variables, which is frequently
innapropriate. The particular innapropriateness in our code which is
clearest is an object that contains a local Sema4 so that it may
engage in mutual exclusion on certain operations (using the CRITICAL
macro). It would be useless, and probably either impossible or
dangerous, to try to transmit this Sema4 to the other end with the
object.
What the restart mechanism provides for is a third alternative (-:
3) Have the normal instance variable that want to be sent in the
normal default way be handled automagically by stubble as always, but
in addition hand-write a routine for re-initializing those instance
variables you want to treat specially in whatever way you want.
Arrange for the stubble generated constructor to end in a call to this
user written routine.
The Proposed Restart Mechanism
With this new mechanism, we write:
CLASS(Foo,Heaper) {
PUBLIC:
Foo ();
COPY(Foo):
IntegerVar i;
RECEIVER:
LEAF void restartFoo (Transceiver * trans = NULL);
protected:
IntegerVar j;
};
Foo::Foo ()
{
i = 0;
this->restartFoo();
}
void Foo::restartFoo (Transceiver * /* trans = NULL */)
{
j = 0;
}
And we'd get:
CLASS(Foo,Heaper) {
PUBLIC:
Foo ();
protected:
virtual void sendSelfTo (Transceiver * trans);
Foo (Transceiver * trans, TCSJ);
protected:
IntegerVar i;
private:
LEAF void restartFoo (Transceiver * trans = NULL);
protected:
IntegerVar j;
};
// ... (same as before)
Foo::Foo (Transceiver * trans, TCSJ)
{
trans->assignNumber (this);
trans->receive (cat_IntegerVar, sizeof i, &i);
this->restartFoo (trans);
}
Stubble is here keying off of the new PROTECTION_TYPE "RECEIVER:", not
the name of the restart routine (as we used to do). The above is also
representative of the style I find useful and recommend: That "j" get
initialized only by the restartFoo routine, i.e., that the user
written constructor also delegate that part of its initialization to
restartFoo (except of course when you actually want to do something
different). Also, I've adopted the convention that the restart
routine for class X be called "restartX". The stubble generated
constructor will always try to pass it "trans", but the restart
routine will typically want to ignore it, so I make the argument
optional and invoke it with no arguments from the user written
constructor.
Passing the "trans" Argument, and Subsuming SELF_COPY
Passing the "trans" argument is also new. Even though I've found no
interesting use for it yet, it seems the right modularity. It enables
a restart method that wants to do something special given a particular
transceiver that it was designed in concert with to check for that
kind of transceiver and, in that case, invoke its special protocol:
void Foo::restartFoo (Transceiver * trans)
{
CHOOSE1(FooTransceiver,t,trans, {
j = t->specialMessageForFooTransceivers();
return;
});
j = 0;
}
This mechanism also subsumes the receiving side of SELF_COPY in a
smooth fashion. The restart method can receive new values from trans
in whatever way it wants to. To get the full effect of SELF_COPY, we
would simply declare an empty COPY section (no instance variables),
and have our restart method do all the receiving. All that we need is
an analogous means on the sending side (to send the data that our
restart method will be receiving). Consider:
CLASS(Foo,Heaper) {
PUBLIC:
Foo ();
COPY(Foo):
IntegerVar i;
SENDER:
LEAF void sendFoo (Transceiver * trans);
RECEIVER:
LEAF void receiveFoo (Transceiver * trans);
protected:
IntegerVar j;
};
Foo::Foo ()
{
i = 0;
j = 0;
}
void Foo::sendFoo (Transceiver * trans)
{
// ... some ideosyncratic way of receiving j from trans
}
void Foo::receiveFoo (Transceiver * trans)
{
// ... some ideosyncratic way of sending j to trans
}
We get:
CLASS(Foo,Heaper) {
PUBLIC:
Foo ();
protected:
virtual void sendSelfTo (Transceiver * trans);
Foo (Transceiver * trans, TCSJ);
protected:
IntegerVar i;
private:
LEAF void sendFoo (Transceiver * trans);
private:
LEAF void receiveFoo (Transceiver * trans);
protected:
IntegerVar j;
};
// ... (same as before)
void Foo::sendSelfTo (Transceiver * trans)
{
trans->assignNumber (this);
trans->send (i);
this->sendFoo (trans);
}
Foo::Foo (Transceiver * trans, TCSJ)
{
trans->assignNumber (this);
trans->receive (cat_IntegerVar, sizeof i, &i);
this->receiveFoo (trans);
}
Right now SELF_COPY is only used in IntegerVar, so this change should
be no big deal (I've already changed my copy of IntegerVar). Anyone
object to SELF_COPY becoming passe? Note that receiveFoo is
operationally the same kind of beast that we've previously named
restartFoo. We've changed the names to emphasize the difference in
styles of use.
COPY Class Inheritance and Restarts
What happens when one class with a restart method is a subclass of
another with a restart method?
CLASS(Foo,Heaper) {
PUBLIC:
Foo ();
COPY(Foo):
IntegerVar i;
/* send and receive j */
SENDER:
LEAF void sendFoo (Transceiver * trans);
RECEIVER:
LEAF void receiveFoo (Transceiver * trans);
protected:
IntegerVar j;
};
CLASS(Bar,Foo) {
PUBLIC:
Bar ();
COPY(Bar):
IntegerVar k;
/* send and receive l */
SENDER:
LEAF void sendBar (Transceiver * trans);
RECEIVER:
LEAF void receiveBar (Transceiver * trans);
protected:
IntegerVar l;
};
In this case, the order in which the variables will be transferred is
the natural one of: i, j, k, l. Some curious facts to notice: When
sending i and j, the instance has the behavior of a Bar (being fully
constructed), but while receiving i and j, the instance has the
behavior of a Foo (being only partially constructed). Before, when
restart methods had to be named exactly "restart()", we actually had
the correct behavior in C++ (since while we were in Foo's constructor,
"restart()" would be bound to Foo's restart). However, we would have
gotten quite different and incorrect behavior in Smalltalk. In
Smalltalk, as soon as a new Bar is created, it immediately gets the
equivalent of Bar's vtable before we've done any construction. This
is why I like to handMangle the class names into the restart, send,
and receive method names.
C++ Level Implementation
In tofux.hxx, within a #ifndef STUBBLE, we add:
#define SENDER private
#define RECEIVER private
To the PROTECTION_TYPE section of Stubble.lexex we add:
:SENDER
:RECEIVER
To StubbleForm.f, we add to the end of sendSelfTo (right before the '}'):
$( FOR FUNCS pro: SENDER)
this->$(FUNC) (trans);
$( ROF)
and to the end of the constructor (again before the '}'):
$( FOR FUNCS pro: RECEIVER)
this->$(FUNC) (trans);
$( ROF)
As these are not in Smalltalk, our normal merging procedure won't get
these in. Mr. Hill and Wjr: can you guys add these to your files so
they'll become part of the next release? They should be completely
upwards compatable. Thanks.
Smalltalk Translator Implementation
A curious thing happened here. I noticed that if there was a
"restart" method, it's declaration would be suppressed but not its
definition. This was apparently a start at implementing translator
support for the previous "restart" scheme (where a method named
"restart" would appear in the COPY section). It was hard to track
down where this was being checked for, but Heh finally found it for me
in ClassDescription>>fileOutCxxMethod:on:and:notifying:. This special
case has been removed.
ClassDescription>>cxxVisibility: is where we define which Smalltalk
method category prefixes become C++ protection categories (like
private and PROXY). SENDER and RECEIVER have been added here. I then
simply put my restart methods in method categories that begin with
"receiver". Wrt the default argument of NULL, I just use the standard
pseudo-overloading trick we've been practicing in Smalltalk.
This does bring up an interesting issue: It is not strictly upwards
compatible to add a new reserved keyword to a system, as Bjarne has
found out. I suspect we will continue to extend cxxVisibility: as we
come up with new macro and formic hacks. How can we do so safely if
there may be normal method categories which begin with what we now
want to consider a keyword? I suggest that prefixes must end in a
colon, such as 'smalltalk: only' or 'private: accessing'. We can do a
one time batch conversion, and then have cxxVisibility: warn of method
categories that contain a colon but are not recognized. This is a
minor issue, but should be done before opening up our Smalltalk
environment to third party developers (yes, I know that'll probably be
a long time anyway).
A minor translation issue: How does one cause the translator to
generate an empty COPY section (one with no instance variables)? We
will certainly need to. (I expect the SELF_COPY style of use to be a
major lever for us during performance engineering phase.)
Acknowledgements
The original idea for restart() was borrowed/adapted from the Argus
language by Barbara Liskov et al. (See her article in the Ecology of
Computation)