Два варианта ответа для разных уровней дзена. 1. Смотрите, что в аргументах передаете потоку. Эти аргументы живут в вызывающем потоке, как только вы второй отцепили, они сразу же будут прибиты, а второй поток об этом не узнает и будет их пытаться использовать, и либо нагрузит вам в базу мусора, либо вызовет av, что еще хороший исход (вам повезло, да). 2. Добавьте в (стандартный) <thread> первую строку следующего вида:
Код
#error This garbage should never be used in production code.
Gla написал: классовую неприязнь ко всем этим буржуям, торгашам, делающим деньги из воздуха и т.п.
Не, это как ритуальные услуги, возня с которыми призвана скрасить горечь события. Недостаточно гламурные иконки облегчают вам прощание навеки с денюшкой, которую вы так любили.
При ближайшем рассмотрении оказывается, что мейн тоже вызывается по той же схеме, только из своего потока, для него тоже все в силе.
Цитата
Nikolay написал: Вот схематичный скрипт, падающий с этой ошибкой.
Ну так да, квик лезет куда-то по прибитому указателю и получается акцесс виолешен (уже не в луа, а в самом квике), а он идет мимо pcall, как выше видно.
В учебно-тренировочном хосте с обычным луа 5.3 попробовал генерировать и плюсовые, и seh исключения в скрипте, так их pcall чудесно отлавливает, только что содержательных описаний не выводит, в первом случае "возможно необработанное исключение", во втором просто "объект ошибки не предоставлен". Помнится, раньше квик тоже похожие сообщения выводил в некоторых случаях, то есть ловились они прекрасно pcall'ом. Как теперь получается, что av пролетает мимо pcall, вот вопрос интересный.
Цитата
Nikolay написал: И еще особенность: скрипт, упавший с этой ошибкой повторно не запустить, кнопка "Запустить" не активна. Приходится удалять и заново добавлять.
Кажется, нашел дырочку, через которую квик может падать при ошибке в скрипте. Вышеприведенная функция вызывается со всеми (?) колбеками по одной схеме, примерно так
Код
int top = lua_gettop(pstate);
lua_getglobal(pstate, "CallbackName");
push_callback_arguments(pstate);
if(this->execute_lua_callback(pstate, nargs, 0))
quik->show_message(make_error_text(), 3);
// вот тут интересно
if(pstate)
lua_settop(pstate, top);
Как мы видели выше, при некоторых ошибках квик прибивает скрипт, то есть стейт к моменту вызова lua_settop уже неживой. А проверяется перед вызовом сохраненный локально указатель, который, конечно, как был не-null, так и остался. Возможно, это компилятор наоптимизировал. В каких колбеках ни посмотри - одно и то же.
Как можете видеть, при входе в OnAllTrade инкрементируется recursion_level, а при выходе - декрементируется. Таким образом, он содержит глубину рекурсии по OnAllTrade в данный момент. В свою очередь, max_recursion_level содержит максимально достигнутую глубину рекурсии за время работы скрипта. Первая выделенная строчка выводит max_recursion_level в таблицу квика. Вторая выводит простой счетчик вызовов OnAllTrade, дабы было видно, что скрипт не завис. Цель всех упражнений - втыкать перед выводом в таблицу различные функции взаимодействия с квиком (чем тормознутее, тем лучше) и смотреть, не получится ли в каком-то из вариантов уровень рекурсии больше единицы.
Старатель написал: Проверьте. При снятии флага пропущенные сделки закачиваются только при перезаказе. Так что как раз теоретически в пределах одного тикера накрутить невозможно.
Просто ручки недостаточно шаловливые. Ставлю с текущего момента, ставлю фильтр только BRV0, подключаюсь, едет с момента подключения. Снимаю с текущего момента, сбрасываю все галки. Ничего не едет. Ставлю опять фильтр только BRV0, едет со вчерашней вечерки безо всякого перезаказа.
s_mike@rambler.ru написал: обезличенные сделки отсортированы в пределах одного инструмента на уровне внутренней базы квика.
С этим и спору нет. Речь о том, что на клиентской стороне, благодаря фильтрам, флагу "получать с текущего момента" и шаловливым ручкам теоретически можно наколбасить по-другому.
Александр, а вот этим скриптиком можете экспортнуть? То же самое получится?
Код
local function save_ticks(cls, sec)
local f = io.open(getScriptPath() .. '\\' .. cls .. '.' .. sec .. '.' .. 'csv', 'w')
f:write('<DATE>;<TIME>;<TRADENUM>;<PRICE>;<QTY>\n')
local fmt = '%04u%02u%02u;%02u%02u%02u%03u;%u;%.6f;%u\n'
local function cmp(t)
if not t then
return nil
end
if cls == t.class_code and sec == t.sec_code then
local dt = t.datetime
f:write(string.format(fmt,
dt.year, dt.month, dt.day,
dt.hour, dt.min, dt.sec, dt.ms,
t.tradenum, t.price, t.qty))
end
return false
end
SearchItems('all_trades', 0, 2000000000, cmp)
end
function main()
save_ticks('SPBFUT', 'BRV0')
end
Табличка подсортировывает при отображении. Вообще манипуляциями с фильтрами можно получить в хранилище месиво. Правда, чтобы по одному инструменту порядок менялся, я не видал еще, но думаю, что нет ничего невозможного для заинтересованного экспериментатора.
Зафильтруйте все, оставьте один газик, перезакажите ТВС - в хранилище будет один газик. Теперь снимите фильтр с газика, выберите один риз, не перезаказывая ТВС, - риз добавится в хранилище после газика. А в таблице будет в правильном порядке показано.
Nikolay написал: А почему она там должна выскочить?
Выше предположил, что если из колбека вызвать (не любую, естественно) функцию квика, то эта функция может (неожиданно для всех) дернуть GetMessage, получить уведомление о новых данных и тут же на радостях дернуть соответствующий колбек, в результате получим вложенное выполнение колбека внутри другого колбека. Вот и хотел такое поведение спровоцировать. Очереди колбеков, насколько понимаю, специальной нет, как сериализатор используется общая очередь виндовых сообщений основного потока, wt_de шлет туда сообщения о приехавших данных, основной поток их выбирает и тут же дергает соответствующие колбеки, т.е. да, после "отвисания" очередь будет обрабатываться в порядке прихода сообщений, тут вопрос, не может ли она "отвиснуть" внутри колбека.
Идея с рекурсией не подтверждается пока, потестил с перезаказами ТВС следующим скриптом
Код
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) попробовал, загружает квик до почти зависания, но это и логично, надо бы чем-нибудь поинтересней.
Старатель написал: А вот, где поток третьего скрипта, не пойму, может прибит уже?
А и правда, где мейн-то от зависшего скрипта. Если прибит, то теория с прибитием скрипта "под ковром" получает плюсик в свою пользу.
Что касается воркеров на скринах, они похожи на воркеры от gdi+ и от COM, первые ждут заданий на отрисовку, вторые маршалят вызовы через свою очередь сообщений, там своя жизнь и, думаю, арка там ничего не меняла, так что пока можно туда не смотреть.
Цитата
Старатель написал: Но зависает не скоро, и написать демонстрационный скрипт, гарантированно приводящий к зависанию в течение разумного времени после запуска не представляется возможным.
Так вот и да. Как раз бесконечную рекурсию я и пробовал как способ захватить лок луа и не выпускать его, также пробовал насильно превысить лимит си-вызовов, но никаких падений-зависаний не словил, в этом случае квик все поймал и четко зачистил. Возможно, в более нагруженном окружении словил бы чего, дык это еще надо как-то устроить.
Старатель написал: За эту версию говорит также тот довод, что неоднократно похожая проблема наблюдалась на скриптах из этой темы .
Там совсем другая картина, квик пытается получить колбек через lua_getglobal и падает из-за access violation. В этой теме проблема аналогичная, только на вызове не OnAllTrade, а OnParam, в остальном один в один. Думал, как оно может случиться, придумал только одно: если колбек вызывает какие-то функции квика, в процессе обработки такой функции квик может получить новое сообщение и вызвать колбек (возможно, другой) рекурсивно. Удерживаемый лок здесь не поможет, поток-то тот же самый. В некоторый момент лимит си-вызовов будет превышен, луа бросит ошибку, квик прибьет скрипт (и его стейты, естественно), рекурсия начнет раскручиваться в обратную сторону, вытесненные колбеки попытаются продолжить работу, а стейтов уже нет, вот и access violation.
Здесь же видим типичный дедлок. Возможно, базовая природа в обоих случаях та же, просто проявления разные, смотря на каком этапе находится колбек в момент ошибки. Впрочем, это надо поверять тестами все, пока только гипотезы.
Старатель, дополню. Квик хочет вызвать колбек OnAllTrade и заполняет для него табличку с очередной сделкой. Все данные из ТВС уже вытащены, почти вся табличка заполнена, в последнюю очередь квик создает вложенную табличку datetime, пытается воткнуть в нее строку "day" и в этот момент виснет на lua_pushstring, как написал выше. В общем, это все мало что дает, косяк где-то в синхронизации все равно.
Можно сказать, что зависло в функции 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), то она и вызывается, иначе синхронизация пропускается и все. Как-зачем-почему не знаю, догадками мусорить не буду.
nikolz написал: если не доказано как это исправить
На мой взгляд, тут исправлять ничего не нужно, кроме известных косяков. Свет клином не сошелся на qlua, его возможностей достаточно, чтобы вытащить нужные данные и ввести заявки, а все остальное можно делать снаружи, не загоняясь конкретной реализацией луа в квике, привычным, хорошо натренированным способом для конкретного исполнителя. Либо, если надо модифицировать поведение самого квика (что уже подразумевает некоторую хачность решения и привязку к версии), qlua дает официальный способ затащить в процесс длл и даже получать некоторые полезные уведомления. Если не пытаться строить космический корабль из запорожца, то все нормально. Опять же это то же самое создание изолированных задач, и это хорошая архитектура, если, конечно, нет желания переписывать все и вся под каждую новую версию квика.
Цитата
nikolz написал: Я достаточно подробно изучал исходники луа, что решить проблему много поточности в своих приложениях и ускорения обмена данными из луа в си.
По-любому все потоки в квике, привязанные к луа, будут шарить одну луа-машину и, таким образом, выполняться по очереди. Поэтому я не понимаю цели подобных изысканий. Ну, создали еще 100 потоков, получили 100 дополнительных переключений контекста, поймали и героически преодолели кучку косяков с синхронизацией. И в чем профит? Кроме, конечно, прокачивания скилла, тоже занятие не сказать чтобы вредное, если есть куда потом этот скилл продать.
Дамп загружается в вижл студию и запускается на отладку, будет показан колстек, а дальше сопоставляем дизасм с сорцами луа и вычисляем, где какая функция. Достаточно просто с некоторым навыком. Были б символы от квика, было б вообще просто, да кто ж их даст )
Эти файлы не должны конечному пользователю поставляться. Надо собирать в релизной конфигурации, если хотите, чтобы у кого-нибудь кроме вас тоже работало.
Евгений, подождите уж пару дней, доберусь до места и выложу.
Получается имена мейна и онстопа - нил, имя вложенной функции находит правильно. Как теория и предполагала. С внешними и с SearchItems до кучи, чтобы два раза не вставать, результаты в комментариях
Код
local run = true
function comparator(lhs, rhs)
local t = debug.getinfo(1)
message('comparator name is ' .. (t.name or 'nil'))
return true
end
local function local_comparator(lhs, rhs)
local t = debug.getinfo(1)
message('local_comparator name is ' .. (t.name or 'nil'))
return true
end
function main()
while run do
sleep(100)
end
end
function OnStop()
local function very_local_comparator(lhs, rhs)
local t = debug.getinfo(1)
message('very_local_comparator name is ' .. (t.name or 'nil'))
return true
end
comparator(1, 2) -- MSG: comparator
local_comparator(1, 2) -- MSG: local_comparator
very_local_comparator(1, 2) -- MSG: very_local_comparator
SearchItems('all_trades', 0, 1, comparator) -- MSG: nil
SearchItems('all_trades', 0, 1, local_comparator) -- MSG: nil
SearchItems('all_trades', 0, 1, very_local_comparator) -- MSG: nil
run = false
end
local run = true
function main()
local t = debug.getinfo(1)
message('main name is ' .. (t.name or 'nil'))
while run do
sleep(100)
end
end
function OnStop()
local t = debug.getinfo(1)
message('OnStop name is ' .. (t.name or 'nil'))
local function inner()
local t = debug.getinfo(1)
message('OnStop::inner name is ' .. (t.name or 'nil'))
end
inner()
run = false
end
s_mike@rambler.ru написал: в getinfo по функции иногда не фигурирует ее название
По ссылке выше как раз ответ, если функция вызвана через pcall, дебаггер ее имя не найдет. От себя добавлю, что в квике все колбеки вызываются через pcall, инфа сотка.
nikolz написал: В итоге любой буратино может легко реализовать свой гениальный алгоритм
Вот с этим у буратин и проблемы, поэтому буратина начинает городить фреймворк, загоняться микросекундами при выставлении заявок и прочая и прочая, а до собственно торгового алгоритма у него дело никогда не доходит. Так что не ломайте людям кайф, пусть вылизывают годами своих чудо-роботов в надежде, что когда-нибудь их доделают и вот тогда уже даааа.
Предположу, что в одних местах функция вызвана "посередине", а в других происходит tail call, в этом случае уровень 1 заглядывает на функцию выше и, соответственно, локальных имен вызывающей функции не видит.
Поглядел (сорри, любопытство), как под копирку: qlua хочет вызвать OnParam, дергает lua_getglobal и крэшится в ней на return auxgetstr в момент записи на текущий стек (последняя строчка в lua_getglobal). По другому поводу дамп воспроизводил и была та же история. Как бы это довольно хорошая новость, все у всех падает в одном и том же месте. С другой стороны, причина-то падения нехорошая (очевидно, разделение стейта между потоками).
Сергей написал: А если я локальную объявляю раньше SetTableNotificationCallback() ?
Тогда все нормально будет. Надо просто понять, локальная относительно чего. Варианты:
Код
function Global() -- глобальная, видна из всех функций и файлов
end
local function One() -- локальная относительно этого файла, не видна из других файлов (подключенных через require или еще как)
local function Two() -- локальная относительно функции One, не видна вне функции One
local function Three() -- локальная относительно функции Two, не видна вне функции Two
-- и так далее
end
end
end
Таки есть. В списке аргументов SetTableNotificationCallback переменная f12 еще не объявлена, вы по факту nil устанавливаете в качестве колбека. Надо так
Сергей написал: Изменил регистр - не работаетУбрал функцию clear() - не работает
TableID и TableId это разные переменные. Вы в одну сохраняете идентификатор таблицы, а потом используете другую (которая nil скорее всего) для очистки и добавления строк.
Imersio Arrigo написал: по-быстренькому найти всё проверки
Их можно заранее найти на своем квике, бинарники-то одинаковые. В любом случае защита (только) на клиентской стороне ломается с большими или меньшими усилиями. Посмотрите, как обеспечивается DRM (гуглить TPM, trusted platform module), навертели на железном уровне аж, иначе никак. А против несложной защиты от случайно забредшего одмина я и не возражал.
Думаю, для того, кто сломал сервер, это будет очень легкое задание. А чтобы мимопроходилы не подглядывали, почему бы нет. Как вариант реализации: по некой кнопке прячем окно квика и выводим иконку в трее. По клику на иконку выводим диалог с паролем, если введен правильно - показываем окно квика. Это, в общем-то, можно и без арки сделать, чисто внешним приложением.
nikolz написал: Проблема многопоточности и проблема синхронизации потоков - это синонимы.
Да, так. Надо пойти дальше и спросить, а в чем проблема синхронизации потоков. Она в синхронизации доступа к данным и только. У каждого потока есть свой (нативный) стек, поток также может выделить tls из кучи, и если он никуда больше не ходит, ему никакая синхронизация не требуется. Как только появляется какое-то разделяемое между потоками состояние, возникает проблема синхронизации доступа к нему. Посмотрим на примере. Берем два потока, совместно использующих один int. Если потоки лезут в этот int без синхронизации, имеем проблемы (даже int с точки зрения процессора не атомарен, атомарно можно его прочитать, но модифицировать уже нет). Защищаем int критической секцией. Все, проблем нет. Но если у нас два разделяемых int'а, появляется выбор: одна общая критическая секция на все разделяемое состояние или у каждого поля своя. Второй вариант это отличный способ огрести проблем: во-первых, появляется возможность дедлока, во-вторых, появляется проблема атомарности изменения обоих полей сразу. То есть лучше выбрать вариант раз с одной критической секцией на все разделяемое состояние, ценой более длительных блокировок всех потоков. В луа самом по себе вариант раз и использован, но есть нюансы*. А в квике получился вариант два, результаты которого в основном и вылезают то тут то там. Внутри квика это еще можно разрулить, зная, что и в каком порядке лочить, но как только к этой запутанной системе дан доступ внешнему кодеру через луа, открыт ящик пандоры, кодер завсегда придумает, как упорядочить свои обращения к состоянию так, чтобы повесить весь квик или получить неконсистентные результаты. Автор топика не раз просил арку дать возможность синхронизации из луа, но это, увы, не решит проблему, в квике нет одного общего лока на все, а еще один лок сделает только хуже.
Радикальный способ решения проблем - как раз приведенный в статье, межпоточное взаимодействие, по определению требующее синхронизации, локализуется в виде очередей сообщений, а каждый поток работает только со своим состоянием и ему никакая дополнительная синхронизация не нужна в принципе. Это еще не процессы (луа-то общий и нативный процесс тоже общий, хоть авторы и выбрали путающую терминологию), это просто собрали всю синхронизацию в одном месте и избавили конечного кодера от размышлений о ней. Как конкретно решили мне не нравится, но путь правильный.
Цитата
nikolz написал: Проблемы такого решения хорошо известны.
Можно ссылок или ключевиков? Винда как-то работает на сообщениях и ничего страшного, даже порт завершения позже придумали, то есть вперли-таки сообщения в само ядро (и молодцы).
* Если посмотреть в сорцы луа, увидим прекрасную вещь: сам по себе интерпретатор всегда работает под локом (пруф: поищите lua_lock /lua_unlock в этой функции). То есть, если выполняется только луа-код без вызовов функций, он весь атомарен (и другие потоки ждут). Лок снимается только перед вызовом си-функции. При этом встроенные библиотеки луа тоже состоят из си-функций, поэтому лок может быть снят (и тут же захвачен снова библиотечной функцией) в непредсказуемых местах, что убивает-таки атомарность изменения состояния как целого даже в луа без квика, при этом средств синхронизации со стороны луа не предоставлено. То есть луа защищает консистентность своего состояния, но не консистентность состояния пользовательского кода, с точки зрения луа-кодера он в той же ситуации, в какой и без луа, вот тебе потоки и рули как хочешь. Поэтому и говорю, что проблема не луа-специфичная, проблема в ожиданиях от луа чего-то, чего он не дает, хоть в нем и есть lua_lock.