command-level.scm 19 KB

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