[comp.os.mach] threads, cthreads, and task resources

erich@kgw2.bwi.WEC.COM (Eric Hammond) (02/20/90)

In article <1126@mtxinu.UUCP> frk@mtxinu.UUCP (Frank Korzeniewski) writes:
> Fine, but what about other ports that the thread had recieve rights for.
> Don't these ports give the rights back to the owner/backup thread?
> If not, this seems to be a lack of appropriate cleanup by cthreads/mach.
[...]
> Again, special caseing the kernel port is ok, *IF* you correctly clean
> up other ports that have been created.  Leaving around these other
> ports would seem to eventually result in resource exhaustion.

In my response to this I am not flaming or putting down Frank.  There just
seems to be a common misconception about port rights and I would like to
try to clear things up a bit for Mach programmers.  I wish this had been
explicitly pointed out to me when I first started multi-thread programming
on the NeXT.

From the Mach documentation from CMU (9 Jan 90):

    "Acess rights to a port consist of the ability to send to, receive
     from, or own that port.  A task may hold just send rights or any
     combination of receive and ownership rights plus send rights.
     Threads within a task may only refer to ports to which that task
     has been given access.  When a new port is created within a task,
     that task is given all three access rights to that port."

    "Every task has its own port name space, used for port and port set
     names."

Mach port rights are owned by a task not a thread.  All threads in a task
have equal rights to all ports. (none, send, receive, ownership/backup).
All threads in a task also have the same name for any given port that
the task has rights to.

When a thread creates a new port all threads in that task have all rights
to that port.  However, not all threads in that task may know the name
of that port unless the creating thread communicates that name to the other
threads (via global memory).  If the creating thread dies, the task (and all
threads in it) still have rights to that port.  As Frank points out above,
this could indeed result in resource exhaustion if the programmer does not
take care.

[ Killing a thread that the cthread library thinks it is using may be bad. ]
> Other than efficiency, I see no justification for this statement. As
> long as the os cleans up appropriatly, there should be no problem.
> After all, that is what an os is for.  (:-)

I don't know the implementation of the cthreads library (which, by the
way, may vary from machine/os/compiler to same), but I'd like to
try to offer some justification for this statement.  When you do a call to
thread_terminate() you are terminating the actual low level thread that
the Mach kernel knows about.  This also will deallocate that thread's
kernel port (among other things).  The Mach kernel knows nothing about
what we refer to as "cthreads".  The cthread library is a user level
run-time library used to interface with the low-level thread primitives.

The cthread library associates several items with a Mach thread.  One of
these is an arbitrary pointer (cthread_set/get_data()).  Another is a string
name (cthread_set_name()).  In my implementation on the NeXT all this 
information is stored in a structure (see cthreads.h).  The type cthread_t
is a pointer to this structure.  Whenever a program makes a call on behalf
of a cthread this pointer is passed to the library routine.  The library
routine can use any of the entries in the structure, the most important
one being the name of the actual Mach thread. This is really the name
of the kernel port of the Mach thread.

If a program calls thread_terminate(cthread_thread(cthread_name)), this
performs a kernel function which terminates the Mach thread, and deallocates
the kernel port of the Mach thread.  However, the cthread library knows
nothing about this transaction; it still has a structure filled with 
information including the (no longer valid) name of a port.  Since we don't
know the implementation of the cthreads library it is best not to make
assumptions.  We should not, therefore, assume that the cthreads library
will reference this port name in the future.  This could cause
possibly serious effects as that port name may be reused by the Mach
kernel for another port (even another thread's kernel port).

It might also be helpful to note that we shouldn't just deallocate the
cthread structure by ourselves.  We don't know what other references
the cthreads library may have to that structure.  (In my implementation
it is part of a linked list.)

(my) conclusion: Don't kill a thread out from under the cthreads library
                 until we hear from a true Mach expert (someone with authority
                 who can state that cthread implementations are and will be
                 written to handle this).

> Please don't say that it is bad form to just exit on an error.
> It can be very difficult for an application to track all system
> resources that are being used, and then free them at termination
> to avoid possible later deadlock or other failures.  Sending
> your own notification is just one example of this.

I agree with you, Frank.  It is difficult to track all resources that
a thread uses and might have to free up at termination.  However, it
is necessary if the thread ever plans on terminating (on error condition
or otherwise) while other threads live on.  Most resources are automatically
freed upon task termination, and this is what we have been used to in
Un*x.  (The problem may be that we are using threads in a situation where
multiple tasks are called for.)

From the Mach documentation from CMU (9 Jan 90):

    "A task is an execution environment and is the basic unit of resource
     allocation."

    "A thread is the basic unit of execution.  It consists of all processor
     state (e.g. hardware registers) necessary for independent execution.  A
     thread executes in the virtual memory and port rights context of a single
     task."

    "Tasks contain the capabilities, namely the port rights, resource limits,
     and address space of a running entity.  Tasks perform no computation;
     they are a framework for running threads."

    "Threads contain the minimal processing state associated with a
     computation, e.g. a program counter, a stack pointer, and a set of
     registers."

(my) conclusion: If you are allocating ports, memory, or other resources
                 like this, you should keep track of them.  Again, this is
                 probably only necessary if your thread might terminate while
                 other threads continue, and is especially necessary if these
                 types of threads are repeatedly created in your task.

-- 
Eric Hammond
erich@kgw2.bwi.wec.com