Re[2]: проблемы с OleLoadPicture и threads
От: Аноним  
Дата: 11.11.21 08:29
Оценка:
Здравствуйте, Слава, Вы писали:

С>(прочитав весь тред)

С>Вы не желаете ли вместо критической секции повесить семафор на 60 единиц, например? Если уж ниже по треду было найдено волшебное число в 64 хэндлера.
С>То есть, потоков может быть уйма, а вот работающих с тем, что вы раньше закрывали критической секцией — не более 60.

Нет, 64 потока — это ограничение для WFMO.

Если вместо WFMO сделать:
for (int num = 0; num < count; ++num)
   WaitForSingleObject(harr[num], INFINITE);

то хоть 10000 потоков можно ждать.

Что там глючит с потоками внутри OleLoadPicture/OleLoadPicturePath неизвестно.
В реальном продукте эффект периодически наблюдается при всего 3-4 потоках.
Ну там, конечно, время жизни объекта IPicture побольше, не сразу удаляется после загрузки.
Re[3]: проблемы с OleLoadPicture и threads
От: Mr.Delphist  
Дата: 11.11.21 08:56
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Что там глючит с потоками внутри OleLoadPicture/OleLoadPicturePath неизвестно.

А>В реальном продукте эффект периодически наблюдается при всего 3-4 потоках.
А>Ну там, конечно, время жизни объекта IPicture побольше, не сразу удаляется после загрузки.

Наверное, либо бага, либо хардкод в ОС. У меня был прикол со ScreenSharing API виндовым — если вызывать более чем для одного окна (в цикле по top-level HWND), иногда при закрытии хэндла оно тупо висло в ожидании чего-то. "Полечил" выносом закрытия в другой поток — зависнет и фиг с ним, меньшее из двух зол. Да, ресурсы никто в систему не вернёт, утечка памяти у приложения заметна в диспетчере невооружённым глазом, ну всяко лучше чем вставшее колом приложение. И что же оказалось? Раз на третий-четвёртый таких зависаний график памяти сперва вырастал, а потом что-то в системе перещёлкивало, и затупы прорывало, с освобождением памяти снова к нормальным величинам.
Re: проблемы с OleLoadPicture и threads
От: Alexander G Украина  
Дата: 20.11.21 17:02
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Коллеги, подскажите!


Q>Столкнулся с такой проблемой, когда несколько потоков грузят картинки из кусков памяти (не из файлов).

Q>Создается IStream функцией CreateStreamOnHGlobal. При каком-то количестве одновременно существующих объектов IStream (созданных с разными HGLOBAL)
Q>функция OleLoadPicture начинает возвращать STG_E_REVERTED (0x80030102).

Q>Что это может быть?


Баг в OleLoadPicture ?

Я воспроизвёл и с заменой CreateStreamOnHGlobal на SHCreateMemStream, так что CreateStreamOnHGlobal не при чём.
Русский военный корабль идёт ко дну!
Re[2]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 21.11.21 10:01
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Баг в OleLoadPicture ?


Вроде того.
Воспроизводится и с загрузкой из файла OleLoadPicturePath, где снаружи никаких стримов не передается.
Re[3]: проблемы с OleLoadPicture и threads
От: Alexander G Украина  
Дата: 21.11.21 10:57
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Вроде того.

Q>Воспроизводится и с загрузкой из файла OleLoadPicturePath, где снаружи никаких стримов не передается.

Хорошего способа репортить баги Windows API мне не известно.
В теории можно через Windows Feedback Hub, с околонулевыми шансом что его вообще посмотрят.

Можно попробовать предложить pull request на документацию, уточняющий, что функция не потокобезопасна.
Это несложно, требует лишь аккаунт на гитхабе, можно сделать без клиента Git, полностью через веб.

Можно попробовать найти точно место гонки: Intel Inspector по идее находит гонки, и .pdb для всех тех DLL доступны.

Можно продолжать использовать критическую секцию, с минимальным, но всё же существующим риском, что кто-то всё же одновременно вызовет OleLoadPicture.
(Ну там shell extensions, third party libraries, print drivers, прочие гости процесса)

Лучше всего забить на OleLoadPicture и взять всё под контроль (притянув по необходимости libjpeg, libpng и прочие).
Русский военный корабль идёт ко дну!
Re[4]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 22.11.21 07:08
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Хорошего способа репортить баги Windows API мне не известно.

AG>В теории можно через Windows Feedback Hub, с околонулевыми шансом что его вообще посмотрят.

AG>Можно попробовать предложить pull request на документацию, уточняющий, что функция не потокобезопасна.

AG>Это несложно, требует лишь аккаунт на гитхабе, можно сделать без клиента Git, полностью через веб.

Я как-то за MS не сильно переживаю.
Если они и пофиксят этот баг, то когда в реальности это произойдет и для каких версий?
В этом моем продукте, например, еще XP поддерживается.

Мой главный посыл был в том, чтобы донести до коллег, что такая проблема имеет место быть.
А решать что с ней делать сможет каждый, в зависимости от специфики своего кода.

AG>Лучше всего забить на OleLoadPicture и взять всё под контроль (притянув по необходимости libjpeg, libpng и прочие).


В моем случае грузятся BMP, ICO.
В принципе хватило бы LoadImage, но мне надо грузить не из файла, а из памяти...

У себя я проблему полностью решил использованием глобальной критической секции на время жизни IPicture.
"Гостевым" вызовом OleLoadPicture в контексте моего процесса можно пренебречь.

К сожалению, вариант с хуком на таблицу импорта с заменой импорта OleLoadPicture на MyOleLoadPicture, не поможет.
Погружение только вызова функции в критическую секцию проблемы не решает.
Надо оборачивать все время жизни IPicture.
Re[5]: проблемы с OleLoadPicture и threads
От: kov_serg Россия  
Дата: 22.11.21 07:52
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Мой главный посыл был в том, чтобы донести до коллег, что такая проблема имеет место быть.

Q>А решать что с ней делать сможет каждый, в зависимости от специфики своего кода.
Поняли — есть гонки внутри.

AG>>Лучше всего забить на OleLoadPicture и взять всё под контроль (притянув по необходимости libjpeg, libpng и прочие).

freeimage

Q>У себя я проблему полностью решил использованием глобальной критической секции на время жизни IPicture.

Но не понятно почему IPicture надо оборачивать.

Q>"Гостевым" вызовом OleLoadPicture в контексте моего процесса можно пренебречь.

Q>К сожалению, вариант с хуком на таблицу импорта с заменой импорта OleLoadPicture на MyOleLoadPicture, не поможет.
Я не очень понял почему такой вывод? Проблема возникает из-за гонок в коде OleLoadPicture. Что мешает повторить вызов несколько раз (можно даже с задержкой). Если событие редкое при повторе может не возникнуть. Чем такой костыль не устраивает?

Q>Погружение только вызова функции в критическую секцию проблемы не решает.

Q>Надо оборачивать все время жизни IPicture.
Ошибка при создании IPicture или он потом не работает? Зачем IPicture оборачивать?
Re[6]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 22.11.21 07:56
Оценка:
Здравствуйте, kov_serg, Вы писали:
_>Ошибка при создании IPicture или он потом не работает? Зачем IPicture оборачивать?

Я же говорю, что оборачивание только OleLoadPicture не снимает проблему.
Если обернуть все до Release() IPicture, то проблема устраняется при любом кол-ве потоков до 10000.
Видимо, какая-то гонка есть и при разрушении IPicture.

При этом работа с IStream не влияет, можно его создавать и удалять вне критической секции.
Re[5]: проблемы с OleLoadPicture и threads
От: Alexander G Украина  
Дата: 22.11.21 08:32
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Мой главный посыл был в том, чтобы донести до коллег, что такая проблема имеет место быть.

Q>А решать что с ней делать сможет каждый, в зависимости от специфики своего кода.

Ну тогда попробуем предложить изменение в документацию, посмотрим, что из этого получится.

AG>>Лучше всего забить на OleLoadPicture и взять всё под контроль (притянув по необходимости libjpeg, libpng и прочие).


Q>В моем случае грузятся BMP, ICO.

Q>В принципе хватило бы LoadImage, но мне надо грузить не из файла, а из памяти...

Можно на GDI+ глянуть (оно есть как часть Windows XP, и доустанавливается в более старые ОС).
Image::FromStream тоже принимает IStream*, который так же можно получить через CreateStreamOnHGlobal или SHCreateMemStream
Русский военный корабль идёт ко дну!
Re[6]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 22.11.21 08:36
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Можно на GDI+ глянуть (оно есть как часть Windows XP, и доустанавливается в более старые ОС).

AG>Image::FromStream тоже принимает IStream*, который так же можно получить через CreateStreamOnHGlobal или SHCreateMemStream

Проект старый. Тащить туда GDI+ или какие-то библиотеки для меня явный overkill.
Свою проблему я решил глобальной критической секцией.
Благо использую свою обертку на OleLoadPicture и это надо было сделать только в одном месте.
Re[7]: проблемы с OleLoadPicture и threads
От: kov_serg Россия  
Дата: 23.11.21 09:47
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Я же говорю, что оборачивание только OleLoadPicture не снимает проблему.

Q>Если обернуть все до Release() IPicture, то проблема устраняется при любом кол-ве потоков до 10000.
Q>Видимо, какая-то гонка есть и при разрушении IPicture.
Значит проблема не в гонках, а в окончании ресурсов GDI
Re[8]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 23.11.21 10:12
Оценка: +1
Здравствуйте, kov_serg, Вы писали:

_>Значит проблема не в гонках, а в окончании ресурсов GDI


Неа. 100 картинок и ресурсы GDI кончились, серьезно?

Скорее всего, и реализация OleLoadPicture/OleLoadPicturePath, и деструктор объекта Picture обращаются
к каким-то изменяемым глобальным переменным без должной синхронизации.
Может буфер какой-то...
Re: проблемы с OleLoadPicture и threads
От: Melamed Россия  
Дата: 25.11.21 13:19
Оценка:
Здравствуйте, qaz77, Вы писали:

Не знаю, поможет ли это, но меня один раз спасло то, что я убрал атрибут const (выделено жирным шрифтом) переменной, которой несколько потоков. Надеюсь, это тебе поможет
Q>Коллеги, подскажите!

Q>Столкнулся с такой проблемой, когда несколько потоков грузят картинки из кусков памяти (не из файлов).

Q>Создается IStream функцией CreateStreamOnHGlobal. При каком-то количестве одновременно существующих объектов IStream (созданных с разными HGLOBAL)
Q>функция OleLoadPicture начинает возвращать STG_E_REVERTED (0x80030102).

Q>Что это может быть?

Q>Если поместить функцию test_load_image_from_mem_ в критическую секцию, то проблема уходит...

Q>При количестве threads 100 у меня через раз срабатывает assert у OleLoadPicture.

Q>При количестве 1000 — гарантировано.
Q>Картинка задана в коде как массив байт. Это валидная BMP 16x16 4bpp.
Q>Вот минимальный пример:.

Q>
Q>#include <windows.h>
Q>#include <ole2.h>
Q>#include <olectl.h>

Q>#include <cassert>

Q>void test_load_image_from_mem_(int thread_num, const void* image_data, size_t data_size)
Q>{
Q>    HGLOBAL hmem = ::GlobalAlloc(GMEM_MOVEABLE, data_size);
Q>    assert(hmem);
Q>    void* pmem = ::GlobalLock(hmem);
Q>    memcpy(pmem, image_data, data_size);
Q>    ::GlobalUnlock(hmem);

Q>    IStream* ps = nullptr;
Q>    HRESULT hr = ::CreateStreamOnHGlobal(hmem, TRUE, &ps);
Q>    assert(SUCCEEDED(hr));
Q>    assert(ps);

Q>    IPicture* pic_ptr = nullptr;
Q>    hr = ::OleLoadPicture(ps, static_cast<LONG>(data_size), FALSE, IID_IPicture, (void**)&pic_ptr);
Q>    assert(SUCCEEDED(hr));
Q>    assert(pic_ptr);
    ps->>Release();

Q>    pic_ptr->Release();
Q>}

Q>const char TestData[246] = {
Q>  0x42u, 0x4Du, 0xF6u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x76u, 0x00u, 0x00u, 0x00u, 0x28u, 0x00u, 
Q>  0x00u, 0x00u, 0x10u, 0x00u, 0x00u, 0x00u, 0x10u, 0x00u, 0x00u, 0x00u, 0x01u, 0x00u, 0x04u, 0x00u, 0x00u, 0x00u, 
Q>  0x00u, 0x00u, 0x80u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 
Q>  0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x80u, 0x00u, 0x00u, 0x80u, 
Q>  0x00u, 0x00u, 0x00u, 0x80u, 0x80u, 0x00u, 0x80u, 0x00u, 0x00u, 0x00u, 0x80u, 0x00u, 0x80u, 0x00u, 0x80u, 0x80u, 
Q>  0x00u, 0x00u, 0xC0u, 0xC0u, 0xC0u, 0x00u, 0x80u, 0x80u, 0x80u, 0x00u, 0x00u, 0x00u, 0xFFu, 0x00u, 0x00u, 0xFFu, 
Q>  0x00u, 0x00u, 0x00u, 0xFFu, 0xFFu, 0x00u, 0xFFu, 0x00u, 0x00u, 0x00u, 0xFFu, 0x00u, 0xFFu, 0x00u, 0xFFu, 0xFFu, 
Q>  0x00u, 0x00u, 0xFFu, 0xFFu, 0xFFu, 0x00u, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 

Q>  0xDDu, 0xDDu, 0xDDu, 0xDDu, 0x00u, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xD0u, 0xFFu, 0x0Du, 0xDDu, 0xDDu, 
Q>  0xDDu, 0xDDu, 0xDDu, 0xD0u, 0xFFu, 0x0Du, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xD0u, 0x00u, 0xDDu, 0xDDu, 0xDDu, 
Q>  0xDDu, 0xDDu, 0xDDu, 0x0Du, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xD0u, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 
Q>  0xDDu, 0xDDu, 0x0Du, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xD0u, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 
Q>  0xDDu, 0x0Du, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xD0u, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0x00u, 
Q>  0x0Du, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xD0u, 0xFFu, 0x0Du, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xD0u, 0xFFu, 
Q>  0x0Du, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0x00u, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 
Q>  0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, 0xDDu, };


Q>DWORD WINAPI thread_proc_(LPVOID lpParameter)
Q>{
Q>    const int thread_num = (int)lpParameter;

Q>    ::OleInitialize(nullptr);

Q>    test_load_image_from_mem_(thread_num, TestData, sizeof(TestData));

Q>    ::OleUninitialize();
Q>    return 0;
Q>}

Q>HANDLE start_thread_(int num)
Q>{
Q>    return ::CreateThread(nullptr, 0, thread_proc_, (LPVOID)num, 0, nullptr);
Q>}

Q>void start_threads_(int count)
Q>{
Q>    HANDLE* harr = new HANDLE[count];

Q>    for (int num = 0; num < count; ++num)
Q>        harr[num] = start_thread_(num);

Q>    ::WaitForMultipleObjects(count, harr, TRUE, INFINITE);

Q>    for (int num = 0; num < count; ++num)
Q>        ::CloseHandle(harr[num]);
Q>    delete[] harr;
Q>}

Q>int CALLBACK WinMain(HINSTANCE hInstance,
Q>                     HINSTANCE hPrevInstance,
Q>                     LPSTR lpCmdLine,
Q>                     int nCmdShow)
Q>{
Q>    ::MessageBox(NULL, L"Ready to start", L"TestOleLoadPicture", MB_OK);
Q>    start_threads_(100);
Q>    ::MessageBox(NULL, L"Everything OK!", L"TestOleLoadPicture", MB_OK);
Q>}
Q>
Re[2]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 25.11.21 13:24
Оценка:
Здравствуйте, Melamed, Вы писали:
M>Не знаю, поможет ли это, но меня один раз спасло то, что я убрал атрибут const (выделено жирным шрифтом) переменной, которой несколько потоков. Надеюсь, это тебе поможет

Не поможет.
Это только в тесте const, а в реальной жизни там std::vector<char>.
В любом случае, содержимое копируется memcpy в выделенный в каждом потоке HGLOBAL.
Т.е. каждый поток работает со своей копией куска памяти.
Re[3]: проблемы с OleLoadPicture и threads
От: Melamed Россия  
Дата: 25.11.21 14:02
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Здравствуйте, Melamed, Вы писали:

M>>Не знаю, поможет ли это, но меня один раз спасло то, что я убрал атрибут const (выделено жирным шрифтом) переменной, которой несколько потоков. Надеюсь, это тебе поможет

Q>Не поможет.

Q>Это только в тесте const, а в реальной жизни там std::vector<char>.
Q>В любом случае, содержимое копируется memcpy в выделенный в каждом потоке HGLOBAL.
Q>Т.е. каждый поток работает со своей копией куска памяти.

Мне помогло.
Могу предложить объявление const char[] равносильна static char[]. Отличие лишь в том, что в первом случае вы не можете менять элементы массива, а во втором можете.
Re[4]: проблемы с OleLoadPicture и threads
От: Melamed Россия  
Дата: 25.11.21 14:12
Оценка:
Здравствуйте, Melamed, Вы писали:

M>Здравствуйте, qaz77, Вы писали:


Q>>Здравствуйте, Melamed, Вы писали:

M>>>Не знаю, поможет ли это, но меня один раз спасло то, что я убрал атрибут const (выделено жирным шрифтом) переменной, которой несколько потоков. Надеюсь, это тебе поможет

Q>>Не поможет.

Q>>Это только в тесте const, а в реальной жизни там std::vector<char>.
Q>>В любом случае, содержимое копируется memcpy в выделенный в каждом потоке HGLOBAL.
Q>>Т.е. каждый поток работает со своей копией куска памяти.

M>Мне помогло.

M>Могу предложить объявление const char[] равносильна static char[]. Отличие лишь в том, что в первом случае вы не можете менять элементы массива, а во втором можете.

И еще константные и глобальный переменные хранятся в разных сегментах данных.
Re[4]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 25.11.21 14:13
Оценка:
Здравствуйте, Melamed, Вы писали:

M>Мне помогло.

M>Могу предложить объявление const char[] равносильна static char[]. Отличие лишь в том, что в первом случае вы не можете менять элементы массива, а во втором можете.

Серьезно?

На уровне бреда проверил — убрал конст. Ничего не изменилось.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.