better timeout error message, doc revisions

svn: r865
This commit is contained in:
Matthew Flatt 2005-09-16 19:48:58 +00:00
parent 04dd9be199
commit 3c487a849b
4 changed files with 137 additions and 115 deletions

View File

@ -3,7 +3,7 @@
;; Also replace the "icon.png" and "server-cert.pem" files. ;; Also replace the "icon.png" and "server-cert.pem" files.
;; Instead of uncommenting the definition of server:port, you ;; Instead of uncommenting the definition of server:port, you
;; can set the PLT_HANDIN_SERVER_PORT environment variable. ;; can set the PLT_HANDIN_SERVER_PORT environment variable.
(define name "Course Handin") (define name "Course")
(define collection "handin-client") (define collection "handin-client")
;(define server:port "localhost:7979") ;(define server:port "localhost:7979")

View File

@ -257,7 +257,7 @@
(define status (define status
(new message% (new message%
[label (format "Manage ~a account at ~a." handin-name server)] [label (format "Manage ~a handin account at ~a." handin-name server)]
[parent this] [parent this]
[stretchable-width #t])) [stretchable-width #t]))
@ -515,7 +515,7 @@
(define/override (file-menu:between-open-and-revert file-menu) (define/override (file-menu:between-open-and-revert file-menu)
(new menu-item% (new menu-item%
(label (format "Manage ~a Account..." handin-name)) (label (format "Manage ~a Handin Account..." handin-name))
(parent file-menu) (parent file-menu)
(callback (lambda (m e) (manage-handin-account)))) (callback (lambda (m e) (manage-handin-account))))
(super file-menu:between-open-and-revert file-menu)) (super file-menu:between-open-and-revert file-menu))

View File

@ -21,7 +21,8 @@ password and upload the current content of the definitions and
interactions window to the course instructor's server. The "File" menu interactions window to the course instructor's server. The "File" menu
is also extended with a "Manage..." menu item for managing a handin is also extended with a "Manage..." menu item for managing a handin
account (i.e., changing the password, or creating a new account if the account (i.e., changing the password, or creating a new account if the
instructor configures the server to allow new accounts). instructor configures the server to allow new accounts). Students can
submit joint work by submitting with a concatenation of usernames.
On the instructor's side, the handin server can be configured to check On the instructor's side, the handin server can be configured to check
the student's submission before accepting it. the student's submission before accepting it.
@ -100,11 +101,11 @@ To customize the client:
* For `name', choose a name for the handin tool as it will * For `name', choose a name for the handin tool as it will
appear in DrScheme's interface (e.g., the "XXX" for the appear in DrScheme's interface (e.g., the "XXX" for the
"Manage XXX Account..." menu item). Again, make the name "Manage XXX Handin Account..." menu item). Again, make the
specific to the course, in case a student installs multiple name specific to the course, in case a student installs
handin tools. It's a good idea to use "Handin" as the last multiple handin tools. Do not use "Handin" as the last part
part of the name, as in "2010 Handin", since the button is of the name, since "Handin" is always added for button and
always named "Handin". menu names.
* For `collection', use the name that you chose for your * For `collection', use the name that you chose for your
collection directory (i.e., whatever you changed collection directory (i.e., whatever you changed
@ -196,9 +197,11 @@ sub-directories:
"BACKUP-1/handin.scm", etc.; the default is 9 "BACKUP-1/handin.scm", etc.; the default is 9
'user-regexp : a regular expression that is used to validate 'user-regexp : a regular expression that is used to validate
usernames, young students often choose exotic usernames that usernames; young students often choose exotic usernames that
are impossible to remember, and forget capitalization; the are impossible to remember, and forget capitalization, so the
default is fairly strict: #rx"^[a-z][a-z0-9]+$" default is fairly strict: #rx"^[a-z][a-z0-9]+$"; be sure to
disallow "+" in a username, since it is used in a submission
to specify joint work
'user-desc : a plain-words description of the acceptable 'user-desc : a plain-words description of the acceptable
username format (according to user-regexp above); #f stands username format (according to user-regexp above); #f stands
@ -207,27 +210,29 @@ sub-directories:
'username-case-sensitive? : a boolean; when #f, usernames 'username-case-sensitive? : a boolean; when #f, usernames
are case-folded for all purposes; defaults to #f are case-folded for all purposes; defaults to #f
(note that you should not set this to #t on Windows, since (note that you should not set this to #t on Windows or when
usernames are used as directory names) using a case-insensitive filesystem, since usernames are used
as directory names)
'id-regexp : a regular expression that is used to validate a 'id-regexp : a regular expression that is used to validate a
"free form" user id (possibly a student id) for a created "free form" user id (possibly a student id) for a created
account; the default is #rx"^.*$" account; the default is #rx"^.*$"
'id-desc : a plain-words description of the acceptable id format 'id-desc : a plain-words description of the acceptable id format
(according to id-regexp above), eg, "Foo ID Number"; the (according to id-regexp above), eg, "Utah ID Number with
default is #f indicating no description exactly nine digits"; the default is #f indicating no
description
'email-regexp : a regular expression that is used to validate 'email-regexp : a regular expression that is used to validate
emails, the #rx"^[^@<>\"`',]+@[a-zA-Z0-9_.-]+[.][a-zA-Z]+$" emails, the #rx"^[^@<>\"`',]+@[a-zA-Z0-9_.-]+[.][a-zA-Z]+$"
default can be changed to "" if you don't care about emails, default can be changed to "" if you don't care about emails,
or can be further restricted, for example requiring a or can be further restricted, for example requiring a
"@cs.foo.edu" suffix "@cs.utah.edu" suffix
'email-desc : a plain-words description of the acceptable email 'email-desc : a plain-words description of the acceptable email
format (according to email-regexp above), eg, "Foo CS email"; format (according to email-regexp above), eg, "Utah CS email
#f stands for no description; the default is "a valid email address"; #f stands for no description; the default is "a
address" valid email address"
'allow-new-users : a boolean indicating whether to allow 'allow-new-users : a boolean indicating whether to allow
new-user requests from a client tool; the default is #f new-user requests from a client tool; the default is #f
@ -278,20 +283,23 @@ sub-directories:
handins; the lowest numbered such directory represents the latest handins; the lowest numbered such directory represents the latest
handin. handin.
Within an "ATTEMPT" or "SUCCESS-n" directory, a file "handin.scm" A cleanup process in the server copies successful submission to
(or some other name if `default-file-name' is set) contains the the student directory -- one level up from the corresponding
actual submission. A `checker' procedure can change this default "SUCCESS-n" directory. This is done only for files and
file name, it can create additional files in an directories that are newer in "SUCCESS-n" than in the submission
"ATTEMPT"/"SUCCESS-n" directory or in the student directory; see root, other files and directories are left intact. If external
below on "checker.ss" for more details. tools add new content to the student directory (eg, a "grade"
file, as described below) it will stay there. If the machine
crashes or the server is stopped, the cleanup process might not
finish. When the server is started, it automatically runs the
cleanup process for each student directory.
A cleanup process will copy successful submission to the Within a student directory, a file "handin.scm" (or some other
submission root -- one level up from the corresponding "SUCCESS-n" name if `default-file-name' is set) contains the actual
directory. This is done only for files and directories that are submission. A `checker' procedure can change this default file
newer in "SUCCESS-n" than in the submission root, other files and name, and it can create additional files in an "ATTEMPT" directory
directories are left intact. This means that you can have (to be copied by the cleanup process); see below on "checker.ss"
external tools that add new content to the submission directory for more details.
(eg, a "grade" file as described below) and it will stay there.
For submissions from a normal DrScheme frame, a submission file For submissions from a normal DrScheme frame, a submission file
contains a copy of the student's definitions and interactions contains a copy of the student's definitions and interactions
@ -301,6 +309,14 @@ sub-directories:
parts, the file can be parsed with `unpack-submission' from parts, the file can be parsed with `unpack-submission' from
"utils.ss" (see below). "utils.ss" (see below).
To submit an assignment as a group, students use a concatenation
of usernames separated by "+" and any number of spaces (e.g.,
"user1+user2"). The same syntax ("user1+user2") is used for the
directory for shared submissions, and the usernames are always
sorted so that the directory name is deterinistic. Multiple
submissions for a particular user in different groups will be
rejected.
* "inactive/" --- sub-directory for inactive assignments, used by the * "inactive/" --- sub-directory for inactive assignments, used by the
HTTPS status web server. HTTPS status web server.
@ -312,16 +328,12 @@ sub-directories:
`checker' function can raise an exception; the exception message `checker' function can raise an exception; the exception message
will be relayed back to the student. will be relayed back to the student.
To submit an assignment as a group, students use "user1+user2" The first argument is a list of usernames to handle the case of a
etc. The checker function will receive a list of usernames in joint submission (where the submission username was a
this case -- this list is always sorted, so it can check that the concatenation of usernames separated by "+").
list of students are in a list of authorized teams. The same
syntax ("user1+user2") is used for the directory for shared
submissions; and the usernames are always sorted so the directory
name is deterinistic.
The `checker' function is called with the current directory as The `checker' function is called with the current directory as
"active/<assignment>/<user/s>/ATTEMPT", and the submission is "active/<assignment>/<username(s)>/ATTEMPT", and the submission is
saved in the file "handin". The checker function can change saved in the file "handin". The checker function can change
"handin", and it can create additional files in this directory. "handin", and it can create additional files in this directory.
(Extra files in the current directory will be preserved as it is (Extra files in the current directory will be preserved as it is
@ -352,10 +364,13 @@ sub-directories:
was returned by the checker (or the value of the was returned by the checker (or the value of the
`default-file-name' configuration option if there's no checker). `default-file-name' configuration option if there's no checker).
If the submission is from multiple users, then "<user>" is If the submission is from multiple users, then "<user>" is
actually "<user1>+<user2>" etc. actually "<user1>+<user2>" etc. Also, if the cleanup process was
interrupted (by a machine failure, etc.), the submission may
actually be in "SUCCESS-n" as described above.
* "[in]active/<assignment>/<user>/grade" (optional) --- <user>'s grade * "[in]active/<assignment>/<user>/grade" (optional) --- <user>'s
for <assignment>, to be reported by the HTTPS status web server. grade for <assignment>, to be reported by the HTTPS status web
server
* "[in]active/<assignment>/solution*" --- the solution to the * "[in]active/<assignment>/solution*" --- the solution to the
assignment, made available by the status server to any user who assignment, made available by the status server to any user who
@ -380,18 +395,18 @@ button for any network transaction. For handins, "cancel" is
guaranteed to work up to the point that the client sends a "commit" guaranteed to work up to the point that the client sends a "commit"
command; this command is sent only after the server is ready to record command; this command is sent only after the server is ready to record
the submission (having run it through the checker, if any), but before the submission (having run it through the checker, if any), but before
the server writes the "handin.scm" file. Also, the server responds to a renaming "ATTEMPT". Also, the server responds to a commit with "ok"
commit with "ok" only after it has written the file. Thus, when the only after it has written the file. Thus, when the client-side tool
client-side tool reports that the handin was successful, the report is reports that the handin was successful, the report is
reliable. Meanwhile, the tool can also report successful cancels most reliable. Meanwhile, the tool can also report successful cancels most
of the time. In the (normally brief) time between a commit and an "ok" of the time. In the (normally brief) time between a commit and an "ok"
response, the tool gives the student a suitable warning that the response, the tool gives the student a suitable warning that the
cancel is unreliable. cancel is unreliable.
To minimize human error, the number of active assignments should be To minimize human error, the number of active assignments should be
limited to 1 whenever possible. When multiple assignments are active, limited to one whenever possible. When multiple assignments are
design a checker to help ensure that the student has selected the active, design a checker to help ensure that the student has selected
correct assignment in the handin dialog. the correct assignment in the handin dialog.
A student can download his/her own submissions through a web server A student can download his/her own submissions through a web server
that runs concurrently with the handin server. The starting URL is that runs concurrently with the handin server. The starting URL is

View File

@ -54,7 +54,7 @@
(define orig-custodian (current-custodian)) (define orig-custodian (current-custodian))
;; On startup, check that the prefs file is not locked: ;; On startup, check that the users file is not locked:
(put-preferences null null (put-preferences null null
(lambda (f) (lambda (f)
(delete-file f) (delete-file f)
@ -84,51 +84,41 @@
(define SUCCESS-RE (regexp (format "^~a$" (success-dir "[0-9]+")))) (define SUCCESS-RE (regexp (format "^~a$" (success-dir "[0-9]+"))))
(define SUCCESS-GOOD (map success-dir '(0 1))) (define SUCCESS-GOOD (map success-dir '(0 1)))
(define-syntax careful-switch-directory-switch
(syntax-rules ()
[(_ ?dir body ...)
(let ([dir (with-handlers ([void (lambda _ #f)]) (normalize-path ?dir))])
(when (and dir (directory-exists? dir))
(parameterize ([current-directory (current-directory)])
(when (with-handlers ([void (lambda _ #f)])
(current-directory dir) #t)
body ...))))]))
(define (cleanup-submission-body) (define (cleanup-submission-body)
;; Find the newest SUCCESS dir -- ignore ATTEMPT, since if it exist it ;; Find the newest SUCCESS dir -- ignore ATTEMPT, since if it exist it
;; means that there was a failed submission and the next one will ;; means that there was a failed submission and the next one will
;; re-create ATTEMPT. ;; re-create ATTEMPT.
(let* ([dirlist (map path->string (directory-list))] (let* ([dirlist (map path->string (directory-list))]
[dir (quicksort [dir (quicksort
(filter (lambda (d) (filter (lambda (d)
(and (directory-exists? d) (and (directory-exists? d)
(regexp-match SUCCESS-RE d))) (regexp-match SUCCESS-RE d)))
dirlist) dirlist)
string<?)] string<?)]
[dir (and (pair? dir) (car dir))]) [dir (and (pair? dir) (car dir))])
(when dir (when dir
(unless (member dir SUCCESS-GOOD) (unless (member dir SUCCESS-GOOD)
(LOG "*** USING AN UNEXPECTED SUBMISSION DIRECTORY: ~a" (LOG "*** USING AN UNEXPECTED SUBMISSION DIRECTORY: ~a"
(build-path (current-directory) dir))) (build-path (current-directory) dir)))
;; We have a submission directory -- copy all newer things (extra ;; We have a submission directory -- copy all newer things (extra
;; things that exist in the main submission directory but not in ;; things that exist in the main submission directory but not in
;; SUCCESS, or things that are newer in the main submission ;; SUCCESS, or things that are newer in the main submission
;; directory are kept (but subdirs in SUCCESS will are copied as ;; directory are kept (but subdirs in SUCCESS will are copied as
;; is)) ;; is))
(for-each (for-each
(lambda (f) (lambda (f)
(define dir/f (build-path dir f)) (define dir/f (build-path dir f))
(cond [(not (or (file-exists? f) (directory-exists? f))) (cond [(not (or (file-exists? f) (directory-exists? f)))
;; f is in dir but not in the working directory ;; f is in dir but not in the working directory
(copy-directory/files dir/f f)] (copy-directory/files dir/f f)]
[(or (<= (file-or-directory-modify-seconds f) [(or (<= (file-or-directory-modify-seconds f)
(file-or-directory-modify-seconds dir/f)) (file-or-directory-modify-seconds dir/f))
(and (file-exists? f) (file-exists? dir/f) (and (file-exists? f) (file-exists? dir/f)
(not (= (file-size f) (file-size dir/f))))) (not (= (file-size f) (file-size dir/f)))))
;; f is newer in dir than in the working directory ;; f is newer in dir than in the working directory
(delete-directory/files f) (delete-directory/files f)
(copy-directory/files dir/f f)])) (copy-directory/files dir/f f)]))
(directory-list dir))))) (directory-list dir)))))
(define cleanup-sema (make-semaphore 1)) (define cleanup-sema (make-semaphore 1))
(define (cleanup-submission dir) (define (cleanup-submission dir)
@ -168,7 +158,11 @@
(let loop () (let loop ()
(let loop ([n (+ 20 (random 20))]) ; 10-20 minute delay (let loop ([n (+ 20 (random 20))]) ; 10-20 minute delay
(when (>= n 0) (when (>= n 0)
(let ([new (map directory-list '("active" "inactive"))]) (let ([new (map (lambda (x)
(if (directory-exists? x)
(directory-list x)
null))
'("active" "inactive"))])
(if (equal? new last-active/inactive) (if (equal? new last-active/inactive)
(begin (sleep 30) (loop (sub1 n))) (begin (sleep 30) (loop (sub1 n)))
(begin (set! last-active/inactive new) (begin (set! last-active/inactive new)
@ -389,6 +383,8 @@
;; Try making a watcher: ;; Try making a watcher:
(let ([session-cust (make-custodian)] (let ([session-cust (make-custodian)]
[session-channel (make-channel)] [session-channel (make-channel)]
[timeout (+ (current-inexact-milliseconds)
(* 1000 SESSION-TIMEOUT))]
[status-box (box #f)]) [status-box (box #f)])
(let ([watcher (let ([watcher
(with-handlers ([exn:fail:unsupported? (with-handlers ([exn:fail:unsupported?
@ -400,25 +396,36 @@
(parameterize ([current-custodian orig-custodian]) (parameterize ([current-custodian orig-custodian])
(thread (lambda () (thread (lambda ()
(let ([session-thread (channel-get session-channel)]) (let ([session-thread (channel-get session-channel)])
(let loop () (let loop ([timed-out? #f])
(if (sync/timeout 3 session-thread) (cond
(begin [(sync/timeout 3 session-thread)
(LOG "session killed while ~s" (unbox status-box)) (LOG "session killed ~awhile ~s"
(fprintf w "~s\n" (if timed-out? "(timeout) " "")
(format (unbox status-box))
"handin terminated due to excessive memory computation~a" (fprintf w "~s\n"
(if (unbox status-box) (format
(format " while ~a" (unbox status-box)) "handin terminated due to ~a (program doesn't terminate?)~a"
""))) (if timed-out?
(close-output-port w) "time limit"
(channel-put session-channel 'done)) "excessive memory use")
(begin (if (unbox status-box)
(collect-garbage) (format " while ~a" (unbox status-box))
(LOG "running ~a (~a ~a)" "")))
(current-memory-use session-cust) (close-output-port w)
(current-memory-use orig-custodian) (channel-put session-channel 'done)]
(current-memory-use)) [((current-inexact-milliseconds) . > . timeout)
(loop)))))))))]) ;; Shutdown here to get the handin-terminated error
;; message, instead of relying on
;; SESSION-TIMEOUT at the run-server level
(custodian-shutdown-all session-cust)
(loop #t)]
[else
(collect-garbage)
(LOG "running ~a (~a ~a)"
(current-memory-use session-cust)
(current-memory-use orig-custodian)
(current-memory-use))
(loop #f)])))))))])
(if watcher (if watcher
;; Run proc in a thread under session-cust: ;; Run proc in a thread under session-cust:
(let ([session-thread (let ([session-thread
@ -490,7 +497,7 @@
;; flushes an internal buffer that's not supposed to exist, while ;; flushes an internal buffer that's not supposed to exist, while
;; the shutdown gives up immediately. ;; the shutdown gives up immediately.
(close-output-port w))))))) (close-output-port w)))))))
SESSION-TIMEOUT (+ SESSION-TIMEOUT 30) ; extra 30 seconds gives watcher thread time to produce a nice message
(lambda (exn) (lambda (exn)
(printf "~a~n" (if (exn? exn) (printf "~a~n" (if (exn? exn)
(exn-message exn) (exn-message exn)