Stephen Deasey wrote:
Your single thread helper mechanism above may not work the disk as
hard as multiple threads, but it does mean that the driver thread
doesn't have to block waiting for disk.
it does not necessary have to write to the disk.
You've also moved quota checking etc. into the helper thread. This
doesn't so much matter if there's just one, but with more than one you
have to remember that a Tcl interp will be allocated for each, and
they'll be just as heavyweight as normal conn threads.
We have a similarly problem with normal downloads, i.e. writing data
to the client, an mp3 file for example, ties up a heavyweight conn
thread. A generic pool of IO threads might be useful in a number of
places. Of course, this is just a simulation of AIO to disk, but see
my other email for why AIO is not so easy.
we are using on our production site a background delivery for large
files (up to 4 mio requests
per day, up to 30 GB/day, up to 1200 concurrent users, oacs) which does
not block the
connection threads. i did this with some help from zoran and it works
like a champ
(single spooling thread, using ns_conn channel + libthread with
thread::transfer and thread:;send
code below). This bgdelivery implementation has already reduced the
memory footprint of
our server, frees us from slow-download-DOS-"attacks" and yielded no
measurable
performance degradation.
AOLserver HEAD has a new filter type, NS_FILTER_PREQUEUE, which gets
called just before the connection is queued for processing by a conn
thread. The unique thing about this filter type is that the
registered code runs in the context of the driver thread.
Unfortunately that's after all the data has been read and spooled, but
we could implement it such that they ran just after all headers have
been received.
interesting
If you want to check quotas by querying a database then then this
doesn't help -- your db query will block the driver thread. These
filters will have to be coded carefully. We could also implement
non-blocking db calls, and that's certainly something I'm interested
in, but it doesn't help right now.
true.
However, if the code you want to run is short and non blocking,
prequeue filters we be a good way to run quota checks and such without
having to involve a conn thread at all.
also the singed cookie checking of oacs might be already to expensive
for a single thread
(no matter whether is happens in the driver or in the model suggested in
my last mail).
The more i think about the problem, the general case (expensive
permission + quota checking)
of uploads should be handled by connection threads. In order to avoid
having these
sitting more or less idle around, one cal use a spooling thread (or a
few of these).
What i have seen, all ajax file upload bars are associating an
Upload-ID with the file upload and query later via GET requests the
status of these.
This means, that ordinary requests must be able to query the status...
This is quite similar
to my request monitor part in oacs-head (you can see this in mannheim in
action, where it is
publically accessible https://dotlrn.uni-mannheim.de/request-monitor/)
A revised model:
1) request HEAD processing alway in the driver thread
2) for incoming data processing delegation to a connection thread with
a new filter type
(can do db-queries etc)
3) delegation to a spooling thread (where it can be monitored)
4) delegation to a connection thread ala aolserver.
when end-of-file/error occurs in the spooling thread, it would require a
tcl-command
to enqueue the request as connection thread.
Actually, this could be done only when there is a POST/PUT request with a
Upload-ID as a query parameter, such that the programmer has full control
when this happen, there will be no penalty for small forms...
-gustaf
=====================================================
ad_library {
Routines for background delivery of files
@author Gustaf Neumann ([EMAIL PROTECTED])
@creation-date 19 Nov 2005
@cvs-id $Id: background-delivery-procs.tcl,v 1.2 2005/12/30 00:07:23
gustafn Exp $
}
::xotcl::THREAD create bgdelivery {
set ::delivery_count 0
proc deliver {ch filename context} {
set fd [open $filename]
fconfigure $fd -translation binary
fconfigure $ch -translation binary
fcopy $fd $ch -command [list end-delivery $filename $fd $ch]
set ::running($ch,$filename) $context
incr ::delivery_count
}
proc end-delivery {filename fd ch bytes args} {
#ns_log notice "--- end of delivery of $filename, $bytes bytes
written $args"
if {[catch {close $ch} e]} {ns_log notice "bgdelivery, closing
channel for $filename, error: $e"}
if {[catch {close $fd} e]} {ns_log notice "bgdelivery, closing file
$filename, error: $e"}
unset ::running($ch,$filename)
}
} -persistent 1
bgdelivery ad_forward running {
Interface to the background delivery thread to query the currently
running deliveries.
@return list of key value pairs of all currently running background
processes
} %self do array get running
bgdelivery ad_forward nr_running {
Interface to the background delivery thread to query the number of
currently running deliveries.
@return number of currently running background deliveries
} %self do array size running
if {[ns_info name] eq "NaviServer"} {
bgdelivery forward write_headers ns_headers
} else {
bgdelivery forward write_headers ns_headers DUMMY
}
bgdelivery ad_proc returnfile {statuscode mime_type filename} {
Deliver the given file to the requestor in the background. This proc
uses the
background delivery thread to send the file in an event-driven manner
without
blocking a request thread. This is especially important when large
files are
requested over slow (e.g. dial-ip) connections.
} {
set size [file size $filename]
if {[my write_headers $statuscode $mime_type $size]} {
set ch [ns_conn channel]
thread::transfer [my get_tid] $ch
throttle get_context
my do -async deliver $ch $filename \
[list [throttle set requestor],[throttle set url] [ns_conn start]]
ns_conn contentsentlength $size ;# maybe overly optimistic
}
}