проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 09.11.21 14:03
Оценка: 5 (1)
Коллеги, подскажите!

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

Что это может быть?
Если поместить функцию test_load_image_from_mem_ в критическую секцию, то проблема уходит...

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

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

#include <cassert>

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

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

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

    pic_ptr->Release();
}

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

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


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

    ::OleInitialize(nullptr);

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

    ::OleUninitialize();
    return 0;
}

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

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

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

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

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

int CALLBACK WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine,
                     int nCmdShow)
{
    ::MessageBox(NULL, L"Ready to start", L"TestOleLoadPicture", MB_OK);
    start_threads_(100);
    ::MessageBox(NULL, L"Everything OK!", L"TestOleLoadPicture", MB_OK);
}
Re: проблемы с OleLoadPicture и threads
От: Сергей Мухин Россия  
Дата: 09.11.21 15:08
Оценка:
Здравствуйте, qaz77, Вы писали:



Q> ::OleInitialize(nullptr);


OleInitialize calls CoInitializeEx internally to initialize the COM library on the current apartment. Because OLE operations are not thread-safe, OleInitialize specifies the concurrency model as single-thread apartment.

см CoInitializeEx
---
С уважением,
Сергей Мухин
Re[2]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 09.11.21 15:49
Оценка:
Здравствуйте, Сергей Мухин, Вы писали:

СМ>OleInitialize calls CoInitializeEx internally to initialize the COM library on the current apartment. Because OLE operations are not thread-safe, OleInitialize specifies the concurrency model as single-thread apartment.


СМ>см CoInitializeEx



Замена OleInitalize(nullptr)/OleUninitialize() на CoInitialize(nullptr, COINIT_MULTITHREADED)/CoUnitialize() ничего не меняет.

Замечу, что в каждом треде используются свои независимые объекты IStream и IPicture.
Re: проблемы с OleLoadPicture и threads
От: kov_serg Россия  
Дата: 09.11.21 17:43
Оценка:
Здравствуйте, qaz77, Вы писали:

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

А нафига вы через GlobalAlloc и CreateStreamOnHGlobal создаёте поток?
Что мешает свой IStream реализовать?
Re: проблемы с OleLoadPicture и threads
От: VVV Россия  
Дата: 10.11.21 01:31
Оценка:
Здравствуйте, qaz77, Вы писали:

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


Не знаю поможет ли с основной проблемой, но нужно поменять местами релизы

было:
    ps->Release();

    pic_ptr->Release();


стало:
    pic_ptr->Release();

    ps->Release();
Re[2]: проблемы с OleLoadPicture и threads
От: kov_serg Россия  
Дата: 10.11.21 06:23
Оценка:
Здравствуйте, VVV, Вы писали:

VVV>Не знаю поможет ли с основной проблемой, но нужно поменять местами релизы


VVV>было:

VVV>
    ps->Release();
    pic_ptr->Release();

VVV>стало:
VVV>
    pic_ptr->Release();
    ps->Release();

Это не должно влиять ибо счетчику ссылок всё равно.
Re[2]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 10.11.21 06:50
Оценка:
Здравствуйте, kov_serg, Вы писали:
_>А нафига вы через GlobalAlloc и CreateStreamOnHGlobal создаёте поток?

Взял готовое.
На самом деле, этому коду уже сто лет в обед.
Наверняка, какой-то пример из msdn был положен в основу.

_>Что мешает свой IStream реализовать?


Попробовал с вашей реализацией IStream:
    IStream* ps = nullptr;
    HRESULT hr = createMemoryStream(&ps, data_size);
    assert(SUCCEEDED(hr));
    assert(ps);

    hr = ps->Write(image_data, data_size, nullptr);
    assert(SUCCEEDED(hr));

    LARGE_INTEGER pos = { 0, 0 };
    hr = ps->Seek(pos, STREAM_SEEK_SET, nullptr);
    assert(SUCCEEDED(hr));


При 100 потоках также вылазит вся та же проблема.
OleLoadPicture возвращает STG_E_REVERTED.
Re[2]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 10.11.21 06:55
Оценка:
Здравствуйте, VVV, Вы писали:

VVV>Не знаю поможет ли с основной проблемой, но нужно поменять местами релизы


VVV>стало:

VVV>
VVV>    pic_ptr->Release();

    ps->>Release();
VVV>


Не помогает.

Да и странно было бы думать, что IPicture внутри держит IStream после загрузки.
Если бы вдруг и держала, то вызывала бы AddRef и мой ps->Release() только уменьшал бы счетчик стрима, а не удалял его.
Re: дополнение про OleLoadPicturePath
От: qaz77  
Дата: 10.11.21 07:41
Оценка:
Сделал аналогичный тест для загрузки IPicture из файла через OleLoadPicturePath.
При таком же количестве тредов ~100 наблюдается отказ в виде возврата E_FAIL.

Рецепт прекращения этого безобразия: поместить все время жизни объекта IPicture в критическую секцию.
Подготовка IStream может быть перед входом в критическую секцию — это не влияет.
Re[3]: проблемы с OleLoadPicture и threads
От: Jack128  
Дата: 10.11.21 08:13
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Да и странно было бы думать, что IPicture внутри держит IStream после загрузки.

Ну например GDI+ так и делает
Re[3]: проблемы с OleLoadPicture и threads
От: kov_serg Россия  
Дата: 10.11.21 09:39
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Взял готовое.

Q>На самом деле, этому коду уже сто лет в обед.
Q>Наверняка, какой-то пример из msdn был положен в основу.

Q>При 100 потоках также вылазит вся та же проблема.

Q>OleLoadPicture возвращает STG_E_REVERTED.
Еще вариант реализовать свой IPicture
А если серьёзно, то какая у вас win-да? На windows 7 не воспроизводится.
Но при 1000 потоков у меня виртуалка очень призадумалась, пришлось пристрелить.
Re[2]: дополнение про OleLoadPicturePath
От: Mr.Delphist  
Дата: 10.11.21 13:03
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Рецепт прекращения этого безобразия: поместить все время жизни объекта IPicture в критическую секцию.


Что по сути означает, что тогда потоки исполняются строго последовательно, верно?
Re: проблемы с OleLoadPicture и threads
От: Mr.Delphist  
Дата: 10.11.21 13:18
Оценка:
Здравствуйте, qaz77, Вы писали:

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

Q>При количестве 1000 — гарантировано.

На всякий случай: C/C++ -> Code Generation -> Runtime Library

с каким рантаймом собирается проект? обычный или MT?
Re[4]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 10.11.21 15:07
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>А если серьёзно, то какая у вас win-да? На windows 7 не воспроизводится.

_>Но при 1000 потоков у меня виртуалка очень призадумалась, пришлось пристрелить.

На хосте Win 8.1 и процессор с 8-ю ядрами.

Сейчас попробовал на виртуалке Win 10.
Если в настройках VM одно ядро, то не воспроизводится.
Поставил 4 ядра — стало воспроизводиться.

Виртуалки Win 7, к сожалению, сейчас нет под рукой.

Время теста под виртуалкой в пределах единиц секунд для 1000 потоков.
Под хостом чуть быстрее.
Re[3]: дополнение про OleLoadPicturePath
От: qaz77  
Дата: 10.11.21 15:09
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

MD>Что по сути означает, что тогда потоки исполняются строго последовательно, верно?


Всякая возня со стримом выполняется параллельно.
Вплоть до вызова OleLoadPicture...
Входим в критическую секцию непосредственно перед OleLoadPicture/OleLoadPicturePath и выходим сразу после pic_ptr->Release().
Re[2]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 10.11.21 15:17
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

MD>На всякий случай: C/C++ -> Code Generation -> Runtime Library


MD>с каким рантаймом собирается проект? обычный или MT?


Рантайм многопоточный. Дебажный /MDd, чтобы assert срабатывали.
Я проверял и для релиза, та же картина. Там просто AV будет при обращении нулевому pic_ptr.
В конце концов, проблема выросла из боевого проекта, который в релизе тестировался.

Собирал x86 exe VS 2013 upd 5.
Воспроизводится, по крайней мере, на Win 8.1 и Win 10.
Похоже, важным фактором для воспроизведения является кол-во ядер > 1.
Re: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 10.11.21 15:39
Оценка:
Q> ::WaitForMultipleObjects(count, harr, TRUE, INFINITE);

Тут вот у меня косяк с ожиданием.
WFMO принимает не более MAXIMUM_WAIT_OBJECTS хендлов, которая равна 64.
Надо в цикле вызывать WaitForSingleObject (аля JoinAll).

Но это не отменяет глюка с OleLoadPicture/OleLoadPicturePath.
Если задавать кол-во тредов 60, то все воспроизводится при правильном ожидании.
Re[3]: проблемы с OleLoadPicture и threads
От: Mr.Delphist  
Дата: 10.11.21 15:44
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Собирал x86 exe


О, а вот это важный момент. Сколько памяти в итоге занимает приложение в момент первого сбоя? Тредов много, каждый имеет накладные расходы на стек и т.п.
Re[4]: проблемы с OleLoadPicture и threads
От: qaz77  
Дата: 10.11.21 16:26
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

Q>>Собирал x86 exe

MD>О, а вот это важный момент. Сколько памяти в итоге занимает приложение в момент первого сбоя? Тредов много, каждый имеет накладные расходы на стек и т.п.

Диспетчер задач показывает 32 Мб памяти.
Сейчас собрал x64 — все то же самое.
Re: проблемы с OleLoadPicture и threads
От: Слава  
Дата: 10.11.21 21:45
Оценка:
Здравствуйте, qaz77, Вы писали:

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

Q>При количестве 1000 — гарантировано.

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

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

То есть, потоков может быть уйма, а вот работающих с тем, что вы раньше закрывали критической секцией — не более 60.
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...
Пока на собственное сообщение не было ответов, его можно удалить.