;;; Unix waitt & process objects for scsh
;;; Copyright (c) 1993, 1994, 1995 by Olin Shivers.

;;; This is a GC'd abstraction for Unix process id's.
; ;; The problem with Unix pids is (a) they clutter up the kernel
;;; process table until you wait(2) them, and (b) you can only
;;; wait(2) them once. Scsh's process objects are similar, but
;;; allow the storage to be allocated in the scsh address space,
;;; and out of the kernel process table, and they can be waited on
;;; multiple times.

;;; Process objects
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-record proc		; A process object
  pid		; Proc's pid.
  (finished? #f)                ; Running, stopped, done
  (status (make-placeholder))	; The cached exit status of the process
  (zombie #t)                   ; Misnomer.  Whether or not the process has
                                ; (not) been waited on.
  ;; Make proc objects print like #{proc 2318}.
  ((disclose p) (list "proc" (proc:pid p) (proc:finished? p))))

;; Unfortunately there is no way to specify the name of the constructor-
;; function in Olins define-record macro, so I had to do this...
(define (make-procobj pid)
  (let ((procobj (make-proc pid)))
    (add-finalizer! procobj procobj-finalizer)
    procobj))


;; Weak pointer tables.  Much more efficient than populations.
;; Maps pids to processes.  Unexited processes are strong pointers, exited
;; procs are weak pointers (to allow gc'ing).
;; 
;; JMG: why ever unexited processes were strong pointer, this won't work 
;; with (autoreap-policy 'late), since then gc waits for the strong pointer
;; until it wait(2)s and the strong pointer waits for wait(2) which is 
;; nothing but a deadlock

(define process-table (make-integer-table))
(make-reinitializer (lambda ()
		      (set! process-table (make-integer-table))))

(define process-table-lock (make-lock))
(define (process-table-ref n)
  (with-lock process-table-lock
	     (lambda ()
	       (weak-table-ref process-table n))))

(define (process-table-set! n val)
  (with-lock process-table-lock
	     (lambda ()
	       (weak-table-set! process-table n val))))

(define (process-table-delete-procobj! procobj)
  (with-lock process-table-lock
	     (lambda ()
	       (if (eq? (weak-table-ref process-table (proc:pid procobj))
			procobj)
		   (weak-table-set! process-table (proc:pid procobj) #f)))))

(define (maybe-pid->proc pid)
  (process-table-ref pid))

(define (pid->proc pid . maybe-probe?)
  (let ((probe? (:optional maybe-probe? #f)))
    (or (maybe-pid->proc pid)
	(case probe?
	  ((#f)     (error "Pid has no corresponding process object" pid))
	  ((create) (new-child-proc pid))
	  (else     #f)))))
	     
;;; Coerce pids and procs to procs.

(define (->proc proc/pid)
  (cond ((proc? proc/pid) proc/pid)
	((and (integer? proc/pid) (>= proc/pid 0))
	 (pid->proc proc/pid 'create))
	(else (error "Illegal parameter" ->proc proc/pid))))


;;; Is X a pid or a proc?

(define (pid/proc? x) (or (proc? x) (and (integer? x) (>= x 0))))


;;; Process reaping
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; "Reaping" a process means using wait(2) to move its exit status from the
;;; kernel's process table into scsh, thus cleaning up the kernel's process
;;; table and saving the value in a gc'd data structure, where it can be
;;; referenced multiple times.
;;;
;;; - Stopped processes are never reaped, only dead ones.  (May change -df)
;;; 
;;; - Stopped process status codes are never cached in proc objects, 
;;;   only status codes for dead processes. So you can wait for a
;;;   dead process multiple times, but only once per process-stop.
;;;   (May change -df)
;;; 
;;; - Unfortunately, reaping a process loses the information specifying its
;;;   process group, so if a process is reaped into scsh, it cannot be
;;;   waited for by WAIT-PROCESS-GROUP. Notice that only dead processes are
;;;   reaped, not suspended ones. Programs almost never use WAIT-PROCESS-GROUP
;;;   to wait for dead processes, so this is not likely to be a problem. If
;;;   it is, turn autoreaping off with (autoreap-policy #f).
;;;   (This never worked right, and it might be wiped out completely -fd)
;;; 
;;; - Reaping can be encouraged by calling (REAP-ZOMBIES).

;;; (autoreap-policy [new-policy])
;;; Watch this area


;;; I'm really tired of opening everything (i.e. events) in scsh-level-0
;;; this is here until someone (Olin !!!) cleans up the scsh modules

(define next-sigevent (structure-ref sigevents next-sigevent))
(define most-recent-sigevent (structure-ref sigevents most-recent-sigevent))


(define *autoreap-policy* #f) ; Not exported from this module.

(define (autoreap-policy . maybe-policy)
  (let ((old-policy *autoreap-policy*))
    (if (pair? maybe-policy)
	(let ((new-policy (car maybe-policy)))
	  (cond ((pair? (cdr maybe-policy))
		 (error "Too many args to autoreap-policy" maybe-policy))
		((not (memq new-policy '(early late #f)))
		 (error "Illegal autoreap policy." new-policy))
		(else (set! *autoreap-policy* new-policy)
		      (cond ((eq? new-policy 'early)
			     (set-sigchld-handler! early-sigchld-handler)
			     (set-post/gc-handler! reap-need-reaping))
			    
			    ((eq? new-policy 'late)
			     (set-sigchld-handler! late-sigchld-handler)
			     (set-post/gc-handler! reap-need-reaping))

			    (else 
			     (set-sigchld-handler! noauto-sigchld-handler)
			     (set-post/gc-handler! 
			      (lambda ()
				#f))))))))
    old-policy))


;;; we don't register the post/gc-handler until the first police change
;;; --- this made sense, but why?
(define *post/gc-handler* 
  (lambda () (error "*post/gc-handler* was not defined")))

(define (really-set-post/gc-handler! handler)
   (set! *post/gc-handler* handler))

(define (start-set-post/gc-handler! handler)
  (set! set-post/gc-handler! really-set-post/gc-handler!)
  (set-post/gc-handler! handler)
  (spawn (lambda ()
	   (let lp ((event (most-recent-sigevent)))
	     (let ((next-event (next-sigevent event interrupt/post-gc)))
	       (*post/gc-handler*)
 	       (lp next-event))))
 	 '*post/gc-handler*-thread))

(define set-post/gc-handler! start-set-post/gc-handler!)


(define (*sigchld-handler*) (early-sigchld-handler))
(define (set-sigchld-handler! handler)
  (set! *sigchld-handler* handler))

(define (with-autoreaping thunk)
  (set! *autoreap-policy* 'early)
  (run-as-long-as
   (lambda ()
     (let lp ((event (most-recent-sigevent)))
       (let ((next-event (next-sigevent event interrupt/chld)))
	 (*sigchld-handler*)
	 (lp next-event))))
   thunk
   (structure-ref threads-internal spawn-on-root)
   'auto-reaping))

;;; This list contains pids whose proc-obj were gc'd before they died
;;; We try to reap them after every gc and maybe on every SIGCHLD
(define need-reaping '())

(define need-reaping-lock (make-lock))

(define (need-reaping-add! pid)
  (obtain-lock need-reaping-lock)
  (set! need-reaping (cons pid need-reaping))
  (release-lock need-reaping-lock))

(define (need-reaping-remove! pid)
  (obtain-lock need-reaping-lock)
  (set! need-reaping (delete pid need-reaping))
  (release-lock need-reaping-lock))

(define (reap-need-reaping)
  (obtain-lock need-reaping-lock)
  (set! need-reaping (filter (lambda (pid) (not (reap-pid pid))) need-reaping))
  (release-lock need-reaping-lock))

;;; reap this special pid
;;; return status or #f 
(define (reap-pid pid)
  (with-lock 
   wait-lock
   (lambda ()
     (let ((status (atomic-wait pid wait/poll)))
       (if status 
	   (waited-by-reap pid status))
       status))))
	
;;; Handler for SIGCHLD according policy
(define (late-sigchld-handler) #f)

(define (early-sigchld-handler)
  (reap-zombies))

(define (noauto-sigchld-handler) #f)


;;; Finalizer for procobjs
;;; 
(define (procobj-finalizer procobj)
  (process-table-delete-procobj! procobj)
  (if (not (proc:finished? procobj))
      (need-reaping-add! (proc:pid procobj))))


;;; (reap-zombies)  => bool
;;;   Move any zombies from the kernel process table into scsh.
;;;   Return true if no more outstanding children; #f if some still live.

(define (reap-zombies)
  (let lp ()
    (obtain-lock wait-lock)
    (receive (pid status)
      (%wait-any (bitwise-ior wait/poll wait/stopped-children))
      (if (and pid (not (status:stop-sig status)))
	  (begin (waited-by-reap pid status)
		 (release-lock wait-lock)
;		 (format (current-error-port)
;			 "Reaping ~d[~d]~%" pid status)
		 (lp))
	  (begin 
	    (release-lock wait-lock)
	    status)))))



(define (new-child-proc pid)
  (let ((proc (make-procobj pid)))
    (process-table-set! pid proc)
    proc))

;;; (WAIT proc/pid [flags])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; (wait proc/pid [flags]) => status or #f
;;;
;;; FLAGS (default 0) is the exclusive or of the following:
;;;     wait/poll	
;;;		Return #f immediately if there are no 
;;;		unwaited children available. 
;;; 	wait/stopped-children
;;; 		Report on suspended children as well.
;;;
;;;     If the process hasn't terminated (or suspended, if wait/stopped 
;;; 	is set) and wait/poll is set, return #f.
;;; (I'm working on the flags -df)

;;; JMG: We have to be careful about wait/poll and autoreap-policy:
;;; If it was 'late at anytime, we may missed the exit of pid/proc
;;; So we cannot just block and hope reap-zombies will give us the status


;;; With this lock, we ensure that only one thread may call
;;; really-wait for a given pid and manipulates the associated process object

(define wait-lock (make-lock))

(define (wait pid/proc . maybe-flags)
  (let* ((flags (:optional maybe-flags 0))
	 (proc (->proc pid/proc))
	 (win (lambda (status)
		(waited-by-wait proc status)
		status)))
    ;; save the event before we check for finished
    (let ((pre-event (most-recent-sigevent)))
      (with-lock 
       wait-lock
       (lambda () 
	 (cond ((atomic-wait proc (bitwise-ior flags wait/poll)) => win)
	       
	       ((zero? (bitwise-and flags wait/poll))
		;; we have to block and hence use the event system
		(let lp ((pre-event pre-event))
		  (cond ((atomic-wait proc (bitwise-ior flags wait/poll)) 
			 => win)
			(else
			 (release-lock wait-lock)
			 (let ((next-event (next-sigevent pre-event interrupt/chld)))
			   (obtain-lock wait-lock)
			   (lp next-event))))))
	       (else #f)))))))
		

;;; -> process-object proc status/#f
(define (atomic-wait proc flags)
  (cond ((proc:finished? proc)
	 (placeholder-value (proc:status proc)))
	(else (really-wait (proc:pid proc) (bitwise-ior flags wait/poll)))))
	   
;;; This one is used, to wait on a positive pid
;;; We NEVER do a blocking wait syscall 
(define (really-wait pid flags)
  (if (zero? (bitwise-and flags wait/poll))   
      (error "really-wait without wait/poll"))
  (if (< pid 1)
      (error "really-wait on nonpos pid" pid))
  (receive (return_pid status)
      (%wait-pid pid flags)
   (cond ((zero? return_pid) #f)      ; failed wait/poll
	 ((= pid return_pid) status)  ; made it
	 (else (error "mismatch in really-wait" 
		      return_pid pid)))))
  


;;; All you have to do, if pid was reaped
;;; proc_obj is maybe no longer alive
(define (waited-by-reap pid status)
  (cond ((maybe-pid->proc pid) =>
	 (lambda (proc) 
	   (obituary proc status)
	   (push-reaped-proc proc)
	   ))))


;;; All you have to do, if a wait on proc was successful
(define (waited-by-wait proc status)
  (if (not (status:stop-sig status))
      (begin
	(obituary proc status)
	(mark-proc-waited! proc))))

;;; we know from somewhere that proc is dead
(define (obituary proc status)
  (if (not (proc? proc))
      (error "obituary: proc was not a procobj" proc))
  (need-reaping-remove! (proc:pid proc))     ; in case it started during 'late
  (placeholder-set! (proc:status proc) status)
  (set-proc:finished? proc #t))



;;; (wait-any [flags]) => [proc status]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;     [#f #f] => non-blocking, none ready.
;;;     [#f #t] => no more.

(define (wait-any . maybe-flags)
  (let ((flags (:optional maybe-flags 0)))
    (if (zero? (bitwise-and flags wait/poll))
	(begin
	  (receive (pid status) 
	     ;; before we maybe block via placeholder-value
	     ;; do a really-wait-any for the ones, missed by 'late
	    (really-wait-any (bitwise-ior flags  wait/poll))
	    (if (not pid)
		(let ((win (get-reaped-proc!)))
		  (values win (placeholder-value (proc:status win))))
		(values pid status))))

	;; The rest of this is quite crude and can be safely ignored. -df
	;; JMG: wait-any is crude and so its implementation
	;; It got even worse, now that we have this fu*$#%g 'late
	(if (maybe-obtain-lock reaped-proc-pop-lock)
	    (if (eq? reaped-proc-head reaped-proc-tail)
		;;; due to 'late we cannot be sure, that they all have been
		;;; reaped
		(begin
		  (release-lock reaped-proc-pop-lock)
		  (really-wait-any flags))
		(let* ((retnode (placeholder-value reaped-proc-head))
		       (retval (weak-pointer-ref (reaped-proc:proc retnode))))
		  (set! reaped-proc-head (reaped-proc:next retnode))
		  (release-lock reaped-proc-pop-lock)
		  (if retval
		      (values retval (placeholder-value (proc:status retval)))
		      (values #f #f))))
	    (values #f #f)))))

(define (really-wait-any flags)
  (if (zero? (bitwise-and flags wait/poll))
      (error "real-wait-any without wait/poll" flags))
  (with-lock
   wait-lock
   (lambda ()
     (receive (pid status)
        (%wait-any flags)
	(if pid
	    (let ((proc (new-child-proc pid)))
	      (waited-by-wait proc status)
	      (values proc status))
	    (values #f #f))))))
      
	     
;;; (wait-process-group [proc-group flags]) => [proc status]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;     [#f #f] => non-blocking, none ready.
;;;     [#f #t] => no more.
;;;
;;; 
;;; If you are doing process-group waits, you do *not* want to use 
;;; early autoreaping, since the reaper loses process-group information.
;;; (I'm working on it -df)

(define (wait-process-group . args)
  (let-optionals args ((proc-group 0) (flags 0))
    (let ((proc-group (cond ((integer? proc-group) proc-group)
			     ((proc? proc-group)    (proc:pid proc-group))
			     (else (error "Illegal argument" wait-process-group
					  proc-group))))
	  (win (lambda (pid status)
		 (let ((proc (pid->proc pid 'create)))
		   (if proc (waited-by-wait proc status))
		   (values proc status)))))
      ;; save the event before we check for finished
      (let ((pre-event (most-recent-sigevent)))
	(receive (pid status) 
          (%wait-process-group proc-group (bitwise-ior flags wait/poll))
          (cond (pid
		 (win pid status))
		((zero? (bitwise-and flags wait/poll))
		 ;; we have to block and hence use the event system
		 (let lp ((pre-event pre-event))
		   (receive (pid status) 
		      (%wait-process-group proc-group (bitwise-ior flags wait/poll))
		      (if pid
			  (win pid status)
			  (lp (next-sigevent pre-event interrupt/chld))))))
		(else 
		 (values #f status))))))))




;;; (%wait-any flags) (%wait-pid pid flags) (%wait-process-group pgrp flags)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Direct interfaces to waitpid(2) call. As opposed to %wait-pid this
;;; waits on any child (using -1) and gets along if no child is alive
;;; at all (i.e. catches errno/child).

;;; [#f #f] means no processes ready on a non-blocking wait.
;;; [#f #t] means no waitable process on wait-any.

(define (%wait-any flags)
  (with-errno-handler 
   ((errno packet) 
    ((errno/child)
     (values #f #t)))
   (receive (pid status)
       (%wait-pid -1 flags)
    (if (zero? pid)
	(values #f #f)			; None ready.
	(values pid status)))))

(define (%wait-process-group pgrp flags)
  (if (zero? (bitwise-and flags wait/poll))   
      (error "really-wait without wait/poll"))
  (with-errno-handler 
   ((errno packet) 
    ((errno/child)
     (values #f #t)))
   (receive (pid status) 
	(%wait-pid (- pgrp) flags)
     (if (zero? pid) 
	 (values #f #f)			; None ready.
	 (values pid status)))))


;;; Reaped process table
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; We keep track of procs that have been reaped but not yet waited on by
;;; the user's code. These proces are eligible for return by WAIT-ANY.
;;; We keep track of these so that WAIT-ANY will hand them out exactly once.
;;; What this code needs is traditional condition variables.

;;; This is (so far) reliable in the following ways:
;;; 1. No process will be returned twice by wait-any, ever.  Even two different
;;;    wait-anys.
;;; 2. Being un-reaped will not prevent garbage collection.
;;;    (actually, there seems to be a problem with this -df)
;;; 3. If a process is waited on, or is gc'ed, wait-any will do the Right
;;;    Thing.

;;; And UNreliable in the following ways:
;;; 1. If a wait and a wait-any are blocking simultaneously, the wait will
;;;    always return the object.  However, whether the wait-any will or not
;;;    is based on racing semaphores.
;;; 2. While processes can still be garbage collected, the nodes on the
;;;    wait-any list will not, and if the program never wait-any's, the queue
;;;    will snake around, eating up memory like pac-man with the munchies.
;;; 3. The process may be garbage collected before wait-any gets to it, and
;;;    that's just tough.

;;; -df

(define-record reaped-proc
  proc
  (next (make-placeholder))
  prev)

(define reaped-proc-tail (make-reaped-proc (make-weak-pointer #f) 'head))
(define reaped-proc-head reaped-proc-tail)
(define reaped-proc-push-lock (make-lock))
(define reaped-proc-pop-lock (make-lock))          ;;; Zippy sez: pop lock!

(define (push-reaped-proc proc)
  (obtain-lock reaped-proc-push-lock)
  (let ((push-me (make-reaped-proc (make-weak-pointer proc) reaped-proc-tail)))
    (placeholder-set! (reaped-proc:next reaped-proc-tail) push-me)
    (add-finalizer! proc (make-reaped-proc-finalizer push-me))
    (set! reaped-proc-tail push-me))
  (release-lock reaped-proc-push-lock))

(define (make-reaped-proc-finalizer push-me)
  (lambda ignore 
    (remove-reaped-proc push-me)))

(define (remove-reaped-proc reaped-proc)
  (spawn (lambda ()                 ;This is blocking, so should run by itself
	   (set-reaped-proc:prev
	    (placeholder-value (reaped-proc:next reaped-proc))
	    (reaped-proc:prev reaped-proc))
	   (set-reaped-proc:next
	    (reaped-proc:prev reaped-proc)
	    (reaped-proc:next reaped-proc)))
  	 "reaped-proc-removing-thread"))

(define (pop-reaped-proc)
  (obtain-lock reaped-proc-pop-lock)        ;;; pop lock pop lock pop lock!
  (let ((pop-me (placeholder-value (reaped-proc:next reaped-proc-head))))
    (set! reaped-proc-head pop-me)
    (release-lock reaped-proc-pop-lock)
    (weak-pointer-ref (reaped-proc:proc pop-me))))



;;; Pop one off the list.
(define (get-reaped-proc!)
  (let loop ((try (pop-reaped-proc)))
    (if (and try (proc:zombie try))
	try
	(loop (pop-reaped-proc)))))

;;; PROC no longer eligible to be in the list. Delete it.
(define (mark-proc-waited! proc)
  (set-proc:zombie proc #f))
