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.comRichard.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