1

Тема: Исследование порядка вызовов Callback-функций в QLua

(Обновлено 28.12.2012 )

[url=https://quik2dde.ru/viewtopic.php?id=18]Как подключать свои DLL-модули к Lua (QLua) и вызывать из этой Dll свои функции[/url] - подробно написано в отдельной теме.

В данном случае подключить DLL мне понадобилось для того, чтобы получить доступ к WinAPI, в частности для исследования в каких потоках какие функции обратного вызова вызываются из QLua.

А сегодня вот о чем. Сделаем такой LUA-скрипт (полный исходник можно скачать по ссылке в конце этого поста):

local testc = require("testc")

f = nil
is_run = true

function writeTyreadID(proc_name)
    f:write(os.date() .. " ".. proc_name .. ":" .. tostring(testc:GetCurrentThreadId()) .. "\n")
    f:flush()
end

function OnInit(path)
    f = io.open(path .. ".out", "w+t")
    writeTyreadID("OnInit")
end

function OnStop(signal)
    writeTyreadID("OnStop")
    is_run = false
end

function main()
    writeTyreadID("main start")
    while is_run do
        sleep(50)
    end
    writeTyreadID("main stop")
    f:close()
end

-------------------

function OnAccountBalance(acc_bal)
    writeTyreadID("OnAccountBalance")
end

function OnAccountPosition(acc_pos)
    writeTyreadID("OnAccountPosition")
end
..........

Под чертой я просто вставил все обработчики, какие перечислены в документации.
Что мы тут видим: подключение внешнего модуля, потом типичная теперь уже функция main() и обработчики инициализации OnInit () и остановки скрипта OnStop, функция writeTyreadID() для записи в файл имени обработчика и ID потока, в рамках которого он вызван. За одно пишется время, чтобы было удобно потом смотреть что и когда происходило, сопоставлять. Идея всей этой затеи с логированием  всех обработчиков была в том, чтобы обнаружить те из них, которые, возможно, вызываются в разных потоках, а значит обрабатываются параллельно.
В итоге в результате запуска и остановки скрипта получился такой лог, повторяющиеся строки я здесь вырезал (полный лог пишется в файле test-all-on.lua.out рядом со скриптом):

12/19/12 09:35:52 OnInit:2584
12/19/12 09:35:52 main start:2592
12/19/12 09:36:25 OnConnected:2584
12/19/12 09:36:25 OnParam:2584
12/19/12 09:36:25 OnParam:2584
12/19/12 09:36:25 OnQuote:2584
12/19/12 09:36:25 OnQuote:2584
12/19/12 09:36:25 OnDepoLimit:2584
12/19/12 09:36:25 OnDepoLimit:2584
12/19/12 09:36:26 OnParam:2584
12/19/12 09:36:32 OnQuote:2584
12/19/12 09:36:33 OnParam:2584
12/19/12 09:36:38 OnFuturesLimitChange:2584
12/19/12 09:36:39 OnParam:2584
12/19/12 09:36:40 OnParam:2584
12/19/12 09:36:38 OnFuturesLimitChange:2584
12/19/12 09:36:40 OnParam:2584
12/19/12 09:37:23 OnQuote:2584
12/19/12 09:37:30 OnParam:2584
12/19/12 09:37:30 OnTrade:2584
12/19/12 09:37:30 OnOrder:2584
12/19/12 09:37:30 OnDepoLimit:2584
12/19/12 09:37:31 OnParam:2584
12/19/12 09:38:33 OnAllTrade:2584
12/19/12 09:38:33 OnAllTrade:2584
12/19/12 09:38:33 OnAllTrade:2584
12/19/12 09:38:36 OnAllTrade:2584
12/19/12 09:38:57 OnParam:2584
12/19/12 09:38:58 OnParam:2584
12/19/12 09:39:02 OnStop:2584
12/19/12 09:39:02 main stop:2592

Из него и нарисовалась [url=https://quik2dde.ru/viewtopic.php?id=16]картинка в соседней теме "Как устроено Lua в QUIK[/url], правда к моменту ее написания все обработчики я еще не посмотрел.
Увы, к большому сожалению, документация и свежие сообщения на форуме от разработчиков в данном случае нас не обманывали: в отдельном потоке (с отличающимся ID потока) выполняется только функция main(), все остальные обработчики (во всяком случае все, вызов которых удалось увидеть) вызываются в рамках одного (основного) потока терминала QUIK. При этом при каждом запуске даже одного и того же скрипта ID потока в main() каждый раз разный, значит при каждом запуске создается новый поток OC Windows. Для других обработчиков ID потока остается неизменным, т.е. используется один и то же основной поток QUIK.

Обещанные в начале исходники:
[url=https://quik2dde.ru/static-img/100/test-all-on.zip]Полный скрипт и готовая DLL-ка в архиве[/url].

2

Re: Исследование порядка вызовов Callback-функций в QLua

< reserved >

3

Re: Исследование порядка вызовов Callback-функций в QLua

Результат чего и в каком виде вы хотите получить? не понятно.

Код VCLua - он доступен и не так уж велик.
Родить поток или контекст виртуальной машины магическими пассами - нельзя; можно это сделать лишь вызовом определённых всем известных функций. Достаточно убедиться, что таких функций в коде не встречается, что не сложно - и ответ станет очевиден на 100%.

Более того. Обычно никто в здравом уме не плодит ни потоки, ни новые контексты, используя Lua. Хотя бы потому, что это весьма нетривиально и требует от всех участников весьма высокого уровня знаний и умений. (Как пример: тьма проблем в QUIK, которые они не так давно кое-как смогли выгрести из своей многопоточной (зачем?!) реализации в Lua. Плюс тьма оставшихся принципиальных проблем, когда идет обращение к одним и тем же переменным (обычно созданным Lua-программистам таблицам), к которым осуществляется доступ как из main(), так и из callback'ов. Но эти проблемы уже так и останутся на совести тех, кто в Lua пишет роботов и им явно не позавидуешь.)

Так что бояться, что кто-то где-то в другой библиотеке начнёт такое городить - не стоит.

Что же до нестабильностей VCLua, то я вижу три их природы:

    4

    Re: Исследование порядка вызовов Callback-функций в QLua

    sam063rus пишет:

    я ничего не путаю. если ты 100% уверенный - то, где результат? почему vcl не стабильна? (без обид)
    Лично я - не 100% уверен и даже не 70. Я пытаюсь рассматривать и принимать во внимание все варианты. То что, любой код вызывается и работает в контексте вызвавшего его потока ещё не говорит о том, что вызываемый код сам не плодит потоки и дочерние/не дочерние контексты виртуальных машин. для меня тут - масса вопросов. насчёт уже якобы составленной карты потоков - я пока не вижу, что она отражает реальную картину. также, я считаю крайне важно оценить/измерить/учесть влияние главного контекста виртальной машины на поведение порождённых. как они влияют друг на друга в квике, как и в какой момент, в какой последовательности разрушаются и полностью ли, как это сказывается на других контекстах, почему квик не отрабатывает или не зарегистрировал должным образом функцию обработки исключений, почему всего один скрипт может уничтожить работу всех остальных скриптов, а значит и виртуальных машин, а также ввести квик в ступор. и чтобы на эти вопросы ответить я предлагаю детально изучить механизм работы скриптов в квике, а начать разумеется, с вышеописанного. таким образом, по результатам исследований, у нас будет аргументированное требование к разработчикам.

    Давайте я попробую тезисно объяснить некоторые вещи.
    1. каждый скрипт выполняется максимум в двух потоках - колбеки и main()
    2. Никакая вызываемая функция не порождает дополнительные потоки ОС
    3. Полноценно обработать все исключения не представляется возможным, тут как повезет.
    И еще по поводу vcl, почему стали происходить падения. Анализ дампов показывает, что после закрытия Lua ( lua_close() ) происходит попытка вызова какого-то кода из уже отгруженной библиотеки, но детально я в этом не разбирался.

    5 (2015-02-02 10:50:22 отредактировано swerg)

    Re: Исследование порядка вызовов Callback-функций в QLua

    mbul пишет:

    2. Никакая вызываемая функция не порождает дополнительные потоки ОС

    Ну справедливости ради стоит наверное можно сказать, что в общем случае какая-либо библиотека вполне может родить потоки; но мало кто в здравом уме захочет идти этим тернистым путём wink

    mbul пишет:

    Анализ дампов показывает, что после закрытия Lua ( lua_close() ) происходит попытка вызова какого-то кода из уже отгруженной библиотеки, но детально я в этом не разбирался.

    А вот это вполне перекликается с моим пунктом номер 2, только с другого боку.
    Михаил, может подскажете как можно отловить это lua_close() или разрушение Lua-объекта? в смысле отловить со стороны библиотеки (фактически со стороны Lua-скрипта).

    6 (2015-02-02 11:24:46 отредактировано mbul)

    Re: Исследование порядка вызовов Callback-функций в QLua

    С ходу могу предложить только отлавливать в vcl событие DLL_THREAD_DETACH

    7

    Re: Исследование порядка вызовов Callback-функций в QLua

    Я несколько другое спрашивал, хотя это тоже надо будет посмотреть.
    Все "объекты" в VCLua, да и в Lua вообще, есть метатаблицы.
    Можно ли отловить событие, когда метатаблица уничтожается Lua?

    8

    Re: Исследование порядка вызовов Callback-функций в QLua

    Насколько я помню, для данных типа userdata можно определить метаметод __gc(), который будет вызываться при удалении. Я поискал в исходниках VCL и не нашел такой подстроки.

    9

    Re: Исследование порядка вызовов Callback-функций в QLua

    sam063rus пишет:

    ну-ну... создай форму в коллбеках...

    В чем проблема?

    VCL = require "qvcl"
    
    is_run = true
    
    function main()
      while is_run do
        sleep(50)
      end
    end
    
    function OnStop()
      is_run = false
    end
    
    function OnOrder(order)
      local form =  VCL.Form( {
                Height = 100, Width = 200, Caption = "",
                Position = "poDefaultPosOnly",
                OnClose = function (sender) sender:Release() end
      } )
      local label = VCL.Label( form, { top=0, left=0 } )
      label.Caption = tostring(order.order_num) .. " " .. order.sec_code .. " " .. tostring(order.qty)
      form:Show()
    end

    10

    Re: Исследование порядка вызовов Callback-функций в QLua

    Михаил, еще вопрос.
    Я верно понимаю, что QUIK вызывает lua_close() ? Ну т.е. вызывает однозначно, но хотелось бы уточнить один момент.
    В какой момент вызывается lua_close()? Менялась ли логика (момент) вызова этой функции, и если да - то в какой версии?

    11

    Re: Исследование порядка вызовов Callback-функций в QLua

    swerg пишет:

    Михаил, еще вопрос.
    Я верно понимаю, что QUIK вызывает lua_close() ? Ну т.е. вызывает однозначно, но хотелось бы уточнить один момент.
    В какой момент вызывается lua_close()? Менялась ли логика (момент) вызова этой функции, и если да - то в какой версии?

    Начиная с версии 6.16 lua_close() вызывается после завершения main() в том же потоке. так же lua_close() может быть вызвана в основном потоке терминала после ручной остановки скрипта, если main() не завершилась корректно (поток был завершен принудительно)

    12

    Re: Исследование порядка вызовов Callback-функций в QLua

    mbul пишет:

    Начиная с версии 6.16 lua_close() вызывается после завершения main() в том же потоке.

    В том же - в смысле в потоке main() ?!
    А lua_open() где делается?
    Хорошо ли это, что lua_close() в дочернем потоке? это наверняка ведь получается, что запускающийся в этот момент сборщик мусора тоже запустится в неосновном потоке?

    13

    Re: Исследование порядка вызовов Callback-функций в QLua

    swerg пишет:
    mbul пишет:

    Начиная с версии 6.16 lua_close() вызывается после завершения main() в том же потоке.

    В том же - в смысле в потоке main() ?!
    А lua_open() где делается?
    Хорошо ли это, что lua_close() в дочернем потоке? это наверняка ведь получается, что запускающийся в этот момент сборщик мусора тоже запустится в неосновном потоке?

    lua_open вызывается в момент нажатия кнопки старт. то есть в потоке терминала. Но в случае с lua_open разницы нет где ее вызвать, сама эта функция ничего особенного не делает. Загрузки библиотек начинаются с вызова require или loadlib в скрипте.
    Можно предусмотреть в VCLua какой-нибудь финализатор? Возможно либо в виде явной потокобезопасной функции для вызова из скрипта, либо как реакцию на событие THREAD_DETACH или PROCESS_DETACH.
    То решение, которое я предложил и дал паре человек попробовать- это скорее "грязный хак". Времени разбираться не было, да и в Дельфи не особенно силен, поэтому выглядит так:

    library vcl;
    
    {$mode Delphi}{$H+}
    {$i vcldef.inc}
    
    {$R *.res}
    
    uses
      Windows,
      Classes, SysUtils,
      Interfaces, InterfaceBase,
      Forms, Controls, Graphics, Dialogs,
      {$i vcl.inc}
    
    function luaopen_qvcl(L: Plua_State): Integer; cdecl;
    begin
      LoadLibrary('qvcl.dll');
    ...

    Побочный эффект - библиотека не выгружается при остановке скрипта.

    14

    Re: Исследование порядка вызовов Callback-функций в QLua

    Михаил, я, возможно, не совсем точно задал вопрос.
    Меня интересует подробнее где, когда и в каком потоке и вызывается lua_close()
    Ведь именно в ней проблемы.

    А всякие THREAD_DETACH или PROCESS_DETACH - они и так обрабатывается, разумеется. И именно в этом и "проблема".
    К сожалению понять в каком потоке все это вызывается экспериментально не так просто, потому и хотелось бы пояснений от разработчиков.

    15

    Re: Исследование порядка вызовов Callback-функций в QLua

    swerg пишет:

    Михаил, я, возможно, не совсем точно задал вопрос.
    Меня интересует подробнее где, когда и в каком потоке и вызывается lua_close()
    Ведь именно в ней проблемы.

    А всякие THREAD_DETACH или PROCESS_DETACH - они и так обрабатывается, разумеется. И именно в этом и "проблема".
    К сожалению понять в каком потоке все это вызывается экспериментально не так просто, потому и хотелось бы пояснений от разработчиков.

    lua_close можеть быть вызвана:
    1. при корректном завершении функции main() в том же потоке. в котором выполнялась эта функция.
    например для такого кода:

    function main()
     return
    end

    или вот такого:

    stopped = false
    function OnStop(signal)
     stopped = true
    end
    function main()
     while not stopped do sleep(100) end 
    end

    2. для такого кода:

    function main()
     while true do  sleep (100) end
    end

    lua_close будет вызвана из основного потока терминала, после ожидания дефолтного тайм-аута и принудительного завершения потока для main()

    16

    Re: Исследование порядка вызовов Callback-функций в QLua

    Понял, спасибо за ответ.

    Теперь возник новый вопрос.
    Наверное это неправильно инициализировать что-либо в одном потоке, а деинициализировать это же - в другом?

    от admin: вопрос перформулировал, наезды убрал, впредь буду банить за такое.

    17 (2015-02-04 10:14:13 отредактировано swerg)

    Re: Исследование порядка вызовов Callback-функций в QLua

    Провел эксперимент.
    Если main() корректно не завершать, то qvclua работает отлично, запускается по несколько скриптов с окошками, надёжно работают, завершаются по кнопке "Остановить" в диалоге скриптов. Потому что деинициализация библиотеки корректно происходит в основном потоке.

    Если же корректно по какому-либо событию завершать main() - то в этот момент терминал довольно часто падает, стек падения показывает куда-то в обработку сообщений.

    Оно и понятно.
    В основной поток Windows накидала финализирующих сообщений, какой-то сообщение обрабатывается оконной функцией - и тут вдруг из другого потока хрясь - выгружают библиотеку. Понятно, адреса, где вот прямо сейчас выполняется основной поток (оконная функция), становятся невалидными, и терминал тут же сваливается по доступу к невалидной памяти.
    А разработчики потом ен стесняясь пишут мол "это у вас падает где-то библиотека, мы ни при чем".

    Если выбить лестницу - то электрононтёр свалится со столба, но он сам в этом виноват ведь, правда? это ведь он упал.

    "О сколько нам открытий чудных..."

    Рекомендация пользователям QVCLua:
    При использовании библиотеки в 6.16 версии терминала - просто уберите из кода Lua скрипта завершение main(), т.е. код в ней должен быть пока такой, например:

    function main()
      while is_run or true do  -- когда починят - уберем or true
        sleep(50)
      end
    end
    function OnStop()
      is_run = false
      mainForm:Release() -- уничтожим основную форму скрипта
    end

    Завершаем скрипт по кнопке "Остановить". В этом случае всё работает корректно, падений не замечено.


    Вопрос:
    Михаил, можно ли надеяться, что это безобразие будет исправлено в ближайших версиях терминала, и вызов lua_close() будет при любом сценарии завершения скрипта всегда выполняться в основном потоке терминала, т.е. там же, где и lua_open()?
    Вы ведь согласны, надеюсь, что сделанный в 6.16 вариант - не нормален?

    18

    Re: Исследование порядка вызовов Callback-функций в QLua

    Значит так.
    swerg и sam063rus идут [url=https://quik2dde.ru/misc.php?action=rules]перечитывать правила[/url].
    Кратко:

    • недопустимы оскорбления, ни в каком виде никого из пользователей;

    • желательно не общаться на "ты"; то, что где-то "так общаются, и ничего" - не аргумент, тащить сюда гадость - не стоит.

    Надеюсь, удастся обойтись без бана кого-либо.
    swerg, быть может вы не заметили, что sam063rus выкладывает качественные переводы?

    Флуд удалён.

    19

    Re: Исследование порядка вызовов Callback-функций в QLua

    swerg пишет:

    Вопрос:
    Михаил, можно ли надеяться, что это безобразие будет исправлено в ближайших версиях терминала, и вызов lua_close() будет при любом сценарии завершения скрипта всегда выполняться в основном потоке терминала, т.е. там же, где и lua_open()?
    Вы ведь согласны, надеюсь, что сделанный в 6.16 вариант - не нормален?

    В текущей ситуации скрипты работают по принципу "fire and forget" - пока просто нет других способов. Если это облегчит жизнь, то попробуем что-нибудь придумать.

    20

    Re: Исследование порядка вызовов Callback-функций в QLua

    Как вы видите, выше есть пример того, что изменение логики очень поможет. (пример про "незаканчивающийся main" и стабильную работу скрипта при этом).
    Этот же пример, на мой взгляд, подсказывает нам, что изменить логику вроде не должно составить для вас труда, т.к. желаемый вариант уже фактически реализован в одном из сценариев.

    21 (2016-09-29 11:24:24 отредактировано sam063rus)

    Re: Исследование порядка вызовов Callback-функций в QLua

    -

    22 (2015-06-13 12:06:08 отредактировано CyberTrader)

    Re: Исследование порядка вызовов Callback-функций в QLua

    swerg пишет:

    while is_run or true do  -- когда починят - уберем or true

    В версии 6.17.1.17 вроде починили.

    23 (2015-10-25 14:53:24 отредактировано CyberTrader)

    Re: Исследование порядка вызовов Callback-функций в QLua

    CyberTrader пишет:
    swerg пишет:

    while is_run or true do  -- когда починят - уберем or true

    В версии 6.17.1.17 вроде починили.

    Поторопился я с выводом: при закрытии формы скрипты продолжают крашить терминал без использования "костылей".

    Сегодня обнаружилась ещё одна неприятная "особенность": после экспериментов с библиотекой qvcl уже через длительное время после остановки всех скриптов терминал самопроизвольно закрылся без вывода каких-либо сообщений. Причём закрылся он аварийно, т.к. не были сохранены изменения последнего сеанса работы.

    24

    Re: Исследование порядка вызовов Callback-функций в QLua

    mbul пишет:

    То решение, которое я предложил и дал паре человек попробовать- это скорее "грязный хак". Времени разбираться не было, да и в Дельфи не особенно силен, поэтому выглядит так:

    library vcl;
    
    {$mode Delphi}{$H+}
    {$i vcldef.inc}
    
    {$R *.res}
    
    uses
      Windows,
      Classes, SysUtils,
      Interfaces, InterfaceBase,
      Forms, Controls, Graphics, Dialogs,
      {$i vcl.inc}
    
    function luaopen_qvcl(L: Plua_State): Integer; cdecl;
    begin
      LoadLibrary('qvcl.dll');
    ...

    Побочный эффект - библиотека не выгружается при остановке скрипта.

    Это прекрасно работает, и уже не один месяц, ещё раз спасибо!
    Подскажите пожалуйста, это всё, что Вы дописали в библиотеку? В какой файл исходников? Дело в том, что swerg периодически добавляет в свои исходники всё новые полезные изменения, а у меня только Ваша скомпилированная версия, которая немного подотстала от жизни )

    25

    Re: Исследование порядка вызовов Callback-функций в QLua

    Позвольте я отвечу.
    Да, это всё, что добавлено для стабильности.
    А что, в самом деле так много стабильнее?