View Issue Details

IDProjectCategoryView StatusLast Update
0001898NoesisGUIC++ SDKpublic2021-01-21 18:16
ReporterstevehAssigned Tohcpizzi 
Status resolvedResolutionfixed 
Product Version3.0.6 
Target Version3.0.10Fixed in Version3.0.10 
Summary0001898: Non thread safe code in InspectorTextureProvider
DescriptionHi guys, we're hitting an assert at random intervals when loading textures and creating bitmap image render proxies at runtime. The assert is coming from hashmap when we're growing the bucket container:

template<typename Bucket>
inline void HashMapImpl<Bucket>::InsertBucketsFrom(Bucket* src, uint32_t count)
    for (Bucket* it = src; it != src + count; ++it)
        if (!it->IsEmpty())
            // Insert the key/value into the new table.
            Bucket* bucket;
            bool found = FindInsertBucket(it->Hash(), it->key, bucket);
            NS_ASSERT(!found); // <--- HERE

            new (bucket) Bucket(MoveArg(*it));


So I double checked, all the buckets in the source were unique so something must have added the key during the while loop. It turns out that there are no locks in InspectorTextureProvider, so for example this runs on render thread:

Noesis::InspectorTextureProvider::OpenStream(const char * uri) Line 76 C++
Noesis::InspectorTextureProvider::LoadTexture(const char * uri, Noesis::RenderDevice * device) Line 196 C++
Noesis::VGLContext::CreateImage(const char * filename) Line 544 C++
Noesis::BitmapImageProxy::CreateImage(Noesis::RenderTree * tree) Line 45 C++
Noesis::ImageSourceProxy::GetImage(Noesis::RenderTree * tree) Line 34 C++
Noesis::ImageBrushProxy::UpdateImageSource(Noesis::RenderTree * tree, Noesis::ImageSourceProxy * is) Line 34 C++
Noesis::RenderTree::ProcessCommand(Noesis::RenderCommandId id, const void * data) Line 516 C++
Noesis::RenderTree::ProcessRenderCommands(const unsigned char * data, unsigned int size) Line 254 C++
Noesis::Renderer::UpdateRenderTree() Line 122 C++

And then we call Image::SetSource() on main thread:

InspectorTextureProvider::OpenStream(const char * uri) Line 76 C++
InspectorTextureProvider::GetTextureInfo(const char * uri) Line 164 C++
BitmapImage::UpdateImageInfo(const char * uri) Line 204 C++
BitmapImage::StaticFillClassType::__l2::<lambda>(Noesis::DependencyObject * d, const Noesis::DependencyPropertyChangedEventArgs & e) Line 249 C++
Delegate<void __cdecl(Noesis::DependencyObject *,Noesis::DependencyPropertyChangedEventArgs const &)>::FunctorStub<void <lambda>(Noesis::DependencyObject *, const Noesis::DependencyPropertyChangedEventArgs &) >::Invoke(Noesis::DependencyObject * <args_0>, const Noesis::DependencyPropertyChangedEventArgs & <args_1>) Line 432 C++
Delegate<void __cdecl(Noesis::DependencyObject *,Noesis::DependencyPropertyChangedEventArgs const &)>::operator()(Noesis::DependencyObject * <args_0>, const Noesis::DependencyPropertyChangedEventArgs & <args_1>) Line 173 C++
DependencyObject::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 607 C++
Freezable::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & e) Line 235 C++
DependencyObject::Init() Line 536 C++
InitComponent(Noesis::IComponentInitializer * component, bool doInit) Line 41 C++
TemplateBindingExpression::Evaluate() Line 76 C++
DependencyObject::InternalSetExpression(const Noesis::DependencyProperty * dp, Noesis::Expression * newExpression, unsigned char priority) Line 676 C++
DependencyObject::InternalInvalidateProperty(const Noesis::DependencyProperty * dp, unsigned char priority) Line 1109 C++
DependencyObject::InvalidateProperty(const Noesis::DependencyProperty * dp, unsigned char priority) Line 291 C++
TemplateBindingExpression::OnTemplatedParentPropertyChanged(Noesis::BaseComponent * __formal, const Noesis::DependencyPropertyChangedEventArgs & args) Line 186 C++
Delegate<void __cdecl(Noesis::BaseComponent *,Noesis::DependencyPropertyChangedEventArgs const &)>::MemberFuncStub<Noesis::TemplateBindingExpression,void (__cdecl Noesis::TemplateBindingExpression::*)(Noesis::BaseComponent *,Noesis::DependencyPropertyChangedEventArgs const &)>::Invoke(Noesis::BaseComponent * <args_0>, const Noesis::DependencyPropertyChangedEventArgs & <args_1>) Line 469 C++
[Inline Frame] Noesis::Delegate<void __cdecl(Noesis::BaseComponent *,Noesis::DependencyPropertyChangedEventArgs const &)>::operator()(Noesis::BaseComponent *) Line 172 C++
Noesis::Delegate<void __cdecl(Noesis::BaseComponent *,Noesis::DependencyPropertyChangedEventArgs const &)>::MultiDelegate::Invoke(Noesis::BaseComponent * <args_0>, const Noesis::DependencyPropertyChangedEventArgs & <args_1>) Line 570 C++
Noesis::Delegate<void __cdecl(Noesis::BaseComponent *,Noesis::DependencyPropertyChangedEventArgs const &)>::operator()(Noesis::BaseComponent * <args_0>, const Noesis::DependencyPropertyChangedEventArgs & <args_1>) Line 173 C++
Noesis::DependencyObject::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 608 C++
Noesis::Visual::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 905 C++
Noesis::UIElement::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 2013 C++
Noesis::FrameworkElement::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 1660 C++
Noesis::Control::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 443 C++
Noesis::Image::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 137 C++
Noesis::DependencyObject::NotifyPropertyChanged(const Noesis::DependencyProperty * dp, Noesis::StoredValue * storedValue, const void * oldValue, const void * newValue, bool valueChanged, bool isBaseComponent, const Noesis::PropertyMetadata * metadata) Line 1260 C++
Noesis::DependencyObject::InternalSetValue(const Noesis::DependencyProperty * dp, void * oldValue, const void * newValue, void * coercedValue, unsigned char priority, Noesis::Expression * newExpression, const Noesis::PropertyMetadata * metadata, Noesis::Value::Destination destination, bool isBaseComponent) Line 894 C++
Noesis::DependencyObject::SetValue_<Noesis::Ptr<Noesis::ImageSource> >(Noesis::Int2Type<1> __formal, const Noesis::DependencyProperty * dp, Noesis::ImageSource * value, Noesis::Value::Destination destination) Line 195 C++
Noesis::DependencyObject::SetValue<Noesis::Ptr<Noesis::ImageSource> >(const Noesis::DependencyProperty * dp, Noesis::ImageSource * value) Line 60 C++
Noesis::Image::SetSource(Noesis::ImageSource *) Line 40 C++

Is this unintended behaviour? I can add locks in InspectorTextureProvider but I don't know if we're supposed to be calling SetSource from main thread, I assumed we were. E.g. we do the following in our game:

String szFullPath = "Assets/UI/Image1.png";
Ptr<BitmapImage> pImageSource = MakePtr<BitmapImage>(Uri(szFullPath.Str()));

This works almost all the time, the only time it doesn't is when the internal map in the InspectorTextureProvider asserts due to being mutated on multiple threads.

Any ideas? Cheers,

TagsNo tags attached.




2021-01-21 18:16

developer   ~0006978

You're absolutely right, there was a race condition there. The TextureProvider is meant to be used from both threads, which leads to these problems, but your use is correct.

The issue is fixed in revision 9949.

Issue History

Date Modified Username Field Change
2021-01-21 16:38 steveh New Issue
2021-01-21 17:04 jsantos Assigned To => hcpizzi
2021-01-21 17:04 jsantos Status new => assigned
2021-01-21 17:04 jsantos Target Version => 3.0.10
2021-01-21 18:16 hcpizzi Status assigned => resolved
2021-01-21 18:16 hcpizzi Resolution open => fixed
2021-01-21 18:16 hcpizzi Fixed in Version => 3.0.10
2021-01-21 18:16 hcpizzi Note Added: 0006978