[comp.sys.amiga] IPC: Safe Ports

pete@violet.berkeley.edu (03/10/88)

[First off, apologies (with much blushing) for duplicating ALL my messages
yesterday.  I thought the system was up to its old tricks and swallowing
all followup messages; then just as I finished "rectifying" the situation
they all magically popped up.  Gnunh!  Sorry again.]

[This new heading derives from discussion under "Re: IPC - a proposal"]


How about this for a hazard proof InterProcess Port?  It's based on Chuck
McManis' original suggestion <44626@sun.uucp>, with some further
simplifications.  I've put the necessary sequences into procedures to make
a clean interface.  I've also used Yet Another Structure (IPLink), which
just helps encapsulate things a bit more; it isn't strictly essential.


Server end of link:

The server program will use CreatePort() and DeletePort() as usual, but one
extra almost trivial operation is needed when it wants to close a port:

==================================================================

    void CutPort(port) struct Port *port;
    {
        RemPort(port);              /* Removes port from public list
                                       to prevent new processes from
                                       acquiring it */
        port->mp_Node.ln_Name = 0;  /* Mark port as invalid for IPLink
                                       -- all further messages blocked */
    }

==================================================================

No Forbid/Permits are necessary because neither of these operations
is hazardous.  (I ASSUME RemPort() is protected internally!)

After calling this procedure, the server can reply to any outstanding
messages on the port at its leisure, then call DeletePort() to get rid of
it completely.  Chuck's safe area is not needed because the port is now
isolated from new messages (provided they are sent through the IPLink
protocol below).

                        %%%%%%%%%%%%

Client end of link:

At the Client end we need to do a little more.  For a start we want a
structure defined to hold the port protection information:

==================================================================

    /* IPC.h */

    struct IPLink {
        struct  Port   *ipl_Port;   /* will get filled with port address */
        APTR            ipl_Key;    /* a copy of the port's name pointer */
        char           *ipl_Name;   /* the port's name (not strictly
                                       necessary, but may be useful) */
    };

==================================================================

And we provide these procedures:

==================================================================

    /*
     * Connect to a named public port in another process
     */

    struct * IPLink
    ConnectIPL(ipl, name) struct IPLink *ipl; char *name;
    {
        ipl->ipl_Port = ipl->ipl_Key = NULL /* for safety */
        if (name) /* if we don't provide name, one already in IPL is used */
            ipl->ipl_Name = name;
        Forbid();
        ipl->ipl_Port = FindPort(ipl->ipl_Name);
        if (ipl->ipl_Port)
            ipl->ipl_Key = ipl->ipl_Port->mp_Node.ln_Name;
        Permit();
        return (ipl->ipl_Port && ipl->ipl_Key) ? ipl : NULL;
                    /* as SUCCESS indicator,
                       but pointer could be convenient */
    }

    /*
     * Send message to connected port
     */

    PutIPL(ipl, msg) struct IPLink *ipl; struct Message *msg;
    {
        int res;
        msg->mn_Node.ln_Type = NT_MESSAGE; /* The standard protocol should
                                              be strictly obeyed (and server
                                              should change this to
                                              NT_REPLYMSG when replying) */
        Forbid();
        if (res = (ipl && ipl->ipl_Port &&
            ipl->ipl_Key == ipl_Port->mp_Node.ln_Name))
            PutMsg(ipl->ipl_Port, msg);
        Permit();
        return res;
    }

==================================================================

Usage should be fairly clear, I think.  The client declares storage for as
many IPLinks as it will need (which can be anywhere in its static data
space), and calls ConnectIPL(&myipl,"portname") for each.  (I put in the
option of storing a pointer to the desired name in the structure before
calling ConnectIPL(&myipl, NULL), if this happens to be more convenient.)

It then can send its messages freely via the PutIPL call.  If the port
happens to have been closed, the call will return FALSE; the client must
always look for this, naturally, or it may wait for a reply forever.

The client doesn't have to take any particular action when it wants to
terminate the conversation -- unless it has done something like give the
server permission to originate messages to the reply port, in which case it
would have to inform the server (and wait for confirmation!).

If we wanted, we could use this technique on a reply port too.  In this
case the name would usually be a PRIVATE one (though I can't see any reason
a reply port couldn't also be used as a public port).  The server would
store the key away in an IPLink, and reply through a ReplyIPL() procedure
analogous to PutIPL(), though it wouldn't use ConnectIPL().


As a general point, all messages sent though this channel should have the
mn_Node.ln_Type filled in appropriately (NT_MESSAGE or NT_REPLYMSG),
because if it isn't enforced now it won't be there when we suddenly need
it.  Also, I think there's a need for a further type -- NT_UNKNOWNMSG -- to
be returned instead of NT_REPLYMSG if the server can't handle it. (See the
ongoing discussion on message format.)


I had better admit that I haven't even compiled these code segments yet.
[How many typos can YOU spot?]  But aside from that, can anyone spot any
flaws in my logic? (aside from the exceedingly remote chance of a key value
being exactly duplicated in any data that happens to write over that slot.)


                                                -- Pete --

cmcmanis%pepper@Sun.COM (Chuck McManis) (03/11/88)

[Well, I like it :-) ]
In article <7574@agate.BERKELEY.EDU> ( Pete Goodeve ) writes:
> It then can send its messages freely via the PutIPL call.  If the port
> happens to have been closed, the call will return FALSE; the client must
> always look for this, naturally, or it may wait for a reply forever.

Well, on FALSE return it could fake the reply as well as return FALSE
but that may be a bit exotic. How about change the mn_Type to LONELY_MSG
or something to indicate the client attempted to send it to a closed port?

> If we wanted, we could use this technique on a reply port too.  In this
> case the name would usually be a PRIVATE one (though I can't see any reason
> a reply port couldn't also be used as a public port).  The server would
> store the key away in an IPLink, and reply through a ReplyIPL() procedure
> analogous to PutIPL(), though it wouldn't use ConnectIPL().

Things stay much simpler when you have distinct client/server relationships.
If you want bi-directional asynchrounous communication you should be required
to act as both a server and a client. If you are going to make these things
really asyncronous bidirectional links you might as well implement "streams"
right now no? Which brings up an interesting question, AT&T streams let you
'push' a 'module' onto a stream, essentially injecting itself into the 
message flow. Would such a capability be useful for our IPC links? It
means messages would flow through 'n' message ports but it would be nice
to put say a filter in the message stream that dumped messages that you
didn't want to deal with.


--Chuck McManis
uucp: {anywhere}!sun!cmcmanis   BIX: cmcmanis  ARPAnet: cmcmanis@sun.com
These opinions are my own and no one elses, but you knew that didn't you.