Я экспериментировал с попыткой передать большой двоичный объект в браузер из базы данных SQLite без необходимости сначала записывать большой двоичный объект в память и не блокировать сценарий Tcl. Очевидно, что chan copy
с -command
позаботится об этом (я пробовал с файлом, и он не блокируется), но эта функция не будет работать с incrblob
SQLite.
Я попробовал несколько способов обойти это, и большинство из них были слишком далеко за пределами моего уровня навыков и, возможно, вообще неправильным подходом. Я снова читал о сопрограммах и увидел пример, использующий Tcllib coroutine:util
, и задался вопросом, смогу ли я сделать что-то подобное без него.
Приведенный ниже код передает большой двоичный объект, представляющий собой видеофайл размером 250 МБ, в браузер, поскольку его канал $sock
содержит событие, доступное для записи. Это работает, поскольку видео воспроизводится, и каждые 30 секунд или около того событие, кажется, срабатывает, и копируется больше данных.
Я понимаю, что большой двоичный объект в конечном итоге все еще копируется в память перед записью в выходной канал; но это маленькие кусочки в зависимости от размера, переданного в каждом chan puts
.
Мои вопросы:
-blocking 0
, и я думаю, что сопрограмма должна помочь ему не блокироваться; но я не совсем понимаю это, за исключением того факта, что он возвращает управление вызывающей стороне, которая в данном случае является процедурой без оставшихся операторов для выполнения. Я так понимаю, это означает, что нет необходимости использовать подход after 0 []
с сопрограммой.incrblob
? Дело в том, что его канал принадлежит другому интерпретатору, так что события не попадают в нужное «место»?Спасибо за рассмотрение моего вопроса.
proc ::REQ::SendBLOB {sock filename type id} {
dbws eval {select rowid, length(col_blob) as bytes from db.table where id=:id} {
# Get the BLOB as a (read-only) channel
set fp [dbws incrblob -readonly db table col_blob $rowid]
chan configure $fp -translation binary -blocking 0
chan configure $sock -translation binary -blocking 0
# Would use chan copy with -command but it will not work with incrblob.
# chan copy $fp $sock -size $bytes -command [list ::REQ::ChanCopyCleanup $fp]
}
coroutine ::coro_incrblob${sock} proxyfcopy $fp $sock $bytes
chan event $sock writable ::coro_incrblob${sock}
}
proc proxyfcopy {fp sock bytes} {
while { ![chan eof $fp] } {
yield
# Think this 4096 should be at least 5,120,000; for it appears
# that Firefox requests 60 MB at first load and 5 MB thereafter.
if { $bytes > 4096 } {
set size 4096
} elseif { $bytes > 0 } {
set size $bytes
} else {
chan event $sock writable {}
close $fp
return
}
set data [read $fp $size]
chan puts stdout $bytes
chan puts -nonewline $sock $data
set bytes [expr {$bytes - [string length $data]}]
}
}
Каналы incrblob
не поддерживают неблокирующую работу; их базовый дескриптор канала не имеет метода отключения режима блокировки (это поле в записи типа C для типа канала — NULL
). Однако запись в большой двоичный объект в любом случае будет выполняться довольно быстро, поскольку она определенно не требует перераспределения пространства — механизм доступа к добавочному большому двоичному объекту не может изменить размер большого двоичного объекта — или фиксации транзакции (в режиме автоматической фиксации операция фиксации происходит, когда вы закрываете канал; в противном случае это происходит, когда вы COMMIT
явно, конечно). Это немного похоже на работу со стандартным файлом; они технически позволяют вам установить неблокирующий режим, но в этом нет смысла, потому что операционная система фактически игнорирует флаг в этой ситуации (или, скорее, это больше похоже на то, что все файлы являются неблокирующими, за исключением случаев, когда в сетевой файловой системе, когда вы получаете все равно заблокирован, потому что у сетевых файловых систем странная семантика). Для каналов только для чтения incrblob
они будут эффективно выполняться из памяти после того, как БД настроит все и, следовательно, очень и очень быстро.
Поскольку вы также имеете дело с сетевыми сокетами, они действительно поддерживают неблокирующий режим. В частности, сокеты доступны для записи, когда их выходной буфер на уровне ОС не заполнен.
Реализация chan copy
требует, чтобы сторона чтения была неблокирующей для асинхронной операции. Вы не можете сделать это здесь, но вы все равно можете использовать синхронный chan copy
, чтобы избежать копирования данных на более высоких уровнях среды выполнения Tcl. Если вы используете фиксированные передачи с этим (1444, вероятно, является хорошим предположением — это 1500 Ethernet MTU минус 54 байта служебных данных для заголовка Ethernet, заголовка IP и заголовка TCP, округленного до размера слова — но больше тоже может быть хорошо) потому что вы в конечном итоге имеете дело с потоковой моделью при выполнении TCP, и ОС делает некоторые вещи для вас; вам нужно будет измерить), тогда вы должны получить разумную эффективность. Перемещайте один из них всякий раз, когда выходной канал доступен для записи.
Спасибо. При использовании синхронного chan copy
является ли целью фиксированного размера передачи 1444 байта «защита» от браузера, заполняющего свой буфер при частичной передаче, оставляя некоторые байты незаписанными из копии размером 1444 и приводя к блокировке Tcl? При экспериментах с большими размерами это происходило временами; но в 14:44 я не мог наблюдать, как это происходит.
1444 был предположением о том, что приведет к эффективной отправке пакета, причем предположение было основано на том, насколько реально работает сеть (о чем я, к сожалению, хорошо осведомлен). Большие значения также могут работать нормально; там много сложностей. Использование доступных для записи событий, чтобы узнать, когда делать копию, означает, что вы будете заполнять сетевые буферы ОС и оборудования, но, вероятно, не будете блокировать. Эти вещи определенно нуждаются в настройке.
Ничто не мешает @Gary использовать синхронный
chan copy
в выделенных потоках записи. (1) передать каналincrblob
иsock
в поток (например, взятый из пула) с помощью thread::transfer и (2) выполнитьchan copy
в этом потоке. Учитывая «разумную эффективность», на которую указал Донал, будет достаточно небольшого пула потоков записи.