#lang racket
;;; Example 6aa - Simple Accepts and Calls with Returned Value
;;; This example uses sequential lock/unlock accepts to implement the lock. This
;;; works, but is very fragile. In particular, if the entry calls to lock or
;;; unlock are out of order or otherwise unmatched, the lock will not perform
;;; as expected.

(require (planet williams/simulation/simulation))

;;; process (lock)
;;; entry (lock i) -> string?
;;;   i : exact-nonnegative-integer?
;;; entry (unlock) -> void?
;;; This process implements a lock with sequential lock/unlock accepts. The lock
;;; entry takes a single argument, which is the id for the p1 process using the
;;; lock, and returns a string with a message indicating that the lock has been
;;; acquired. The unlock entry unlocks the lock. There is no internal state.
(define-process (lock)
  (let loop ()
    (accept caller (lock i)
      (format "locked for p1(~a)" i))
    (accept caller (unlock))

;;; process (p1 i a-lock)
;;;   i : exact-nonnegative-integer?
;;;   a-lock : process?
;;; This process uses the lock to protect its critical region, which just does a
;;; random wait in this case. It prints the message returned from the lock call.
(define-process (p1 i a-lock)
  (printf "~a: process p1(~a) started.~n"
          (current-simulation-time) i)
  (let ((msg (call a-lock (lock i))))
    (printf "~a msg = ~s~n"
            (current-simulation-time) msg))
  (printf "~a: process p1(~a) acquired lock.~n"
          (current-simulation-time) i)
  (wait (random-flat 0.0 10.0))
  (printf "~a: process p1(~a) releasing lock.~n"
          (current-simulation-time) i)
  (call a-lock (unlock)))

;;; (main n) -> void?
;;;   n : exact-nonnegative-integer?
;;; Creates the lock process and schedules n p1 processes that use the lock.
(define (main n)
    (let ((a-lock (schedule #:now (lock))))
      (for ((i (in-range n)))
        (schedule #:at (random-flat 0.0 10.0) (p1 i a-lock)))

;;; Test with 10 p1 processes.
(main 10)