[comp.os.mach] summary: timeout on condition_wait

erich@kgw2.bwi.WEC.COM (Eric Hammond) (03/07/90)

My thanks go out to those who responded to my post requesting help
in implementing condition_wait()s with timeouts.  In alphabetical
order:

    Eric.Cooper@CS.CMU.EDU
    Richard.Draves@CS.CMU.EDU
    Richard H. E. Eiger <rheiger@ccng.waterloo.edu>
    Alessandro.Forin@SPICE.CS.CMU.EDU
    Arul A. Menezes <arul@Neon.Stanford.EDU>
    Michael_Young@transarc.com

A rough summary of the problem is as follows:

    I need to do a condition_wait() on a condition, but
    want that condition to be signalled after n seconds
    (if and only if it hasn't been signalled already).  Only
    one thread will ever be waiting on this condition,
    and only one thread will ever signal this condition.
    This condition and the waiting thread might dissappear
    after the condition is signalled.  There are multiple
    thread/condition pairs in the same task that need this
    capability.

Following is a list of possible solutions.  Incorporated into
these are summaries of suggestions I've received:

    Let thread A be a thread which wants to wait on the condition.
    Let thread T be a "timeout thread".

    1) Before thread A waits on the condition it forks a new
       thread T.  Thread T sleeps for n seconds then signals
       the condition.

       pro:
           - Simple.

       con:
           - Uses system resources (?): one thread created per
             condition.
           - In my application the condition might have been
             deallocated by the time thread T wakes up.
           - In my application thread A might have decided
             to do another condition wait with another timeout
             while thread T is still sleeping.  Then when thread
             T wakes up it will signal the condition before the
             timeout now in effect.

    2) Same as (1) but add a sequence number to each timeout.
       When thread T wakes up it checks to see if the sequence
       number is the same as when it was forked.  If so, it
       signals the condition.  If not, it simply exits.

        pro:
            - Solves third con in (1).
        
        con:
            - Still uses one thread per condition.
            - Still might signal a deallocated condition.

    3) Create one timer thread T (per task) which will handle
       all timeouts.  This thread will provide services
       by receiving requests on a port.  One request will be to
       start a condition wait with timeout.  Another will be to
       signal a condition.
       
       The timer thread will keep a queue of conditions being
       waited for in order of the time they will timeout.  (Implement
       this in the standard method with each node indicating its
       timeout time and the length of time between its timeout and
       the timeout of the next node on the queue.)  
       
       A timeout will then be added to the msg_receive() call
       corresponding to the timeout of the first condition wait in
       the queue.
       
       When a condition times out it will be signalled and removed
       from the queue.  The same will be done when a request to
       signal a condition is received on the message port.
       
       pro:
           - Uses only one thread per task.
           - Will never signal a deallocated condition.
           - Will never signal a condition after it has already
             been signalled.
       
       con:
           - More complex to implement.
    
    4) Same as (3), but why use condition variables at all?
       Have the condition wait implemented as a msg_rpc()
       and when the "condition" should be "signalled" simply
       reply to the rpc.
       
       pro:
           - Simpler than (3).
    
    5) Don't use another thread at all.  Implement conditions
       using Mach ports and messages.  Here is a correlation
       between the concepts in conditions and what should be
       used to implement them.
       
           condition_t        - port_name_t
           condition_alloc()  - port_allocate()
           condition_free()   - port_deallocate()
           condition_signal() - msg_send()
           condition_wait()   - msg_receive()     [ with timeout ]
       
       pro:
           - Fairly easy to implement.
           - No extra threads.
           - Solves all problems stated above.
       
       con:
           - When nobody is waiting on a real condition
             signalling it has no effect.  Using this
             scheme a message is placed on the port's queue.
       
       This con might be solved a couple of ways:
       
           a. Clear the queue before doing a msg_receive().
              (What about the queue filling up, though?)
       
           b. Since a msg_send() to PORT_NULL has no effect (?)
              store a copy of the name of the allocated port
              and msg_send() to this copy.  Between calls to
              msg_receive() set this copy to PORT_NULL.

And, of course, any of the above should be written in such a way that
the actual implementation is hidden from the user.

I'm going to be attacking the problem using (5) and (5b).  I am not
absolutely sure about the comment I made " a msg_send() to PORT_NULL
has no effect".  The worst I could see it doing would be to return
the "invalid port" error which could be ignored.  I'd like to hear
from someone who knows.

If anyone has further suggestions or comments about these or other
solutions, I am still interested in hearing them.

Eric.
-- 
Eric Hammond @ Xetron Corporation, Cincinnati, Ohio
erich@kgw2.bwi.wec.com

Richard.Draves@CS.CMU.EDU (03/10/90)

> Excerpts from netnews.comp.os.mach: 6-Mar-90 summary: timeout on
> conditi.. Eric Hammond@kgw2.bwi.WE (5567)

> I'm going to be attacking the problem using (5) and (5b).  I am not
> absolutely sure about the comment I made " a msg_send() to PORT_NULL
> has no effect".  The worst I could see it doing would be to return
> the "invalid port" error which could be ignored.  I'd like to hear
> from someone who knows.


Sending a message to PORT_NULL will result in a SEND_INVALID_PORT error.
 The msg_send will have no effect.  (This is not true of all msg-send
errors.  For example, if there is a problem somewhere in the body of a
message, and a previously parsed piece of the message sent out-of-line
memory with the deallocate option, then the memory will be deallocated
when the msg-send returns the error.)

If you plan to use msg_receive with a timeout, and you care about the
accuracy of the timeouts, there are some "features" you should be aware
of.  If a msg_receive is interrupted, say because of a signal, then the
system call is interrupted and restarted unless the RCV_INTERRUPT option
is used.  However, when the msg-receive is restarted, it resumes with
the original timeout.  So if you do a receive with a 10 second timeout,
and have a 1 second interval timer, the receive will never timeout
because it will restart every second with another full 10 second timeout.

If this is a problem for you, you should use the RCV_INTERRUPT option so
you regain control when the msg-receive is interrupted (with a
RCV_INTERUPTED return code), and then you can calculate the correct
timeout value for the retried msg-receive.

Rich