[chuck-users] Semaphore class: wait for timeout or signal, whichever comes first

Robert Poor rdpoor at gmail.com
Sun Nov 29 09:57:32 EST 2009


All:

Based on some ideas originally by Tom Lieber, here is a Semaphore  
class that makes it easy to wait for a specific time OR for an  
external signal, whichever comes first.  I've been using it  
extensively in my real-time performance code and would like to believe  
it's well debugged.  Presented here in case anyone else might find it  
useful.

Comments, questions welcome.

Best,

- Rob Poor

// semaphore.ck -- wait until a specific time arrives or an external
// signal is generated, whichever comes first.
//
// Sample Usage ===================================================
// class TickleSignal extends Object {}
// TickleSignal tickle_signal;
// Semaphore semaphore;
//
// repeat(10) {
//     now + 2::second => time test_end_time;
//     spork ~ tickler_proc();
//     semaphore.wait_for(1::second) @=> Object @ cause;
//     <<< "semaphore woke due to", cause.toString() >>>;
//     test_end_time => now;
// }
//
// fun void tickler_proc() {
//     Std.rand2f(0.0, 2.0)::second => now;
//     semaphore.signal(tickle_signal);
// }
// ================================================================
// Author: Robert Poor <rdpoor at gmail.com>, November 2009

// TimeoutArrived is used as the <cause> argument for signal() when
// the semaphore wakes due to timeout.  While it's not neccessary to
// define a custom class -- any object can be used as the <cause>
// argument to signal() -- a custom class has the nice property that
// .toString() will print out the class name.  See Sample Usage above
// to see how a custom class may be used as the argument to signal().
class TimeoutArrived extends Object {};

// ================================================================
// ================================================================
// The Semaphore class supports waiting for an external signal or a
// timeout.
public class Semaphore extends Event {

     // ================================================================
     // class constants

     // TIMEOUT_ARRIVED is a constant returned by wait() to indicate
     // that the wait() was terminated by timeout rather than by a user
     // signal.
     static TimeoutArrived @ TIMEOUT_ARRIVED; new TimeoutArrived @=>  
TIMEOUT_ARRIVED;

     // ================================================================
     // class methods

     // ChucK 1.2.1.3 exhibits scheduling bugs when passed time values
     // with non-zero fractional parts.  truncate_time() removes the
     // fractional part to work around the bug.  (See set_timeout(t)
     // for usage.)
     fun static time truncate_time(time t) { return t - (t % samp); }

     // ================================================================
     // instance variables

     Object @ _cause;
     time _timeout_time;

     // ================================================================
     // public instance methods

     // ================
     // wait() blocks until someone calls signal(cause).  It then
     // returns the cause argument.
     fun Object wait() {
         this => now;            // wait here for call to signal()
         return _cause;
     }

     // ================
     // signal(cause) signals the semaphore, allowing it to return from
     // a call to wait() with the given cause.
     fun Semaphore signal(Object cause) {
         cause @=> _cause;       // remember the cause of the signal
         clear_timeouts();       // inhibit pending timeouts
         this.broadcast();       // unblock the wait()
         me.yield();             // allow wait()'ing thread to run
         return this;
     }

     // ================
     // set_timeout(time) generates a call to signal(TIMEOUT_ARRIVED) in
     // the future.  If <time> has already passed, no timeout is set
     // and set_timeout() returns null.
     fun Semaphore set_timeout(dur duration) { return set_timeout(now  
+ duration); }
     fun Semaphore set_timeout(time t) {
         if ((truncate_time(t) => _timeout_time) <= now) return null;
         spork ~ _timeout_proc(t);
         me.yield();
         return this;
     }

     // ================
     // clear_timeouts() prevents any pending timeouts from generating
     // a signal().
     //
     // Implementation note: each call to set_timeout() spawns a new
     // _timeout_proc(), and once spawned, a proc cannot be killed or
     // interupted.  To inhibit subsequent calls to signal(), we set
     // _timeout_time to an "impossible" value (i.e. a time in the
     // past) to inhibit the generation of the signals -- see the
     // implementation of _timeout_proc() to see why this works.
     fun Semaphore clear_timeouts() {
         now - 1::second => _timeout_time;
         return this;
     }

     // ================
     // wait_for() and wait_until() combine a call to set_timeout()
     // followed by a wait(): they will block until the desired time
     // arrives OR until <this>.signal() is called from another thread,
     // whichever comes first.
     fun Object wait_for(dur duration) { return wait_until(now +  
duration); }
     fun Object wait_until(time t) {
         if (set_timeout(t) == null) {
             return TIMEOUT_ARRIVED; // don't wait() if time already  
passed
         } else {
             return this.wait();
         }
     }

     // ================================================================
     // private methods

     // _timeout_proc() is spawned in its own thread at each call to
     // set_timeout().  It blocks until the requested time, and if
     // the timeout is still current (i.e. now == _timeout_time), it
     // calls <this>.signal(TIMEOUT_ARRIVED) to unblock the wait().
     fun void _timeout_proc(time t) {
         t => now;                     // wait until the time arrives
         // Signal the Semaphore only if this timeout is current
         if (now == _timeout_time) this.signal(TIMEOUT_ARRIVED);
     }

}


More information about the chuck-users mailing list