Я запускаю приложение tcl8.5/tk8.5 в Windows (проблема возникает в XP, 7 и 8) с драйвером FTDI D2xx в режиме BitBash. Также используя расширение ftd2xx c для доступа к FTDI dll.
У меня есть отчет от пользователя, приложение изначально работает нормально, но после дня неиспользования, живя в фоновом режиме, оно внезапно растет с 5M до 100M и начинает потреблять 99% процессора. (Это не хорошо!)
У меня были проблемы с USB до этого, особенно если USB «горячее отключение». Это может привести к блокировке приложения, и Windows не сможет его убить. Моему приложению никогда не нужно читать USB, оно просто пишет на него (управляет им), но я обнаружил, что в режиме BitBash чип FTDI отправляет непрерывный поток данных обратно. Обработка чтения очищает буфер чтения, поэтому, если USB отключен, нет ожидающих чтения для блокировки, и я могу изящно выйти.
Но теперь я думаю, что обработчик чтения вызывает у меня проблемы. Я написал небольшой тестовый фрагмент кода, который имитирует реальное приложение, которое, как мне кажется, позволяет понять корень проблемы. В любом случае, я не понимаю поведения tcl.
Вот код:
package require -exact ftd2xx 1.2.1
# define a read handler
# gets called by a fileevent readable
#
proc readchan {} {
variable cnt
set len [gets $::handle buf]
# buffer is non zero
if {$len > 0} {
incr cnt
set end [eof $::handle]
puts "$len ($cnt) eof $end";
# if it wasn't an eof output the buffer
if {!$end} {
puts $buf
}
} elseif {$len == 0} {
# was a zero length read
puts -nonewline "0"
set end [eof $::handle]
if { !$end } {
# eof makes the $len invalid?
puts "-0"
#puts "\nlen is 0 and eof is $end - exit!";
#exit
}
} else {
# len was negative (-1) so data in buffer but no end of line (in binary mode)
if { [eof $::handle] } {
puts "EOF w/len 0"
} else {
puts -nonewline "."
}
}
}
# main code
#
# find the usb device and open it
set usb [ftd2xx list]
lassign $usb d
lassign $d d id e loc f serial
puts "found USB $serial"
set handle [ftd2xx open -serial $serial]
puts "Opened USB $handle"
#
# configure it for bitbash mode and for binary, non blocking
#
set bitmode 0xFF01
chan configure $handle -tranlation binary -bitmode $bitmode -blocking 0
fileevent $handle readable readchan
# output something to the usb every 100ms or so required to cause failure
#
while {1} {
set continue 0
after 100 {set continue 1}
# putting:
# "a" made it crash after 245 "timeouts" - no flush
# null made it past 255 - no flush
# "aa" made it crash after 164 "timeouts - no flush
puts $handle "aa"
#flushing $handle makes it crash after first flush
#flush $handle
puts -nonewline "w"
vwait continue
}
Вот вывод из приведенного выше кода:
$ tclsh85 usbfailtest.tcl
found USB AH009L40
Opened USB ftd2xx0
w...w....w....w....w....w....w...w....w.126976 (1) eof 1 <<< 126K length with eof true
..w....w....w....w...w....w....w....w..126976 (2) eof 1 <<< "w" output for usb write
.w....w....w...w....w....w....w....w...126976 (3) eof 1 <<< this is 3rd non-zero read
w...w....w....w....w....w...w....w....w.126976 (4) eof 1 <<< "." output when len -1
..w....w...w....w....w....w....w...w...126976 (5) eof 1 <<< line takes about 1 sec
w....w....w....w....w...w....w....w....w126976 (6) eof 1
.
. (output skipped)
.
.w....w....w....w...w....w....w....w...126976 (156) eof 1
w....w....w...w....w....w....w....w....w126976 (157) eof 1
..w....w....w....w....w....w...w....w..126976 (158) eof 1
.w....w....w...w....w....w....w....w...126976 (159) eof 1
w....w...w....w....w....w....w....w...w.126976 (160) eof 1
..w....w....w....w....w...w....w....w..126976 (161) eof 1
.w....w....w....w...w....w....w....w...126976 (162) eof 1
w....w...w....w....w....w....w....w...w.126976 (163) eof 1
..w....w....w....w....w...w....w102695 (164) eof 0 << a 102K buffer with EOF false
aa << all but two of the 102K buffer were null?
0-0 << zero length buffer with no eof
2 (165) eof 0
aa << two length buffer, no eof, and we got two chars
3 (166) eof 0
aaa << three length bufffer, no eof, and we got three chars
2 (167) eof 0
aa << etc.
0-0 << another zero zero these scroll out *very* fast.
0-0
0-0
0-0
2 (168) eof 0
aa << an so forth
0-0
0-0
.
. (output skipped)
.
43 (268) eof 0
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
..w...22192 (269) eof 1
w...w....w....w....w....w...w....w....w.126976 (270) eof 1 << revert to "slow" mode
..w....w....w....w...w....w....w....w..126976 (271) eof 1
.w....w....w...w....w....w....w....w...126976 (272) eof 1
w....w...w....w....w....w....w....w...w.126976 (273) eof 1
..w....w....w....w....w...w....w....w..126976 (274) eof 1
Объяснение вывода:
Начнем с того, что обработчик чтения вызывается несколько раз (каждый ".") с неполным буфером (длина -1) и без условия eof. В конце концов что-то либо истекает, либо достигается предел внутреннего буфера, и чтение принудительно завершается. В то же время поднялся EOF. Написание каждой строки "...126987" занимает около секунды.
После некоторого количества EOF (то есть строк ....126987 - в данном случае 164 - очень повторяемо), в зависимости от количества данных, ЗАПИСАННЫХ в канал, и от того, очищен ли канал, чтение завершено без EOF (164-й линия).
До этого момента частота прерывания события чтения была приемлемой, но затем она резко возрастает и требует много циклов обработки. На более медленных машинах не остается времени на что-либо полезное.
У меня много вопросов по этому поводу, которые трудно сформулировать. Но для начала:
Я не понимаю, почему обработчик чтения вызывается с 0 ожидающими байтами и условием, отличным от eof. Разве в буфере не должен быть хотя бы один байт, чтобы он был «читабельным»?
Я не понимаю, почему EOF является временным. Я ожидал бы, что EOF на USB-порту означает что-то вроде того, что он отключен, но это не так.
Если я очищаю канал после записи на USB, ВСЕ, что я получаю, это чтение с нулевой длиной, и это сохраняется, даже если я убью приложение и перезапущу его. Единственный способ очистить это (до медленного режима) - отключить устройство от USB и начать сначала (без сбросов). Я не уверен, что с этим делать. Мне нужно очистить канал, чтобы запись действительно пошла на чип FTDI.
Должен ли я использовать чтение с фиксированной длиной вместо получения для этого?