QUIK 8.8.4.3. Стандартная схема оформления. Иногда после нескольких дней работы зависает. При этом загрузка ЦП процессом 0 Сначала грешил на потокобезопасные функции. Но анализ логов показывает, что зависании происходит вне потокобезопасных функций (но это не точно )) Можно ли по стеку определить последнюю вызванную QLua функцию?
Добрый день! По скриншотам ответить на ваш вопрос, к сожалению, не сможем.
Просим выслать на почту нашей поддержки (quiksupport@arqatech.com) дамп процесса в момент зависания, архив рабочего места QUIK со скриптами, которые работали на момент появления ошибки, и подробное описание проблемы.
QUIK не совсем завис, а только основной поток находится в ожидании чего-то. Один из скриптов в main периодически шлёт отладочную информацию через PrintDbgStr.
Надо делать так, как надо. А как не надо - делать не надо.
Можно сказать, что зависло в функции lua_pushstring, но это не главное, на ее месте в принципе могла быть и другая, скрипт встал в ожидание на lua_lock (первая строка в сорце) и встал намертво. Где-то этот лок захвачен и не выпущен, возможно, в одном из (других) скриптов, возможно, косячок в самом квике. В скрипте захватить лок и не выпустить можно, например, если присутствует бесконечная рекурсия, может и другие варианты есть. Модельный пример с бесконечной рекурсией (элементарнейший, практические варианты, конечно, не так очевидны)
Код
function so()
return so()
end
function main()
so()
end
Тут еще важно, есть ли в рекурсивном кольце вызовы си-функций, они будут периодически отпускать лок и квик намертво не зависнет, будет течь и тормозить только. Тот же пример с вызовом си-функции квик не вешает
Код
function so()
sleep(0)
return so()
end
function main()
so()
end
Тот факт, что один из скриптов в мейне все же прорывается время от времени, идею о совсем уж дедлоке ломает, однако ж, как ни крути, скрипт (и весь квик) стоит на lua_lock.
Еще замечу, что в варианте с 5.3 схему синхронизации сильно поменяли, раньше была просто критическая секция одна на весь луа, теперь там позабавнее система, вот этот кусочек на дизасме
Код
mov qword ptr [...]
...
test rax, rax
je <skip next instruction>
call rax
немного показывает, как теперь сделано. Если установлена "функция синхронизации" (т.е. она не null), то она и вызывается, иначе синхронизация пропускается и все. Как-зачем-почему не знаю, догадками мусорить не буду.
Старатель, дополню. Квик хочет вызвать колбек OnAllTrade и заполняет для него табличку с очередной сделкой. Все данные из ТВС уже вытащены, почти вся табличка заполнена, в последнюю очередь квик создает вложенную табличку datetime, пытается воткнуть в нее строку "day" и в этот момент виснет на lua_pushstring, как написал выше. В общем, это все мало что дает, косяк где-то в синхронизации все равно.
Anton написал: Квик хочет вызвать колбек OnAllTrade и заполняет для него табличку с очередной сделкой. Все данные из ТВС уже вытащены, почти вся табличка заполнена, в последнюю очередь квик создает вложенную табличку datetime, пытается воткнуть в нее строку "day" и в этот момент виснет на lua_pushstring, как написал выше. В общем, это все мало что дает, косяк где-то в синхронизации все равно.
Первые два трейда - это видимо, два скрипта из трёх запущенных. Один из которых, как я написал, выше, раз в минуту шлёт отладочную информацию. Второй просто молча работает (вроде, работает).
А третий скрипт, я также предполагал, что застрял перед вызовом одного из колбеков, потому и не отображается в логе. Что касается бесконечной рекурсии, то их нет в скрипте. И я почти на 100% уверен, что QUIK "завис" не из-за логической ошибки в скрипте. За эту версию говорит также тот довод, что неоднократно похожая проблема наблюдалась на скриптах из этой темы.
Надо делать так, как надо. А как не надо - делать не надо.
Старатель написал: За эту версию говорит также тот довод, что неоднократно похожая проблема наблюдалась на скриптах из этой темы .
Там совсем другая картина, квик пытается получить колбек через lua_getglobal и падает из-за access violation. В этой теме проблема аналогичная, только на вызове не OnAllTrade, а OnParam, в остальном один в один. Думал, как оно может случиться, придумал только одно: если колбек вызывает какие-то функции квика, в процессе обработки такой функции квик может получить новое сообщение и вызвать колбек (возможно, другой) рекурсивно. Удерживаемый лок здесь не поможет, поток-то тот же самый. В некоторый момент лимит си-вызовов будет превышен, луа бросит ошибку, квик прибьет скрипт (и его стейты, естественно), рекурсия начнет раскручиваться в обратную сторону, вытесненные колбеки попытаются продолжить работу, а стейтов уже нет, вот и access violation.
Здесь же видим типичный дедлок. Возможно, базовая природа в обоих случаях та же, просто проявления разные, смотря на каком этапе находится колбек в момент ошибки. Впрочем, это надо поверять тестами все, пока только гипотезы.
Anton написал: Где-то этот лок захвачен и не выпущен, возможно, в одном из (других) скриптов, возможно, косячок в самом квике.
7500 и 5364 - потоки двух скриптов. Они большую часть времени стоят на SleepEx. Один из них, известно точно, периодически просыпается и шлёт отладочную информацию. Так что, лок захвачен явно не ими. А вот, где поток третьего скрипта, не пойму, может прибит уже?
Скрытый текст
Цитата
Anton написал: Там совсем другая картина, квик пытается получить колбек через lua_getglobal и падает из-за access violation.
Помимо описанных ошибок и падений в теме, несколько раз были и зависания. Но зависает не скоро, и написать демонстрационный скрипт, гарантированно приводящий к зависанию в течение разумного времени после запуска не представляется возможным.
Надо делать так, как надо. А как не надо - делать не надо.
Старатель написал: А вот, где поток третьего скрипта, не пойму, может прибит уже?
А и правда, где мейн-то от зависшего скрипта. Если прибит, то теория с прибитием скрипта "под ковром" получает плюсик в свою пользу.
Что касается воркеров на скринах, они похожи на воркеры от gdi+ и от COM, первые ждут заданий на отрисовку, вторые маршалят вызовы через свою очередь сообщений, там своя жизнь и, думаю, арка там ничего не меняла, так что пока можно туда не смотреть.
Цитата
Старатель написал: Но зависает не скоро, и написать демонстрационный скрипт, гарантированно приводящий к зависанию в течение разумного времени после запуска не представляется возможным.
Так вот и да. Как раз бесконечную рекурсию я и пробовал как способ захватить лок луа и не выпускать его, также пробовал насильно превысить лимит си-вызовов, но никаких падений-зависаний не словил, в этом случае квик все поймал и четко зачистил. Возможно, в более нагруженном окружении словил бы чего, дык это еще надо как-то устроить.
Roman Azarov, У меня там скрипты на lua api написаны. Давайте я понаблюдаю еще и возможно более точно вам скажу. Но я вам выслал на почту dumps после вылета квика. Возможно это другая проблема.
Идея с рекурсией не подтверждается пока, потестил с перезаказами ТВС следующим скриптом
Код
local run = true
local tabid = nil
local rowid = nil
local counter = 0
local recursion_level = 0
local max_recursion_level = 0
function OnInit()
tabid = AllocTable()
if 0 == AddColumn(tabid, 1, 'recursion level', true, QTABLE_STRING_TYPE, 16) then
DestroyTable(tabid)
tabid = nil
error("AddColumn failed")
end
if 0 == AddColumn(tabid, 2, 'counter', true, QTABLE_STRING_TYPE, 16) then
DestroyTable(tabid)
tabid = nil
error("AddColumn failed")
end
if 0 == CreateWindow(tabid) then
DestroyTable(tabid)
tabid = nil
error("CreateWindow failed")
end
rowid = InsertRow(tabid, -1)
if -1 == rowid then
DestroyTable(tabid)
tabid = nil
rowid = nil
error("InsertRow failed")
end
end
function OnAllTrade(v)
recursion_level = recursion_level + 1
counter = counter + 1
if recursion_level > max_recursion_level then
max_recursion_level = recursion_level
end
if tabid then
SetCell(tabid, rowid, 1, tostring(max_recursion_level))
SetCell(tabid, rowid, 2, tostring(counter))
end
recursion_level = recursion_level - 1
end
function OnStop()
if tabid then
local t = tabid
tabid = nil
DestroyTable(t)
rowid = nil
end
run = false
end
function main()
while run do
sleep(1000)
end
end
Результат - уровень рекурсии стоит 1 как влитой. Кому не лень прошу повторить упражнения, может где двоечка хотя бы выскочит, тогда будем знать, что хотя бы в принципе такое возможно. Также можно попробовать понагружать OnAllTrade перед выводом в таблицу чем-нибудь. Вариант sleep(100) попробовал, загружает квик до почти зависания, но это и логично, надо бы чем-нибудь поинтересней.
А почему она там должна выскочить? Если колбек выполняется, то там 1, а если висит основной поток, то, по идее, колбек не выполнится. Либо, можно предположить, что после "отвисания" он просто вызовет все колбеки в очереди. Последовательно.
Nikolay написал: А почему она там должна выскочить?
Выше предположил, что если из колбека вызвать (не любую, естественно) функцию квика, то эта функция может (неожиданно для всех) дернуть GetMessage, получить уведомление о новых данных и тут же на радостях дернуть соответствующий колбек, в результате получим вложенное выполнение колбека внутри другого колбека. Вот и хотел такое поведение спровоцировать. Очереди колбеков, насколько понимаю, специальной нет, как сериализатор используется общая очередь виндовых сообщений основного потока, wt_de шлет туда сообщения о приехавших данных, основной поток их выбирает и тут же дергает соответствующие колбеки, т.е. да, после "отвисания" очередь будет обрабатываться в порядке прихода сообщений, тут вопрос, не может ли она "отвиснуть" внутри колбека.
Anton написал: Идея с рекурсией не подтверждается пока, потестил с перезаказами ТВС следующим скриптом
Код
local run = true
local tabid = nil
local rowid = nil
local counter = 0
local recursion_level = 0
local max_recursion_level = 0
function OnInit ()
tabid = AllocTable ()
if 0 = = AddColumn (tabid, 1 , 'recursion level' , true , QTABLE_STRING_TYPE, 16 ) then
DestroyTable (tabid)
tabid = nil
error( "AddColumn failed" )
end
if 0 = = AddColumn (tabid, 2 , 'counter' , true , QTABLE_STRING_TYPE, 16 ) then
DestroyTable (tabid)
tabid = nil
error( "AddColumn failed" )
end
if 0 = = CreateWindow (tabid) then
DestroyTable (tabid)
tabid = nil
error( "CreateWindow failed" )
end
rowid = InsertRow (tabid, - 1 )
if - 1 = = rowid then
DestroyTable (tabid)
tabid = nil
rowid = nil
error( "InsertRow failed" )
end
end
function OnAllTrade (v)
recursion_level = recursion_level + 1
counter = counter + 1
if recursion_level > max_recursion_level then
max_recursion_level = recursion_level
end
if tabid then
SetCell (tabid, rowid, 1 , tostring(max_recursion_level))
SetCell (tabid, rowid, 2 , tostring(counter))
end
recursion_level = recursion_level - 1
end
function OnStop ()
if tabid then
local t = tabid
tabid = nil
DestroyTable (t)
rowid = nil
end
run = false
end
function main ()
while run do
sleep ( 1000 )
end
end
Результат - уровень рекурсии стоит 1 как влитой. Кому не лень прошу повторить упражнения, может где двоечка хотя бы выскочит, тогда будем знать, что хотя бы в принципе такое возможно. Также можно попробовать понагружать OnAllTrade перед выводом в таблицу чем-нибудь. Вариант sleep(100) попробовал, загружает квик до почти зависания, но это и логично, надо бы чем-нибудь поинтересней.
зачем Вы данные всех сделок собираете в таблицу QLUA - это же тормозуха. Собирайте в таблицу луа, либо в массив на C и будет вам счастье.
nikolz написал: зачем Вы данные всех сделок собираете в таблицу QLUA
Ничего никуда не собираю, сижу примус починяю, а тут и добрый совет подоспел. Попал тксть под раздачу.
тогда поясните, что вы делаете в функции колбека: ------------------------------- function OnAllTrade (v) recursion_level = recursion_level + 1 counter = counter + 1 if recursion_level > max_recursion_level then max_recursion_level = recursion_level end if tabid then SetCell (tabid, rowid, 1 , tostring(max_recursion_level)) SetCell (tabid, rowid, 2 , tostring(counter)) end recursion_level = recursion_level - 1 end -------------------------
Как можете видеть, при входе в OnAllTrade инкрементируется recursion_level, а при выходе - декрементируется. Таким образом, он содержит глубину рекурсии по OnAllTrade в данный момент. В свою очередь, max_recursion_level содержит максимально достигнутую глубину рекурсии за время работы скрипта. Первая выделенная строчка выводит max_recursion_level в таблицу квика. Вторая выводит простой счетчик вызовов OnAllTrade, дабы было видно, что скрипт не завис. Цель всех упражнений - втыкать перед выводом в таблицу различные функции взаимодействия с квиком (чем тормознутее, тем лучше) и смотреть, не получится ли в каком-то из вариантов уровень рекурсии больше единицы.
Anton написал: смотреть, не получится ли в каком-то из вариантов уровень рекурсии больше единицы.
В QUIK существующая cхема обработки колбеков следующая: 1. Существует единственный служебный поток, из которого запускаются потоки скриптов пользователя (main). 2. При запуске пользовательских скриптов выполняется регистрация объявленных в них колбеков. 3. Колбеки всех запущенных пользователем скриптов обрабатываются в служебном потоке последовательно, так как поток один. Если в нескольких пользовательских скриптах объявлен некоторый колбек (с одинаковым названием), то при возникновении соответствующего ему события, для каждого скрипта в его state запускается свой (объявленный) колбек и выполняется это в служебном потоке последовательно. Таким образом никакие существующие пользовательские колбеки не могут обрабатываться параллельно. С учетом выше изложенного всегда max_recursion_level = 1 и это подтверждает предложенный вами тестовый скрипт.
Похоже, источник проблемы - функция с переменным числом аргументов. Возникает либо при вызове функции
Код
c(func, ...)
либо при формировании таблицы аргументов
Код
local arg = {...}
Код демонстрационного скрипта:
Код
local run = true
function c(func, ...)
local arg = {...}
end
local f1 = function() end
function OnAllTrade(alltrade)
c(f1, alltrade)
end
function OnParam(class, sec)
end
function OnStop()
run = nil
end
local f2 = function() end
function main()
while run do
for i = 1, 100 do
c(f2, i, 1, 2, 3)
end
sleep(1)
end
end
Надо делать так, как надо. А как не надо - делать не надо.
local run
local file
local function log(s)
file:seek('set')
file:write(s)
file:flush()
end
function c(func, ...)
log('1')
local arg = {...}
log('2')
end
function OnInit(path)
local err
file, err = io.open(path..'.log', 'w+')
if file == nil then
message(err, 3)
else
run = true
end
end
function OnAllTrade(alltrade)
end
function OnParam(class, sec)
end
function OnStop()
run = nil
end
local f2 = function() end
function main()
if not run then return end
while run do
for i = 1, 100 do
log('0')
c(f2, i, 1, 2, 3)
log('3')
end
sleep(1)
end
file:close()
end
В логе:
Цитата
1
Как и в прошлый раз поток main скрипта прибит. Основной поток остался в состоянии ожидания.
Скрытый текст
Надо делать так, как надо. А как не надо - делать не надо.
Старатель написал: Похоже, источник проблемы - функция с переменным числом аргументов.
Здравствуйте.
Я в соседней ветке обсуждения как-то уже написал https://forum.quik.ru/messages/forum10/message48194/topic5527/#message48194 что, похоже, многие проблемы версий QUIK >= 8.5... вызваны некорректной реализацией QLua-VM. Ситуации, когда при падении функций, написанных на "чистом" QLua, пользователю не выдаются диагностические сообщения из самого QLua - это ошибки разработчиков языка QLua (отличного от нативного Lua, в части реализации управления памятью). Причем, скорее всего, это ошибки синхронизации при реализации потокобезопасного управления автоматической памятью QLua. Такого рода ошибки "плавающие" и внешне похожи на сбои аппаратуры. Но могут быть программы, которые создают нагрузку, при которой эти ошибки возникают чаще обычного. Это, наверное, делает и ваш срипт. Я 15.08.20 послал поддержке свою программу, которая "роняет" QUIKи в интервале 5 минут (в разные моменты времени). А до этого отправил кучу дампов. Наверное, имеет смысл и вам туда же отправить ваш скрипт.
Старатель, не желаете патченую qlua.dll запробовать со своими тестами? Заменил там все lua_checkstack(1) на lua_checkstack(16), кроме OnParam, с этой сходу не понял даже, что там вытворено такое, выглядит как (в том фильме). Если паранойя не мучает, качнуть можно здесь. У меня нынче брокера нет на связи, оффлайн я не могу ваши крэши воспроизвести, а без крэшей что с патчем, что без, работает одинаково.
Разобрался и с OnParam, это там компилятор начудил своих оптимизаций, у арки было 1 как везде, гыгы. Как оказалось, я не 16, а 256 везде понаставил, ну даже и лучше, чай не на контроллере выполняемся. Тут вариант 2 с патчем и на OnParam тоже.
ДокладАю. Приведенный здесь тест у меня не воспроизвелся, воспользовался тестом из этого поста, он у меня хорошо воспроизводится. На оригинальной qlua.dll вывалился с дампом (все того же вида) с первого перезаказа. На патченой четыре перезаказа (вот сейчас, на большой ТВС) отработал, полет нормальный. Особо нагружать боевой сервер я стесняюсь, так что для сбора статистики предлагаю заинтересованным коллегам повторить эксперименты у себя. Если подтвердится работоспособность в патченом виде, то причина разрушения луа-стека в inline-обертке lua_checkstack внутри qlua.dll, там захардкодили расширение стека на 1 слот. Патч меняет это значение на 256 и в одном месте на 63 (не хватило байт для опкода).
Старатель написал: Не, знаю, после долгого тестирования, QUIK ошибки накапливает или чё?
Каждый слот стека начинается с GCObject, а его первое поле это GCObject * next. То есть все луа-объекты связаны в списки, используемые коллектором для прибития зомбаков. Раз уж вы словили разрушение стека, все эти списки коллектора уже сломаны, так что он начнет творить непредсказуемые вещи. Плюс, вылезая за границы выделенной памяти (и не будучи пойманным), луа ломает также и списки аллокатора, так что опять же когда-то потом в непредсказуемом месте (может быть, через пару дней) случится внезапный крэшик, концов которого не найдешь.
А логические ошибки, когда в непредсказуемом месте вылезает nil, типа attempt to compare number with nil и т.п., они могут как-то в будущем повлиять? QUIK перегружать нужно?
Надо делать так, как надо. А как не надо - делать не надо.
Старатель написал: А логические ошибки, когда в непредсказуемом месте вылезает nil, типа attempt to compare number with nil и т.п., они могут как-то в будущем повлиять? QUIK перегружать нужно?
Когда после ошибки в одном скрипте начинают сыпаться ошибки в других, или в нем же после рестарта, это показывает, что стек был покоцан, дальше уже как повезет, насколько сильно и т.д., где-то закончится небольшой утечкой, где-то уже серьезные повреждения, которые вылезут позже.
PS Не поделитесь содержимым ip.cfg от джуниора? Чет я его в виде архива не найду.
Старатель написал: А логические ошибки, когда в непредсказуемом месте вылезает nil, типа attempt to compare number with nil и т.п., они могут как-то в будущем повлиять? QUIK перегружать нужно?
Когда после ошибки в одном скрипте начинают сыпаться ошибки в других, или в нем же после рестарта, это показывает, что стек был покоцан, дальше уже как повезет, насколько сильно и т.д., где-то закончится небольшой утечкой, где-то уже серьезные повреждения, которые вылезут позже.
Я про логические ошибки скрипта, когда где-то что-то недосмотрел, не проверил на nil полученные данные и т.п. И в какой-то момент вместо данных там оказался nil. Скрипт остановился. Это может повлиять на будущую работу?
Надо делать так, как надо. А как не надо - делать не надо.
Старатель написал: И в какой-то момент вместо данных там оказался nil. Скрипт остановился. Это может повлиять на будущую работу?
В идеальном мире не должно, квик вызывает скрипты и колбеки под pcall, такие ошибки должны им отлавливаться и правильно обрабатываться. В реальном мире квик не всегда проверяет, когда ему из скрипта передан nil или битый указатель, лезет по ним и получает по ушам. Недавняя тема тому пример. Эти ошибки почему-то пролетают мимо pcall, в качестве костыля арка настроила вокруг pcall'а try-catch блок, и если ошибка долетела до него, скрипт принудительно прибивается прямо в ловушке. Проблема в том, что в этом случае вся постобработка в pcall обходится, то есть стек не выравнивается (одна из причин, видимо, почему в арке решили тут же скрипт прибивать, он уже и так не жилец), но главное не выпускается лок. С одной стороны, у каждого скрипта лок свой, раз скрипт прибили, то и проблем быть не должно, остался ли он захваченным или нет. С другой можно придумать (и изредка пронаблюдать) сценарий, в котором квик уже после прибития скрипта попытается что-то проделать в том же стейте, соответственно попробует захватить лок и повиснет на нем (а выпускать его уже некому). Это проблема отдельная, по-хорошему от костыля с ловушкой надо избавляться, а сначала расковырять все места, где квик наступает на мину в виде нила или битого указателя.
Чет увлекся деталями и краткого ответа по существу не написал. В общем, если ошибка (любая) была поймана pcall'ом, никаких разрушений в квике нет. Если поймана костыльной ловушкой, разрушения есть. Второе не значит, что дальше обязательно зависнет, все может быть хорошо, разве что утечет сколько-нибудь памяти (вряд ли много). В колбеках разница видна снаружи, пойманные pcall'ом ошибки скрипт не останавливают, только выводят сообщение, пойманные ловушкой - останавливают. С мейном сложнее, скрипт прибивается в любом случае. Сообщение ACCESS VIOLATION говорит о том, что поймала ловушка, типовое луа-сообщение типа не есть функция и подобные - это из pcall.
Старатель, а вы тоже наблюдаете такой эффект: если много этих тестовых скриптов запущено, таблица всех сделок не успевает обновляться, перерисовывается изредка сразу с большим шагом. Но если кликнуть на каком-то меню, чтобы оно вылезло, таблица начинает рисоваться как обычно. Или открыть какой-нибудь модальный диалог, вроде заказа данных.
На новом джуне и ваших тестах получил кучку дампов и одно зависание с полпинка. После замены клуа на патченую перезаказываю уже который раз и пока все без эксцессов.
Для нехакеров замечу, что патченая длл для продакшена не предназначена, она патчена наскоро кое-как, чисто для тестов. Правильную длл сделает арка.
Anton написал: вы тоже наблюдаете такой эффект: если много этих тестовых скриптов запущено, таблица всех сделок не успевает обновляться, перерисовывается изредка сразу с большим шагом.
У меня в таблицу обезличенных сделок добавлен только один инструмент: либо какой-нибудь старый уже истёкший фьючерс либо какой-то неторговый. Т.ч., по факту само окно пустое, а сделки заказываю через фильтры (либо скриптом).
Надо делать так, как надо. А как не надо - делать не надо.
Следующий тест: запустил скрипт 1 и три скрипта 3 (дабы быстрее получить результат) с вашей патченой длл. Сделал несколько тестов. Ошибки не дождался, т.к. QUIK зависал по уже знакомой схеме: main одного скрипта прибит, остальные скрипты крутятся, основной поток остался в состоянии ожидания.
Сейчас вышла версия 8.9. Посмотрим насколько она стабильнее.
Надо делать так, как надо. А как не надо - делать не надо.
У меня с патченой не было ни одного зависания/падения, гонял все тесты одновременно, перезаказывал по многу раз. Заменяешь на родную и достаточно быстро на тех же тестах что-нибудь ловишь. Кстати, патч только на колбеки, на SearchItems и потокобезопасные оставлено как есть. До них дорыться сложнее, все ж это пруф оф концепт, а не для работы.
Цитата
Старатель написал: Сейчас вышла версия 8.9. Посмотрим насколько она стабильнее.
Одним глазком заглянул, lua_checkstack(1) на месте.
Anton, так и у меня может до нескольких дней без зависаний работать. Но всё же проблема есть, в т.ч. в 8.9
Попробуйте следующий скрипт, также зависает:
Код
local run = true
local z = {0, 0}
local ssort = table.ssort
function OnAllTrade(alltrade)
ssort(z, function () return true end)
end
function OnParam(class, sec)
end
function OnStop()
run = nil
end
function f1()
for k, v in pairs(_G) do end
end
local function f2()
f1()
end
local function f3()
f2()
end
local function f4()
f3()
end
function main()
while run do
for i = 1, 50 do
f4()
end
sleep(1)
end
end
Зачем вложенные функции? Дело в том, что если использовать pairs непосредственно в цикле main, то скрипт раньше остановится с ошибкой
Цитата
invalid key to 'next'
Что делал, чтобы не ждать неделю и быстрее воспроизвести: заказал обезличенные сделки по всем инструментам, запустил три скрипта и по очереди каждый останавливал и запускал. Занятие довольно нудное, но результат будет быстрее.
Надо делать так, как надо. А как не надо - делать не надо.
Старатель, воспроизвел. Четыре скрипта завел, перезаказал твс, после второго стоп-старт квик слетел без дампа. Соответственно поковырять нечего, только догадки строить. Попробую еще дамп получить.