This repository has been archived by the owner on Sep 25, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
jobs.lisp
252 lines (210 loc) · 8.66 KB
/
jobs.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
;;
;; jobs.lisp - Shell job objects
;;
(in-package :lish)
;; If we someday jettison being tied to a text only world, we can get rid of
;; the *job-counter* and job-id, which only exists so you can type it in text.
;; In that world, we could have the job object *in* the command line. We would
;; of course have to have some, presumablely interactive, way of putting the
;; object in there.
;; So, of course, I would like a portable atomic operations library, pretty
;; much like the one in SBCL. Does such a thing exist? Probably. But how am I
;; supposed to find it??? So instead you get another page from the cookbook of
;; failure.
(declaim (type fixnum *job-counter*))
#+sbcl (sb-ext:defglobal *job-counter* 0 "A counter for job identifier numbers.")
(eval-when (:compile-toplevel :load-toplevel :execute)
#+sbcl (defalias 'atomic-incf 'sb-ext:atomic-incf))
#-sbcl (defvar *job-counter* 0 "A counter for job identifier numbers.")
(eval-when (:compile-toplevel :load-toplevel :execute)
#-sbcl (defalias 'atomic-incf 'incf))
(deftype job-status ()
"A keyword indicating what's going on with a job."
'(member :running :stopped :suspended :dead))
(defclass job ()
((id
:initarg :id :accessor job-id
;;:initform (atomic-incf *job-counter*)
:type integer
:documentation "Still stuck in the text world.")
(name
:initarg :name :accessor job-name
:documentation "Some kind of name for the job.")
(command-line
:initarg :command-line :accessor job-command-line
:documentation "The command line that started it.")
(status
:initarg :status :accessor job-status :initform :running :type job-status
:documentation "Keyword indicating what's going on with the job."))
(:default-initargs
:id (atomic-incf *job-counter*))
(:documentation "A generic shell job."))
(defmethod print-object ((object job) stream)
"Print a job to STREAM."
(with-slots (id name status command-line) object
(print-unreadable-object (object stream :type t :identity t)
(format stream "~s ~s ~s ~s" id name status command-line))))
(defgeneric continue-job-in-foreground (job)
(:documentation "Continue a stopped job in the foreground."))
(defgeneric continue-job-in-background (job)
(:documentation "Continue a stopped job in the background."))
(defgeneric kill-job (job &key signal)
(:documentation "Destroy a job."))
(defgeneric list-all-jobs (type)
(:documentation "List all jobs of a type."))
(defgeneric check-job-status (shell type)
(:documentation "Check status of all jobs of a type."))
(defun job-p (object)
"Return true if OBJECT is a JOB."
(typep object 'job))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; System jobs
(defclass system-job (job)
((pid
:initarg :pid :accessor job-pid
:documentation "System process identifier.")
(process-group
:initarg :process-group :accessor job-process-group
:documentation "System process group.")
;; (process-handle-value ;; @@@ resolve this somehow
;; :initarg :process-handle-value :accessor job-process-handle-value
;; :documentation "Job process handle.")
)
(:documentation "An operating system job."))
(defmethod job-pid ((job job))
"Pretend non-system jobs have a NIL PID."
nil)
(defmethod continue-job-in-foreground ((job system-job))
#+unix
(multiple-value-bind (result status)
(os-unix::resume-background-pid (job-pid job))
(handle-job-change job result status)))
(defmethod continue-job-in-background ((job system-job))
#+unix
(progn
(uos::background-pid (job-pid job))
(setf (job-status job) :running)))
(defmethod kill-job ((job system-job) &key signal)
#+unix (os-unix:kill (job-pid job) (or signal uos:+SIGTERM+))
#+windows (funcall (caddr (find signal *siggy* :key #'second)) (job-pid job))
)
(defmethod list-all-jobs ((type (eql 'system-job)))
;; @@@ This is underwhelming. We probably should use the OS specific
;; system-procces-list, but of course that creates more problems.
(mapcar (_ (make-instance 'system-job
:id (os-process-id _)
:name (os-process-name _)
:command-line ""
:pid (os-process-id _)))
(nos:process-list)))
(defmethod check-job-status (shell (type (eql 'system-job)))
(let (job pid result status)
(loop :do
(multiple-value-setq (pid result status) (nos:check-jobs))
:while pid
:do
(if (setf job (find pid (lish-jobs shell) :test #'eql :key #'job-pid))
(handle-job-change job result status)
(format t "Unknown job changed ~a~%" pid)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Lisp jobs
(defclass lisp-job (job)
((resume-function
:initarg :resume-function :accessor job-resume-function
:documentation "A function that resumes the job."))
(:documentation "A job which is a Lisp closure."))
(defmethod continue-job-in-foreground ((job lisp-job))
(when (job-resume-function job)
(setf (lish-jobs *shell*) (delete job (lish-jobs *shell*)))
(funcall (job-resume-function job))))
(defmethod continue-job-in-background ((job lisp-job))
(error "I don't know how to background a Lisp job yet."))
(defmethod kill-job ((job lisp-job) &key signal)
(declare (ignore signal))
(error "I don't know how to kill a Lisp job yet."))
(defmethod list-all-jobs ((type (eql 'lisp-job)))
;; @@@ To really do this we would have to somehow find all shell instances.
;; It would probably be cool to be able to resume a job from a diffent shell,
;; but we would probably have to add a terminal switching interface.
(remove-if (_ (not (typep _ 'lisp-job))) (lish-jobs *shell*)))
(defmethod check-job-status (shell (type (eql 'lisp-job)))
(declare (ignore shell))
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Thread jobs
(defclass thread-job (job)
((thread
:initarg :thread :accessor job-thread
:documentation "A thread object."))
(:documentation "A job which is a thread."))
(defmethod continue-job-in-foreground ((job thread-job))
(nos:join-thread (job-thread job))
)
(defmethod continue-job-in-background ((job thread-job))
;; @@@ If we created the the thread, we could have set up
;; a condition variable that in the interrupt handler we would
;; ask the thread to wait on, then we could use
;; condition-notify here to wake it up, effectively backgrounding it.
(error "Threads can't be backgrounded yet."))
(defmethod kill-job ((job thread-job) &key signal)
(declare (ignore signal))
(nos:destroy-thread (job-thread job)))
(defmethod list-all-jobs ((type (eql 'thread-job)))
;; @@@ This still has the id specification problem.
(when nos:*supports-threads-p*
(mapcar (_ (make-instance 'thread-job
:id nil
:name (nos:thread-name _)
:command-line ""
:thread _))
(nos:all-threads))))
(defmethod check-job-status (shell (type (eql 'thread-job)))
(loop :for j :in (lish-jobs shell)
:when (typep j 'thread-job) :do
(cond
((not (find (job-thread j) (nos:all-threads)))
(format t ";; Thread done ~a~%" (job-name j))
(delete-job j))
((not (nos:thread-alive-p (job-thread j)))
;; It's destroyed but it's still in all-threads ??
(setf (job-status j) :dead)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defparameter *job-types* '(system-job lisp-job thread-job)
"All the different job types.")
(defun job-type-name (job)
"Return a personized string for the type of JOB."
(if (not (typep job 'job))
"????"
(string-capitalize (remove-suffix (string (type-of job)) "-JOB"))))
(defun find-job (job-descriptor)
"Return a job given a descriptor."
(cond
;; Presumably this is a good guess.
((null job-descriptor)
(first (lish-jobs *shell*)))
((stringp job-descriptor)
;; strip off a leading percent
(when (and (not (zerop (length job-descriptor)))
(char= (char job-descriptor 0) #\%))
(setf job-descriptor (subseq job-descriptor 1)))
(or
(find job-descriptor (lish-jobs *shell*) :test #'equalp :key #'job-name)
(find job-descriptor (nos:all-threads)
:test #'equalp
:key #'nos:thread-name)
(and (setf job-descriptor (ignore-errors (parse-integer job-descriptor)))
(find job-descriptor (lish-jobs *shell*)
:test #'eql :key #'job-id))))
((numberp job-descriptor)
(find job-descriptor (lish-jobs *shell*) :test #'= :key #'job-id))
((symbolp job-descriptor)
(or (find (string job-descriptor) (lish-jobs *shell*) :test #'equalp
:key #'job-name)
(when (find-package :bt)
(find (string job-descriptor) (nos:all-threads)
:test #'equalp
:key #'nos:thread-name))))
(t
(find job-descriptor (lish-jobs *shell*) :test #'equalp
:key #'job-name))))
;; EOF