обрабатывать непостоянный eof в обработчике чтения tcl при неблокировке

Я запускаю приложение 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.

Должен ли я использовать чтение с фиксированной длиной вместо получения для этого?


person user1967890    schedule 27.10.2013    source источник
comment
Если произойдет что-то странное после сброса канала или после того, как на канал будет записано много материала без сброса, я бы подумал, что запись чего-либо просто не работает. Если вы пишете без очистки, данные, скорее всего, никогда не будут отправлены, пока буфер не будет заполнен.   -  person evil otto    schedule 28.10.2013
comment
В моем реальном приложении, конечно, я делаю сброс, чтобы публиковать записи. В рамках экспериментального кода я выполняю/не очищаю запись. Что мне кажется странным, так это то, что очистка -channel- (запись?) изменяет поведение, казалось бы, независимого чтения. Путы также должны иметь параметр -nonewline в двоичном режиме.   -  person user1967890    schedule 29.10.2013
comment
Это сложно, пока вы ничего не знаете о байтах, которые нужно получить. Канал находится в бинарном режиме. В этом случае может быть нехорошо использовать gets. gets не может обнаружить конец строки? Вместо этого используйте чтение. Я не знаю FTDI-чип - это 5V-чип? Некоторые 232-серии имеют проблемы с 5V-микросхемами (стандарт RS232 +-12V). может быть, вы получаете неверные данные.   -  person tue    schedule 07.11.2013
comment
Потратив значительное количество времени на эту проблему (но до того, как вы ответили), я пришел к выводу, что мне следует использовать чтение вместо получения. Похоже, поскольку вы можете определить длину чтения, в двоичном режиме eof не является двусмысленным - если вы его получите, это означает, что канал закрыт, а не то, что вы только что получили байт = 0.   -  person user1967890    schedule 11.01.2014


Ответы (2)


Я реализовал фактический код с чтением вместо получения. Используя моего клиента в качестве испытательного стенда, он сообщил, что проблема решена. Теперь я буду использовать чтение вместо получения для двоичных каналов.

person user1967890    schedule 11.01.2014

Конкретно о пакете ftd2xx ничего не знаю, но вижу кое-что странное. В общем, когда eof возвращает 1, лучше закрыть канал в обработчике. Обработчики файловых событий вызываются всякий раз, когда данные готовы ИЛИ когда канал закрывается извне (может быть, драйвер или ОС закрыли его?), и если вы не скажете TCL закрыть канал в обработчике, TCL будет продолжать вызывать его постоянно ( говорит вам что-то сделать). Из руководства по файловым событиям:

«Канал также считается доступным для чтения, если в базовом файле или на устройстве присутствует состояние конца файла или ошибки. Для сценария важно проверять эти условия и обрабатывать их соответствующим образом; например, если нет специальной проверки для конца файла может возникнуть бесконечный цикл, когда скрипт не считывает данные, возвращается и немедленно вызывается снова».

person David Good    schedule 16.11.2013