command-level.scm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. ; Part of Scheme 48 1.9. See file COPYING for notices and license.
  2. ; Authors: Richard Kelsey, Jonathan Rees, Mike Sperber
  3. ; Command levels for the command processor
  4. ;
  5. ; Each command level has its own threads and scheduling queues. Only one
  6. ; command level is running at any time. An exception stops the current
  7. ; level and all its threads.
  8. ;
  9. ; A command level also has the condition that caused the level to be pushed,
  10. ; if any.
  11. ;----------------------------------------------------------------
  12. ; Command levels
  13. (define-record-type command-level :command-level
  14. (really-make-command-level queue thread-counter dynamic-env
  15. levels throw terminated?
  16. condition menu menu-position value-stack
  17. paused threads)
  18. command-level?
  19. (queue command-level-queue) ; queue of runnable threads
  20. (thread-counter command-level-thread-counter) ; count of extant threads
  21. (dynamic-env command-level-dynamic-env) ; used for spawns
  22. (levels command-level-levels) ; levels above this one
  23. (throw command-level-throw) ; exit from this level
  24. (terminated? command-level-terminated? set-command-level-terminated?!)
  25. ; true if unwinds already run
  26. (condition command-level-condition) ; condition which caused this
  27. ; level to be pushed
  28. (menu command-level-menu set-command-level-menu!)
  29. (menu-position command-level-menu-position set-command-level-menu-position!)
  30. (value-stack command-level-value-stack set-command-level-value-stack!)
  31. (repl-thread command-level-repl-thread set-command-level-repl-thread!)
  32. ; thread running the REPL
  33. (paused command-level-paused-thread set-command-level-paused-thread!)
  34. ; thread that pushed next level
  35. (threads x-command-level-threads set-command-level-threads!))
  36. ; lazily generated list of this level's threads
  37. (define (make-command-level condition inspecting? dynamic-env levels throw)
  38. (let ((level (really-make-command-level (make-queue)
  39. (make-counter)
  40. dynamic-env
  41. levels
  42. throw
  43. #f ; not yet terminated
  44. condition
  45. #f ; no menu
  46. #f ; no menu position
  47. (if inspecting? ; value stack
  48. '()
  49. #f)
  50. #f ; paused thread
  51. #f))) ; undetermined thread list
  52. (if (user-session-script-mode? (user-session))
  53. (spawn-script-thread! level)
  54. (spawn-repl-thread! level))
  55. level))
  56. ; Add THUNK as a thread to LEVEL. The level is stored in the thread so
  57. ; that when it is rescheduled after blocking it can be put on the correct
  58. ; run queue.
  59. (define (spawn-on-command-level level thunk id)
  60. (let ((thread (make-thread thunk id)))
  61. (spawn-thread-on-command-level level thread)
  62. thread))
  63. (define (spawn-thread-on-command-level level thread)
  64. (set-thread-dynamic-env! thread (command-level-dynamic-env level))
  65. (set-thread-scheduler! thread (command-thread))
  66. (set-thread-data! thread level)
  67. (enqueue! (command-level-queue level) thread)
  68. (increment-counter! (command-level-thread-counter level)))
  69. ; Add a new REPL thread to LEVEL.
  70. (define (spawn-repl-thread! level)
  71. (let ((thread (spawn-on-command-level level
  72. (user-session-repl-thunk (user-session))
  73. 'command-loop)))
  74. (set-command-level-repl-thread! level thread)))
  75. ; Add a script thread to LEVEL
  76. (define (spawn-script-thread! level)
  77. (spawn-on-command-level level
  78. (let ((thunk
  79. (user-session-script-thunk (user-session))))
  80. (lambda ()
  81. (set-exit-status! (thunk))))
  82. 'script))
  83. ; Find all of the threads belonging to LEVEL. This may be expensive to call
  84. ; and may not return the correct value if LEVEL is currently running.
  85. (define (command-level-threads level)
  86. (cond ((and (x-command-level-threads level)
  87. (weak-pointer-ref (x-command-level-threads level)))
  88. => (lambda (x) x))
  89. ((= 1 (counter-value (command-level-thread-counter level)))
  90. (list (command-level-repl-thread level)))
  91. (else
  92. (exact-command-level-threads level))))
  93. ; Use this when you really have to know. It's still somewhat
  94. ; imprecise as we may get threads that are already dead, but at least
  95. ; it doesn't leave anything out.
  96. (define (exact-command-level-threads level)
  97. (let ((threads (all-threads)))
  98. (do ((i 0 (+ i 1))
  99. (es '() (let ((thread (vector-ref threads i)))
  100. (if (and (thread-continuation thread)
  101. (eq? level (thread-data thread)))
  102. (cons thread es)
  103. es))))
  104. ((= i (vector-length threads))
  105. (set-command-level-threads! level (make-weak-pointer es))
  106. es))))
  107. ;----------------------------------------------------------------
  108. ; Entry point
  109. ; Starting the command processor. This arranges for an interrupt if the heap
  110. ; begins to fill up or when a keyboard interrupts occurs, starts a new user
  111. ; session, runs an initial thunk and then pushes a command level.
  112. (define (start-command-levels resume-args context
  113. greeting-thunk start-thunk
  114. repl-thunk script-thunk
  115. condition inspector-state
  116. input-port output-port error-port)
  117. ;(debug-message "[Starting levels]")
  118. (notify-on-interrupts (current-thread))
  119. (let ((dynamic-env (get-dynamic-env))
  120. (session (make-user-session (current-thread)
  121. (or context (make-user-context))
  122. repl-thunk script-thunk
  123. input-port
  124. output-port
  125. error-port
  126. resume-args ; focus values
  127. #f ; exit status
  128. (and (pair? resume-args)
  129. (string=? (os-string->string (car resume-args)) "batch"))
  130. (and (pair? resume-args)
  131. (string=? (os-string->string (car resume-args)) "run-script")))))
  132. (with-handler command-levels-condition-handler
  133. (lambda ()
  134. (let-fluids $command-level-thread? #t
  135. $user-session session
  136. (lambda ()
  137. (with-translations (translations)
  138. (lambda ()
  139. (if (not (or (user-session-batch-mode? session)
  140. (user-session-script-mode? session)))
  141. (greeting-thunk))
  142. ;;(debug-message "[start-thunk]")
  143. (start-thunk)
  144. (let ((thunk (really-push-command-level condition
  145. inspector-state
  146. dynamic-env
  147. '())))
  148. (ignore-further-interrupts)
  149. thunk)))))))))
  150. ; A fluid to tell us when we are in the command level thread (used to
  151. ; avoid sending upcalls to whomever is running us).
  152. (define $command-level-thread? (make-fluid #f))
  153. (define (on-command-level-thread?)
  154. (fluid $command-level-thread?))
  155. (define $user-session (make-fluid #f))
  156. ; If true exceptions cause a new command level to be pushed.
  157. (define push-command-levels?
  158. (user-context-accessor 'push-command-levels (lambda () #t)))
  159. (define set-push-command-levels?!
  160. (user-context-modifier 'push-command-levels))
  161. ; Have THREAD be sent an event when an interrupt occurs.
  162. (define (notify-on-interrupts thread)
  163. (set-interrupt-handler! (enum interrupt keyboard)
  164. (lambda stuff
  165. (schedule-event thread
  166. (enum event-type interrupt)
  167. (enum interrupt keyboard))))
  168. (call-before-heap-overflow!
  169. (lambda stuff
  170. (schedule-event thread
  171. (enum event-type interrupt)
  172. (enum interrupt post-major-gc))))
  173. (call-when-deadlocked!
  174. (lambda stuff
  175. (schedule-event thread
  176. (enum event-type deadlock)))))
  177. (define (ignore-further-interrupts)
  178. (set-interrupt-handler! (enum interrupt keyboard)
  179. (lambda stuff
  180. (signal-condition
  181. (condition
  182. (make-interrupt-condition (enum interrupt keyboard))
  183. (make-irritants-condition stuff)
  184. (make-who-condition 'ignore-further-interrupts)))))
  185. (call-before-heap-overflow! (lambda stuff #f))
  186. (call-when-deadlocked! #f))
  187. ; Handler for the command-levels thread. Warnings and notes are printed,
  188. ; errors cause an exit. This handler is used to catch errors before they
  189. ; go to the
  190. (define (command-levels-condition-handler c next-handler)
  191. (cond ((or (warning? c)
  192. (note? c))
  193. (force-output (current-output-port)) ; keep synchronous
  194. (display-condition c (current-error-port)
  195. (condition-writing-depth) (condition-writing-length))
  196. (unspecific)) ; proceed
  197. ((serious-condition? c)
  198. (force-output (current-output-port)) ; keep synchronous
  199. (display-condition c (current-error-port)
  200. (condition-writing-depth) (condition-writing-length))
  201. (scheme-exit-now 1))
  202. (else
  203. (next-handler))))
  204. ;----------------------------------------------------------------
  205. ; Grab the current continuation, then make a command level and run it.
  206. ;
  207. ; The double-paren around the CWCC is because it returns a continuation which
  208. ; is the thing to do after the command level exits.
  209. ;
  210. ; Should this detect the difference between normal termination and a throw
  211. ; out?
  212. (define (really-push-command-level condition inspecting? dynamic-env levels)
  213. ((call-with-current-continuation
  214. (lambda (throw)
  215. (let ((level (make-command-level condition
  216. inspecting?
  217. (preserve-interaction-env dynamic-env)
  218. levels
  219. throw)))
  220. (let-fluid $current-level level
  221. (lambda ()
  222. (dynamic-wind
  223. (lambda ()
  224. (if (command-level-terminated? level)
  225. (assertion-violation 'really-push-command-level
  226. "trying to throw back into a command level"
  227. level)))
  228. (lambda ()
  229. (run-command-level level #f))
  230. (lambda ()
  231. (if (command-level-terminated? level)
  232. (warning 'really-push-command-level
  233. "abandoning failed level-termination unwinds."
  234. level)
  235. (begin
  236. (set-command-level-terminated?! level #t)
  237. (terminate-level level))))))))))))
  238. ; Rebind the interaction environment so that side-effects to it are local
  239. ; to a command level.
  240. (define (preserve-interaction-env dynamic-env)
  241. (let ((old (get-dynamic-env)))
  242. (set-dynamic-env! dynamic-env)
  243. (let ((new (with-interaction-environment (interaction-environment)
  244. get-dynamic-env)))
  245. (set-dynamic-env! old)
  246. new)))
  247. ; Fluid to tell us what the current level is. This is only visible in the
  248. ; command-levels thread.
  249. (define $current-level (make-fluid #f))
  250. (define (terminate-level level)
  251. (let ((threads (exact-command-level-threads level))
  252. (*out?* #f))
  253. (for-each (lambda (thread)
  254. (if (thread-continuation thread)
  255. (terminate-level-thread thread level)))
  256. threads)
  257. (dynamic-wind
  258. (lambda ()
  259. (if *out?*
  260. (assertion-violation 'terminate-level
  261. "trying to throw back into a command level" level)))
  262. (lambda ()
  263. (run-command-level level (length threads)))
  264. (lambda ()
  265. (set! *out?* #t)
  266. (let ((levels (command-level-levels level)))
  267. (if (not (null? levels))
  268. (reset-command-input! (car levels))))))))
  269. ; Put the thread on the runnable queue if it is not already there and then
  270. ; terminate it. Termination removes the thread from any blocking queues
  271. ; and interrupts with a throw that will run any pending dynamic-winds.
  272. (define (terminate-level-thread thread level)
  273. (let ((queue (command-level-queue level)))
  274. (if (not (on-queue? queue thread))
  275. (enqueue! queue thread))
  276. (terminate-thread! thread)))
  277. (define (reset-command-input! level)
  278. (let ((repl (command-level-repl-thread level)))
  279. (if repl
  280. (interrupt-thread repl
  281. (lambda return-values
  282. (signal-condition the-reset-command-input-condition)
  283. (apply values return-values))))))
  284. (define-condition-type &reset-command-input &condition
  285. make-reset-command-input-condition
  286. reset-command-input-condition?)
  287. (define the-reset-command-input-condition
  288. (make-reset-command-input-condition))
  289. ; Make sure the input and output ports are available and then run the threads
  290. ; on LEVEL's queue.
  291. ; TERMINATE-COUNT is a number if we're terminating, indicating the
  292. ; exact number of threads that must still terminate. Note that the
  293. ; current value of the thread counter is not a good indication, as it
  294. ; includes threads that have died a quiet death by garbage collection:
  295. ; We'll never see them again, but if they were included in the count,
  296. ; the thread system would falsely detect deadlock.
  297. (define (run-command-level level terminate-count)
  298. (let ((counter (command-level-thread-counter level))
  299. (terminating? (and terminate-count #t)))
  300. (if terminating?
  301. (set-counter! counter terminate-count)
  302. (set-exit-status! #f))
  303. (run-threads
  304. (round-robin-event-handler (command-level-queue level)
  305. command-quantum
  306. (unspecific)
  307. counter
  308. (command-level-event-handler level terminating?)
  309. command-level-upcall-handler
  310. (command-level-wait level terminating?)))))
  311. ; The number of milliseconds per timeslice in the command interpreter
  312. ; scheduler. Should be elsewhere?
  313. (define command-quantum 200)
  314. ; Handling events.
  315. ; SPAWNED and RUNNABLE events require putting the job on the correct queue.
  316. ; A keyboard interrupt exits when in batch mode and pushes a new command
  317. ; level otherwise.
  318. (define (command-level-event-handler level terminating?)
  319. (let ((levels (cons level (command-level-levels level))))
  320. (lambda (event args)
  321. (enum-case event-type event
  322. ((spawned)
  323. (spawn-thread-on-command-level level (car args))
  324. #t)
  325. ((runnable)
  326. (let* ((thread (car args))
  327. (level (thread-data thread)))
  328. (cond ((not (command-level? level))
  329. (assertion-violation
  330. 'command-level-event-handler
  331. "non-command-level thread restarted on a command level"
  332. level thread))
  333. ((memq level levels)
  334. (enqueue! (command-level-queue level) thread))
  335. (else
  336. (warning 'command-level-event-handler
  337. "dropping thread from exited command level"
  338. level thread)))
  339. #t))
  340. ((interrupt)
  341. (if terminating?
  342. (warning 'command-level-event-handler
  343. "Interrupted while unwinding terminated level's threads."
  344. level))
  345. (let ((int (car args)))
  346. (quit-or-push-level
  347. (condition
  348. (make-message-condition
  349. (enum-case interrupt int
  350. ((keyboard) "keyboard interrupt")
  351. ((post-major-gc) "insufficient memory after major GC")
  352. (else "interrupt")))
  353. (make-interrupt-condition int)
  354. (make-who-condition 'command-level-event-handler)
  355. (make-irritants-condition
  356. (list
  357. (enumerand->name int interrupt))))
  358. levels))
  359. #t)
  360. ((deadlock)
  361. (if terminating?
  362. (warning 'command-level-event-handler
  363. "Deadlocked while unwinding terminated level's threads."
  364. level))
  365. (quit-or-push-level (make-deadlock-condition) levels)
  366. #t)
  367. (else
  368. #f)))))
  369. (define (quit-or-push-level condition levels)
  370. (if (batch-mode?)
  371. ((command-level-throw (last levels)) (lambda () (lambda () 0)))
  372. (really-push-command-level condition
  373. #f
  374. (command-level-dynamic-env (car levels))
  375. levels)))
  376. ; Wait for events if there are blocked threads, otherwise add a new REPL
  377. ; thread if we aren't on the way out.
  378. (define (command-level-wait level terminating?)
  379. (lambda ()
  380. (cond ((positive? (counter-value (command-level-thread-counter level)))
  381. (wait-for-event)
  382. #t)
  383. (terminating?
  384. #f)
  385. ((exit-status)
  386. (exit-levels level (exit-status)))
  387. (else
  388. (warning 'command-level-wait
  389. "command interpreter has died; restarting" level)
  390. (spawn-repl-thread! level)
  391. #t))))
  392. ; Leave the command-level system with STATUS.
  393. (define (exit-levels level status)
  394. (let ((top-level (last (cons level (command-level-levels level)))))
  395. ((command-level-throw top-level)
  396. (lambda () (lambda () status)))))
  397. ;----------------------------------------------------------------
  398. ; Upcalls
  399. ; The tokens are records which have contain the upcall procedure.
  400. (define command-level-upcall-handler
  401. (lambda (thread token args)
  402. (if (upcall? token)
  403. (apply (upcall-procedure token) args)
  404. (begin
  405. (propogate-upcall thread token args)))))
  406. (define-record-type upcall :upcall
  407. (make-upcall procedure id)
  408. upcall?
  409. (procedure upcall-procedure)
  410. (id upcall-id))
  411. (define-record-discloser :upcall
  412. (lambda (upcall)
  413. (list 'upcall-token (upcall-id upcall))))
  414. ; If we are already in the command-level thread we just make the call;
  415. ; if not, we have to actually do the upcall.
  416. (define-syntax define-upcall
  417. (syntax-rules ()
  418. ((define-upcall (id args ...) . body)
  419. (define id
  420. (let ((token (make-upcall (lambda (args ...) . body)
  421. 'id)))
  422. (lambda (args ...)
  423. (if (on-command-level-thread?)
  424. ((upcall-procedure token) args ...)
  425. (upcall token args ...))))))))
  426. ;----------------
  427. ; The current command level and friends
  428. ; Return the current command level.
  429. (define-upcall (command-level)
  430. (fluid $current-level))
  431. ; Return the current list of command levels.
  432. (define (command-levels)
  433. (let ((current-level (command-level)))
  434. (cons current-level
  435. (command-level-levels current-level))))
  436. ; Top-most command level.
  437. (define (top-command-level)
  438. (last (command-levels)))
  439. ;----------------
  440. ; Menus and the value stack.
  441. (define (maybe-menu)
  442. (command-level-menu (command-level)))
  443. (define (set-menu! value)
  444. (set-command-level-menu! (command-level) value))
  445. (define (menu-position)
  446. (command-level-menu-position (command-level)))
  447. (define (set-menu-position! value)
  448. (set-command-level-menu-position! (command-level) value))
  449. (define (value-stack)
  450. (command-level-value-stack (command-level)))
  451. (define (set-value-stack! value)
  452. (set-command-level-value-stack! (command-level) value))
  453. ;----------------
  454. ; User session
  455. (define-upcall (user-session)
  456. (fluid $user-session))
  457. ;----------------
  458. ; Command-level control
  459. (define-upcall (terminate-command-processor! status)
  460. (set-exit-status! status)
  461. (let* ((level (command-level))
  462. (repl-thread (command-level-repl-thread level)))
  463. (if repl-thread
  464. (begin
  465. (set-command-level-repl-thread! level #f)
  466. (terminate-thread! repl-thread)))))
  467. (define-upcall (push-command-level-upcall condition inspecting?
  468. thread dynamic-env)
  469. (set-command-level-paused-thread! (command-level) thread)
  470. (really-push-command-level condition
  471. inspecting?
  472. dynamic-env
  473. (command-levels)))
  474. ; Have to grab the current thread and dynamic environment before making the
  475. ; upcall.
  476. (define (push-command-level condition inspecting?)
  477. (push-command-level-upcall condition
  478. inspecting?
  479. (current-thread)
  480. (get-dynamic-env)))
  481. (define-upcall (throw-to-command-level level thunk)
  482. ((command-level-throw level) thunk))
  483. ; This makes a new level just like the old one.
  484. (define (restart-command-level level)
  485. (throw-to-command-level
  486. level
  487. (lambda ()
  488. (really-push-command-level (command-level-condition level)
  489. #f ; drop the old value stack
  490. (command-level-dynamic-env level)
  491. (command-level-levels level)))))
  492. ; Proceed with LEVEL causing RETURN-VALUES to be returned from the
  493. ; PUSH-COMMAND-LEVELS call that started LEVEL.
  494. (define (proceed-with-command-level level . return-values)
  495. (throw-to-command-level (level-pushed-from level)
  496. (lambda ()
  497. (apply values return-values))))
  498. ; Find the level that was pushed from LEVEL.
  499. (define (level-pushed-from level)
  500. (let loop ((levels (command-levels)))
  501. (cond ((null? (cdr levels))
  502. (assertion-violation 'level-pushed-from "level not found" level))
  503. ((eq? level (cadr levels))
  504. (car levels))
  505. (else
  506. (loop (cdr levels))))))
  507. ; Kill the thread on LEVEL that caused a new level to be pushed. This is
  508. ; used when the user wants to continue running the rest of LEVEL's threads.
  509. ; We enqueue the paused thread so that its dynamic-winds will be run.
  510. (define (kill-paused-thread! level)
  511. (let ((paused (command-level-paused-thread level)))
  512. (if paused
  513. (begin
  514. (if (eq? paused (command-level-repl-thread level))
  515. (spawn-repl-thread! level))
  516. (terminate-thread! paused) ; it's already running, so no enqueue
  517. (set-command-level-paused-thread! level #f))
  518. (warning 'kill-paused-thread! "level has no paused thread" level))))