View Issue Details

IDProjectCategoryView StatusLast Update
0002511Far ManagerPlugins APIpublic2016-09-29 17:08
ReporterNightRoman Assigned Tovskirdin  
PrioritynormalSeverityblockReproducibilityalways
Status closedResolutionfixed 
Product Version3.0 
Fixed in Version3.0 
Summary0002511: ACTL_GETWINDOWINFO зависает при вызове для диалога при недавнем наличии другого диалога
DescriptionПроблема проявляется в модуле PowerShellFar плагина FarNet.

В панелях:
- Открыть диалог Apply command (CtrlG)
- F11 \ PowerShellFar \ Invoke input code...
- В диалоге ввести код $Far.Dialog, нажать Enter

==> После закрытия второго диалога обрабатывается код получения $Far.Dialog. Там есть вызов ACTL_GETWINDOWINFO, который зависает.

Связано или нет, был тикет http://bugs.farmanager.com/view.php?id=2241 , вдруг связано. Там так и не понятно осталось, кто виноват. Мне думается, что Фар виноват в дедлоке, а не плагин, что вызывает Фар из другого потока. Плагин делает это корректно, так как основной поток в это время заморожен и конфликтов быть не должно. Там я выкрутился, просто отключив часть функциональности для диалога авто-дополнения, не так жалко. В этом же случае так поступать не хотелось бы.
TagsNo tags attached.
Build3839

Activities

NightRoman

2014-03-15 14:10

reporter   bugnote:0011937

Есть надежда, что на это будет обращено внимание? Что-то очень неправильно стало работать. Именно стало, когда-то это работало. Таким способом из PowerShell исследовались данные диалогов Фара (Id и так далее). Теперь этот пласт функциональности отвалился. Когда произошло, не знаю. Используется это редко (но метко).

Как мне поступить? Если проблема остается, то надо отрубить в FarNet и PowerShell значительный пласт возможностей. Нельзя же чтоб пользователь был вынужден прибивать Фар с остальной незаконченной работой. Не хотелось бы отрубать, оно работало и было полезно.

Проявляется на многих диалогах, не только на CtrlG. Попробовал на атрибутах файлов, тоже зависает.

shmuel

2014-03-15 15:09

developer   bugnote:0011938

А нельзя ли тест-кейс на Lua соорудить? Мне, например, по описанию вообще не понятно, в чём дело. Думаю, что шансы на исправление (если баг действительно имеет место) повысились бы.

DrKnS

2014-03-15 15:22

administrator   bugnote:0011939

А лучше на C. С тщательным применением бритвы Оккама.

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

С такими входными данными надежды нет.

NightRoman

2014-03-15 15:28

reporter   bugnote:0011940

А Lua в основном потоке выполняет скрипт? В том, в каком Фар зовет плагин?

Дело (думается) вот в чем (но детали могут и другими оказаться, не пинайте). PowerShell свои скрипты выполняет не в основном потоке, а в новом. Основной при этом ничего не делает, ждет и может, например, снять тот поток при желании (клавиша пользователя и т.п.).

С точки зрения thread safe тут все в порядке, хотя есть несколько потоков, работает только один, пусть и не основной. Никаких конфликтов при вызовах Фара нет, вызовы могут быть только из одного потока. Хоть и не обязательно из того, что вызвал плагин.

Но Фар иногда может ошибочно предполагать, что обратные вызовы обязаны делаться из главного потока. Что может получиться, глупый пример, но не удивлюсь, если так и происходит - Фар входит в ошибочно добавленную в код критическую секцию и оттуда зовет плагин. Плагин зовет Фар, который зовет код, который тоже входит в эту секцию. Если этот вызов в том же потоке, то плагин работает (может, Lua, тогда в лоб не воспроизвести). Если этот вызов из другого потока (PSF), то вызов будет ждать освобождения этой секции бесконечно, дед лок.

Сумбурно немного или понятно примерно? Думается мне, что где-то в Фаре критическая секция либо не по делу, либо не аккуратно добавлена.

В PSF огромное количество всего работает, *всегда* из неосновного потока. Это на практике подтверждает, что вызов из основного потока не должен быть обязательством для плагина. Но и Фар не должен полагать, что вызовы будут из того же потока.

NightRoman

2014-03-15 15:35

reporter   bugnote:0011941

На написание примера на С у меня может уйти неограниченное количество времени, ибо на нем я мало пишу, а потоки не писал никогда. И при этом не факт, что удастся воспроизвести, по многим причинам может оказаться.

Но есть уже готовый пример проблемы, вот в тикете. При этом плагин ничего неправильного не делает. А проблема проявляется. Наверное, непросто ее разрешить, я понимаю это. Потоки - дел хитрое. Но написать что-то еще с этой проблемой - это не совсем способ ее разрешить.

DrKnS

2014-03-15 15:54

administrator   bugnote:0011942

> Но есть уже готовый пример проблемы, вот в тикете.

Вот я взял "готовый пример" и запустил:

> ntdll.dll!NtWaitForMultipleObjects() Unknown
     KernelBase.dll!WaitForMultipleObjectsEx() Unknown
     kernel32.dll!WaitForMultipleObjectsExImplementation() Unknown
     user32.dll!RealMsgWaitForMultipleObjectsEx() Unknown
     user32.dll!MsgWaitForMultipleObjectsEx() Unknown
     ole32.dll!CCliModalLoop::BlockFn(void * * ahEvent=0x00000000001983d4, unsigned long cEvents=2147549461, unsigned long * lpdwSignaled=0x000007feebea56d2) Line 1222 C++
     ole32.dll!CoWaitForMultipleHandles(unsigned long dwFlags=1, unsigned long dwTimeout=57605840, unsigned long cHandles=4294967294, void * * pHandles=0x00000000001983e0, unsigned long * lpdwindex=0x00000000001983d4) Line 109 C++
     clr.dll!MsgWaitHelper(int,void * *,int,unsigned long,int) Unknown
     clr.dll!Thread::DoAppropriateAptStateWait(int,void * *,int,unsigned long,enum WaitMode) Unknown
     clr.dll!Thread::DoAppropriateWaitWorker(int,void * *,int,unsigned long,enum WaitMode) Unknown
     clr.dll!Thread::DoAppropriateWait(int,void * *,int,unsigned long,enum WaitMode,struct PendingSync *) Unknown
     clr.dll!WaitHandleNative::CorWaitOneNative(class SafeHandle *,int,bool,bool) Unknown
     mscorlib.ni.dll!000007fee756cc34() Unknown

Это Main Thread.
Кода из far.exe тут нет вообще, поэтому вполне возможно предположить, что это плагин сам что-то ошибочно напредполагал.

> С точки зрения thread safe тут все в порядке, хотя есть несколько потоков, работает только один, пусть и не основной.

С точки зрения thread safe это полный пэ, т. к. любой такой вызов может изменить глобальное состояние, делая всё дальнейшее поведение программы неопределенным.

NightRoman

2014-03-15 16:01

reporter   bugnote:0011943

> С точки зрения thread safe это полный пэ, т. к. любой такой вызов может изменить глобальное состояние, делая всё дальнейшее поведение программы неопределенным.

Я не понимаю эту фразу. Если к Фару в данный момент времени из плагина обращается только один поток, то это thread safe доступ к Фару, никаких конфликтов с данными глобальными быть не должно. При этом в PSF основной поток вообще ничего с Фаром не делает, основной поток уходит в ядро PowerShell и он там просто ждет другой запущенный. Запущенный может делать с Фаром, что хочет, PowerShell до этого дела нет.

А в дебаггере входы в критические секции в Фаре кода есть, на которых висит?

DrKnS

2014-03-15 16:13

administrator   bugnote:0011944

> Я не понимаю эту фразу.

Ок, псевдокод:

main thread:

foreach(i, GetCurrentWindows())
{
  i.show();
}


your thread:

Mainthread.Suspend();
if (DoSomeJob())
  Message("Done");
Mainthread.Resume();

Технические детали:
- Message - это экземпляр window;
- все создаваемые экземпляр window помещаются в коллекцию;
- GetCurrentWindows() возвращает ссылку на эту коллекцию;
- изменение коллекции инвалидирует итераторы;

Внимание, вопрос:
что произойдёт после Mainthread.Resume()?

P.S. Я не говорю, что такой же или похожий код обязательно есть в far.exe и твоём плагине.
Но он может там быть или может появиться в любой момент.

NightRoman

2014-03-15 16:17

reporter   bugnote:0011945

Висит вот тут

C:\-\GIT\FarManager3\unicode_far\dialog.cpp(4431):
SCOPED_ACTION(CriticalSectionLock)(CS);

Попробую понять, почему.

NightRoman

2014-03-15 16:23

reporter   bugnote:0011946

Тупо закомментил все эти секции. Данный пример заработал.

Я не говорю, что это решение проблемы.

NightRoman

2014-03-15 16:30

reporter   bugnote:0011947

Вообще, сам факт, что эти секции введены в код, говорит о том, что он даже задуман для работы из разных потоков. Иначе зачем эти секции. Но вот что-то идет не так, как задумано. Хорошо бы разобраться. Пытаюсь, но я в коде Фара очень слаб. В своем тоже ищу подвох. Но не находится, потому и пришел сюда.

DrKnS

2014-03-15 16:36

administrator   bugnote:0011948

> Иначе зачем эти секции.
Это следствие того, что поиск файлов (Alt+F7) выполняется в отдельном потоке и обновляет диалог. Их расставили когда-то давно в объеме, достаточном для того, чтобы хотя бы поиск не падал, и на этом остановились.

А дедлок, очевидно, происходит потому, что эту же секцию уже захватил один из твоих дополнительных потоков.

NightRoman

2014-03-15 16:44

reporter   bugnote:0011949

> А дедлок, очевидно, происходит потому, что эту же секцию уже захватил один из твоих дополнительных потоков.

А как это возможно? Работает с Фаром только один вроде у меня, где скрипт выполняется.

Это предположение, что плагин захватил. А может не он? Может не он. Как проверить? Бряки на всех секциях (попробовал) - не вариант, диалоговая процедура то и дело работает. Темный лес..

NightRoman

2014-03-15 17:10

reporter   bugnote:0011950

И не понятно, как прагин может "захватить" секцию. Что такого можно вызвать у Фара из плагина в данной ситуации, чтобы управление к плагину не вернулось и из-за этого дед лок случился? Мне все же пока кажется, что это Фар химичит с секциями сам. Но проявляется это, вероятно, в такой ситуации, когда обратный вызов из плагина идет из другого потока. Примерно как описано в предположении в начале обсуждения.

У меня пока кончились идеи. Надо код Фара знать лучше, чем я, чтобы понять, что происходит. Но думаю об этом тоже.

NightRoman

2014-03-15 17:58

reporter   bugnote:0011951

Не знаю, полезно ли, но провел эксперимент и отпишусь.

Добавил макрос и вызвал его из того же диалога CtrlG:

Macro {
area="Dialog"; description="$Far.Dialog in Dialog"; key="CtrlShiftF9"; action=function()
Plugin.Call("10435532-9BB3-487B-A045-B0E6ECAAB6BC", "ps:Show-FarMessage ($Far.Dialog | Out-String) -LeftAligned")
end;
}

Работает. $Far.Dialog получен и все его данные показаны. Повершелл скрипт, как всегда, и тут работает из другого потока.

Именно сценарий с F11 и дополнительным диалогом, похоже, оказывается критическим.

NightRoman

2014-03-15 18:10

reporter   bugnote:0011952

И еще эксперимент. Он подчеркивает важность F11 в приведенном сценарии.

Макрос:
Macro {
area="Dialog"; description="$psf.InvokeInputCode() in Dialog"; key="CtrlShiftF9"; action=function()
Plugin.Call("10435532-9BB3-487B-A045-B0E6ECAAB6BC", "ps:$psf.InvokeInputCode()")
end;
}

Он запускает тот же диалог ввода кода из того же диалога CtrlG, только не через F11, а из макроса. Код $Far.Dialog набранный там успешно отрабатывает, данные выводятся во вьюер.

Итак, именно такой сценарий, как описано вначале, приводит к зависанию, а не вариации. Может, это знание пригодится.

NightRoman

2014-03-17 08:07

reporter   bugnote:0011957

А нет ли в Фаре возможности залогировать входы и выходы в эти секции с выводом стека в лог? Тогда по анализу лога можно было бы понять, кто именно захватил секцию.

NightRoman

2014-03-18 10:20

reporter   bugnote:0011958

Вроде разобрался, знание про F11 помогло-таки. Дело именно в F11 из диалога ("CtrlG" или другого). При обработке клавиши попадаем сюда и входим в критическую секцию:

unicode_far\dialog.cpp(2390):
int Dialog::ProcessKey(int Key)

Выхода из этой критической секции не происходит, так как при обработке F11 вызывается этот код в этой функции:

unicode_far\dialog.cpp(2809):
return Global->FrameManager->ProcessKey(Key);

В этом вызове F11 обрабатывается Фаром, он вызывает FarNet\PowerShellFar, PowerShellFar запускает скрипт (новый поток), замораживает текущий поток, новый поток работает и зовет Фар для получения данных диалога. В этом диалоге критическая секция уже залочена входом в Dialog::ProcessKey, поэтому новый поток при входе в эту критическую секцию ждет бесконечно, дедлок.

Проверка. Достаточно закомментировать одну первую строку SCOPED_ACTION(CriticalSectionLock)(CS); в Dialog::ProcessKey, чтобы проблемный пример заработал без зависания.

Если мои наблюдения верны, и это действительно причина зависания, то возможно ли будет ее устранить?

NightRoman

2014-03-18 10:48

reporter   bugnote:0011962

Думается, что это в принципе, может, и нормально, что критические секции там почти в каждом методе. Подвох в том, что из них нельзя звать плагин, так как плагин может перевызвать из другого потока. Причем, даже не из "параллельного", а единственного работающего в данное время, как в случае с PowerShell. Словом, надо бы покинуть секцию, и только потом звать плагин (return Global->FrameManager->ProcessKey(Key)).

NightRoman

2014-03-20 10:51

reporter   bugnote:0011989

Коллеги, кто-нибудь работает над исправлением этого зависания? Не к тому спрашиваю, чтобы поворчать. Если некому заняться, я предложу простой патч, который устранит зависание. Красивый рефакторинг можно будет и потом, если захочется. Но будет ли принят патч? Или с ним тикет также затихнет, вот в чем вопрос.

NightRoman

2014-03-21 11:08

reporter  

dialog.cpp.diff (1,187 bytes)   
diff --git a/unicode_far/dialog.cpp b/unicode_far/dialog.cpp
index 6a97c5d..0e5ba6a 100644
--- a/unicode_far/dialog.cpp
+++ b/unicode_far/dialog.cpp
@@ -2413,6 +2413,11 @@ __int64 Dialog::VMProcess(int OpCode,void *vParam,__int64 iParam)
 */
 int Dialog::ProcessKey(int Key)
 {
+	// flag to call global ProcessKey out of critical section, Mantis#2511
+	bool doGlobalProcessKey = false;
+
+	// enter critical section
+	{
 	SCOPED_ACTION(CriticalSectionLock)(CS);
 	_DIALOG(CleverSysLog CL(L"Dialog::ProcessKey"));
 	_DIALOG(SysLog(L"Param: Key=%s",_FARKEY_ToName(Key)));
@@ -2829,9 +2834,7 @@ int Dialog::ProcessKey(int Key)
 			if (!Global->IsProcessAssignMacroKey)
 			{
 				if(!CheckDialogMode(DMODE_NOPLUGINS))
-				{
-					return Global->FrameManager->ProcessKey(Key);
-				}
+					doGlobalProcessKey = true;
 			}
 			break;
 
@@ -3068,6 +3071,12 @@ int Dialog::ProcessKey(int Key)
 			return ProcessHighlighting(Key,FocusPos,TRUE);
 		}
 	}
+	} // exit from critical section
+	
+	// call global ProcessKey out of critical section to avoid dead lock
+	if (doGlobalProcessKey)
+		return Global->FrameManager->ProcessKey(Key);
+
 	return FALSE;
 }
 
dialog.cpp.diff (1,187 bytes)   

NightRoman

2014-03-21 11:09

reporter   bugnote:0012015

Прикрепил патч, который устраняет описанное зависание Фара - dialog.cpp.diff

vskirdin

2014-03-21 12:35

administrator   bugnote:0012017

build 3839

NightRoman

2014-03-21 13:09

reporter   bugnote:0012018

Спасибо. Можно закрывать.

NightRoman

2016-09-28 20:05

reporter   bugnote:0014824

Дело сделано. Закройте, пожалуйста, для порядка.

Issue History

Date Modified Username Field Change
2013-10-12 08:17 NightRoman New Issue
2014-03-15 14:10 NightRoman Note Added: 0011937
2014-03-15 15:09 shmuel Note Added: 0011938
2014-03-15 15:22 DrKnS Note Added: 0011939
2014-03-15 15:28 NightRoman Note Added: 0011940
2014-03-15 15:35 NightRoman Note Added: 0011941
2014-03-15 15:54 DrKnS Note Added: 0011942
2014-03-15 16:01 NightRoman Note Added: 0011943
2014-03-15 16:13 DrKnS Note Added: 0011944
2014-03-15 16:17 NightRoman Note Added: 0011945
2014-03-15 16:23 NightRoman Note Added: 0011946
2014-03-15 16:30 NightRoman Note Added: 0011947
2014-03-15 16:36 DrKnS Note Added: 0011948
2014-03-15 16:44 NightRoman Note Added: 0011949
2014-03-15 17:10 NightRoman Note Added: 0011950
2014-03-15 17:58 NightRoman Note Added: 0011951
2014-03-15 18:10 NightRoman Note Added: 0011952
2014-03-17 08:07 NightRoman Note Added: 0011957
2014-03-18 10:20 NightRoman Note Added: 0011958
2014-03-18 10:48 NightRoman Note Added: 0011962
2014-03-20 10:51 NightRoman Note Added: 0011989
2014-03-21 11:08 NightRoman File Added: dialog.cpp.diff
2014-03-21 11:09 NightRoman Note Added: 0012015
2014-03-21 12:35 vskirdin Note Added: 0012017
2014-03-21 12:35 vskirdin Assigned To => vskirdin
2014-03-21 12:35 vskirdin Status new => feedback
2014-03-21 13:09 NightRoman Note Added: 0012018
2014-03-21 13:09 NightRoman Status feedback => assigned
2016-09-28 20:05 NightRoman Note Added: 0014824
2016-09-29 17:08 DrKnS Build => 3839
2016-09-29 17:08 DrKnS Status assigned => closed
2016-09-29 17:08 DrKnS Resolution open => fixed
2016-09-29 17:08 DrKnS Fixed in Version => 3.0