mirror of
https://github.com/opencv/opencv.git
synced 2024-11-29 13:47:32 +08:00
Merge pull request #5177 from lupustr3:pvlasov/tls_fixes
This commit is contained in:
commit
887d8d091b
@ -58,6 +58,8 @@
|
|||||||
|
|
||||||
#include "opencv2/hal/defs.h"
|
#include "opencv2/hal/defs.h"
|
||||||
|
|
||||||
|
#define OPENCV_ABI_COMPATIBILITY 300
|
||||||
|
|
||||||
#ifdef __OPENCV_BUILD
|
#ifdef __OPENCV_BUILD
|
||||||
# define DISABLE_OPENCV_24_COMPATIBILITY
|
# define DISABLE_OPENCV_24_COMPATIBILITY
|
||||||
#endif
|
#endif
|
||||||
|
@ -513,30 +513,42 @@ private:
|
|||||||
AutoLock& operator = (const AutoLock&);
|
AutoLock& operator = (const AutoLock&);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TLS interface
|
||||||
class CV_EXPORTS TLSDataContainer
|
class CV_EXPORTS TLSDataContainer
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
int key_;
|
|
||||||
protected:
|
protected:
|
||||||
TLSDataContainer();
|
TLSDataContainer();
|
||||||
virtual ~TLSDataContainer();
|
virtual ~TLSDataContainer();
|
||||||
public:
|
|
||||||
virtual void* createDataInstance() const = 0;
|
|
||||||
virtual void deleteDataInstance(void* data) const = 0;
|
|
||||||
|
|
||||||
|
#if OPENCV_ABI_COMPATIBILITY > 300
|
||||||
void* getData() const;
|
void* getData() const;
|
||||||
|
void release();
|
||||||
|
|
||||||
|
private:
|
||||||
|
#else
|
||||||
|
void release();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void* getData() const;
|
||||||
|
#endif
|
||||||
|
virtual void* createDataInstance() const = 0;
|
||||||
|
virtual void deleteDataInstance(void* pData) const = 0;
|
||||||
|
|
||||||
|
int key_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Main TLS data class
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class TLSData : protected TLSDataContainer
|
class TLSData : protected TLSDataContainer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
inline TLSData() {}
|
inline TLSData() {}
|
||||||
inline ~TLSData() {}
|
inline ~TLSData() { release(); } // Release key and delete associated data
|
||||||
inline T* get() const { return (T*)getData(); }
|
inline T* get() const { return (T*)getData(); } // Get data assosiated with key
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual void* createDataInstance() const { return new T; }
|
virtual void* createDataInstance() const {return new T;} // Wrapper to allocate data by template
|
||||||
virtual void deleteDataInstance(void* data) const { delete (T*)data; }
|
virtual void deleteDataInstance(void* pData) const {delete (T*)pData;} // Wrapper to release data by template
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @brief Designed for command line parsing
|
/** @brief Designed for command line parsing
|
||||||
|
@ -936,87 +936,254 @@ bool Mutex::trylock() { return impl->trylock(); }
|
|||||||
|
|
||||||
//////////////////////////////// thread-local storage ////////////////////////////////
|
//////////////////////////////// thread-local storage ////////////////////////////////
|
||||||
|
|
||||||
class TLSStorage
|
|
||||||
{
|
|
||||||
std::vector<void*> tlsData_;
|
|
||||||
public:
|
|
||||||
TLSStorage() { tlsData_.reserve(16); }
|
|
||||||
~TLSStorage();
|
|
||||||
inline void* getData(int key) const
|
|
||||||
{
|
|
||||||
CV_DbgAssert(key >= 0);
|
|
||||||
return (key < (int)tlsData_.size()) ? tlsData_[key] : NULL;
|
|
||||||
}
|
|
||||||
inline void setData(int key, void* data)
|
|
||||||
{
|
|
||||||
CV_DbgAssert(key >= 0);
|
|
||||||
if (key >= (int)tlsData_.size())
|
|
||||||
{
|
|
||||||
tlsData_.resize(key + 1, NULL);
|
|
||||||
}
|
|
||||||
tlsData_[key] = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static TLSStorage* get();
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#pragma warning(disable:4505) // unreferenced local function has been removed
|
#pragma warning(disable:4505) // unreferenced local function has been removed
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef TLS_OUT_OF_INDEXES
|
||||||
#ifdef WINRT
|
#define TLS_OUT_OF_INDEXES ((DWORD)0xFFFFFFFF)
|
||||||
// using C++11 thread attribute for local thread data
|
#endif
|
||||||
static __declspec( thread ) TLSStorage* g_tlsdata = NULL;
|
|
||||||
|
|
||||||
static void deleteThreadData()
|
|
||||||
{
|
|
||||||
if (g_tlsdata)
|
|
||||||
{
|
|
||||||
delete g_tlsdata;
|
|
||||||
g_tlsdata = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline TLSStorage* TLSStorage::get()
|
|
||||||
{
|
|
||||||
if (!g_tlsdata)
|
|
||||||
{
|
|
||||||
g_tlsdata = new TLSStorage;
|
|
||||||
}
|
|
||||||
return g_tlsdata;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#ifdef WINCE
|
|
||||||
# define TLS_OUT_OF_INDEXES ((DWORD)0xFFFFFFFF)
|
|
||||||
#endif
|
#endif
|
||||||
static DWORD tlsKey = TLS_OUT_OF_INDEXES;
|
|
||||||
|
|
||||||
static void deleteThreadData()
|
// TLS platform abstraction layer
|
||||||
|
class TlsAbstraction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TlsAbstraction();
|
||||||
|
~TlsAbstraction();
|
||||||
|
void* GetData() const;
|
||||||
|
void SetData(void *pData);
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef WIN32
|
||||||
|
#ifndef WINRT
|
||||||
|
DWORD tlsKey;
|
||||||
|
#endif
|
||||||
|
#else // WIN32
|
||||||
|
pthread_key_t tlsKey;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
#ifdef WINRT
|
||||||
|
static __declspec( thread ) void* tlsData = NULL; // using C++11 thread attribute for local thread data
|
||||||
|
TlsAbstraction::TlsAbstraction() {}
|
||||||
|
TlsAbstraction::~TlsAbstraction() {}
|
||||||
|
void* TlsAbstraction::GetData() const
|
||||||
|
{
|
||||||
|
return tlsData;
|
||||||
|
}
|
||||||
|
void TlsAbstraction::SetData(void *pData)
|
||||||
|
{
|
||||||
|
tlsData = pData;
|
||||||
|
}
|
||||||
|
#else //WINRT
|
||||||
|
TlsAbstraction::TlsAbstraction()
|
||||||
|
{
|
||||||
|
tlsKey = TlsAlloc();
|
||||||
|
CV_Assert(tlsKey != TLS_OUT_OF_INDEXES);
|
||||||
|
}
|
||||||
|
TlsAbstraction::~TlsAbstraction()
|
||||||
|
{
|
||||||
|
TlsFree(tlsKey);
|
||||||
|
}
|
||||||
|
void* TlsAbstraction::GetData() const
|
||||||
|
{
|
||||||
|
return TlsGetValue(tlsKey);
|
||||||
|
}
|
||||||
|
void TlsAbstraction::SetData(void *pData)
|
||||||
|
{
|
||||||
|
CV_Assert(TlsSetValue(tlsKey, pData) == TRUE);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#else // WIN32
|
||||||
|
TlsAbstraction::TlsAbstraction()
|
||||||
|
{
|
||||||
|
CV_Assert(pthread_key_create(&tlsKey, NULL) == 0);
|
||||||
|
}
|
||||||
|
TlsAbstraction::~TlsAbstraction()
|
||||||
|
{
|
||||||
|
CV_Assert(pthread_key_delete(tlsKey) == 0);
|
||||||
|
}
|
||||||
|
void* TlsAbstraction::GetData() const
|
||||||
|
{
|
||||||
|
return pthread_getspecific(tlsKey);
|
||||||
|
}
|
||||||
|
void TlsAbstraction::SetData(void *pData)
|
||||||
|
{
|
||||||
|
CV_Assert(pthread_setspecific(tlsKey, pData) == 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Per-thread data structure
|
||||||
|
struct ThreadData
|
||||||
|
{
|
||||||
|
ThreadData()
|
||||||
{
|
{
|
||||||
if(tlsKey != TLS_OUT_OF_INDEXES)
|
idx = 0;
|
||||||
{
|
slots.reserve(32);
|
||||||
delete (TLSStorage*)TlsGetValue(tlsKey);
|
|
||||||
TlsSetValue(tlsKey, NULL);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline TLSStorage* TLSStorage::get()
|
std::vector<void*> slots; // Data array for a thread
|
||||||
|
size_t idx; // Thread index in TLS storage. This is not OS thread ID!
|
||||||
|
};
|
||||||
|
|
||||||
|
// Main TLS storage class
|
||||||
|
class TlsStorage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TlsStorage()
|
||||||
{
|
{
|
||||||
if (tlsKey == TLS_OUT_OF_INDEXES)
|
tlsSlots = 0;
|
||||||
{
|
threads.reserve(32);
|
||||||
tlsKey = TlsAlloc();
|
|
||||||
CV_Assert(tlsKey != TLS_OUT_OF_INDEXES);
|
|
||||||
}
|
|
||||||
TLSStorage* d = (TLSStorage*)TlsGetValue(tlsKey);
|
|
||||||
if (!d)
|
|
||||||
{
|
|
||||||
d = new TLSStorage;
|
|
||||||
TlsSetValue(tlsKey, d);
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}
|
}
|
||||||
#endif //WINRT
|
~TlsStorage()
|
||||||
|
{
|
||||||
|
for(size_t i = 0; i < threads.size(); i++)
|
||||||
|
{
|
||||||
|
if(threads[i])
|
||||||
|
{
|
||||||
|
/* Current architecture doesn't allow proper global objects relase, so this check can cause crashes
|
||||||
|
|
||||||
|
// Check if all slots were properly cleared
|
||||||
|
for(size_t j = 0; j < threads[i]->slots.size(); j++)
|
||||||
|
{
|
||||||
|
CV_Assert(threads[i]->slots[j] == 0);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
delete threads[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
threads.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void releaseThread()
|
||||||
|
{
|
||||||
|
AutoLock guard(mtxGlobalAccess);
|
||||||
|
ThreadData *pTD = (ThreadData*)tls.GetData();
|
||||||
|
for(size_t i = 0; i < threads.size(); i++)
|
||||||
|
{
|
||||||
|
if(pTD == threads[i])
|
||||||
|
{
|
||||||
|
threads[i] = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tls.SetData(0);
|
||||||
|
delete pTD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve TLS storage index
|
||||||
|
size_t reserveSlot()
|
||||||
|
{
|
||||||
|
AutoLock guard(mtxGlobalAccess);
|
||||||
|
tlsSlots++;
|
||||||
|
return (tlsSlots-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release TLS storage index and pass assosiated data to caller
|
||||||
|
void releaseSlot(size_t slotIdx, std::vector<void*> &dataVec)
|
||||||
|
{
|
||||||
|
AutoLock guard(mtxGlobalAccess);
|
||||||
|
CV_Assert(tlsSlots > slotIdx);
|
||||||
|
|
||||||
|
for(size_t i = 0; i < threads.size(); i++)
|
||||||
|
{
|
||||||
|
if(threads[i]->slots[slotIdx])
|
||||||
|
{
|
||||||
|
dataVec.push_back(threads[i]->slots[slotIdx]);
|
||||||
|
threads[i]->slots[slotIdx] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we removing last element, decriment slots size to save space
|
||||||
|
if(tlsSlots-1 == slotIdx)
|
||||||
|
tlsSlots--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get data by TLS storage index
|
||||||
|
void* getData(size_t slotIdx) const
|
||||||
|
{
|
||||||
|
CV_Assert(tlsSlots > slotIdx);
|
||||||
|
|
||||||
|
ThreadData* threadData = (ThreadData*)tls.GetData();
|
||||||
|
if(threadData && threadData->slots.size() > slotIdx)
|
||||||
|
return threadData->slots[slotIdx];
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set data to storage index
|
||||||
|
void setData(size_t slotIdx, void* pData)
|
||||||
|
{
|
||||||
|
CV_Assert(pData != NULL);
|
||||||
|
|
||||||
|
ThreadData* threadData = (ThreadData*)tls.GetData();
|
||||||
|
if(!threadData)
|
||||||
|
{
|
||||||
|
threadData = new ThreadData;
|
||||||
|
tls.SetData((void*)threadData);
|
||||||
|
{
|
||||||
|
AutoLock guard(mtxGlobalAccess);
|
||||||
|
threadData->idx = threads.size();
|
||||||
|
threads.push_back(threadData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(slotIdx >= threadData->slots.size())
|
||||||
|
threadData->slots.resize(slotIdx+1);
|
||||||
|
threadData->slots[slotIdx] = pData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TlsAbstraction tls; // TLS abstraction layer instance
|
||||||
|
|
||||||
|
Mutex mtxGlobalAccess; // Shared objects operation guard
|
||||||
|
size_t tlsSlots; // TLS storage counter
|
||||||
|
std::vector<ThreadData*> threads; // Array for all allocated data. Thread data pointers are placed here to allow data cleanup
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create global TLS storage object
|
||||||
|
static TlsStorage &getTlsStorage()
|
||||||
|
{
|
||||||
|
CV_SINGLETON_LAZY_INIT_REF(TlsStorage, new TlsStorage())
|
||||||
|
}
|
||||||
|
|
||||||
|
TLSDataContainer::TLSDataContainer()
|
||||||
|
{
|
||||||
|
key_ = (int)getTlsStorage().reserveSlot(); // Reserve key from TLS storage
|
||||||
|
}
|
||||||
|
|
||||||
|
TLSDataContainer::~TLSDataContainer()
|
||||||
|
{
|
||||||
|
CV_Assert(key_ == -1); // Key must be released in child object
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLSDataContainer::release()
|
||||||
|
{
|
||||||
|
std::vector<void*> data;
|
||||||
|
data.reserve(32);
|
||||||
|
getTlsStorage().releaseSlot(key_, data); // Release key and get stored data for proper destruction
|
||||||
|
for(size_t i = 0; i < data.size(); i++) // Delete all assosiated data
|
||||||
|
deleteDataInstance(data[i]);
|
||||||
|
key_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* TLSDataContainer::getData() const
|
||||||
|
{
|
||||||
|
void* pData = getTlsStorage().getData(key_); // Check if data was already allocated
|
||||||
|
if(!pData)
|
||||||
|
{
|
||||||
|
// Create new data instance and save it to TLS storage
|
||||||
|
pData = createDataInstance();
|
||||||
|
getTlsStorage().setData(key_, pData);
|
||||||
|
}
|
||||||
|
return pData;
|
||||||
|
}
|
||||||
|
|
||||||
|
TLSData<CoreTLSData>& getCoreTlsData()
|
||||||
|
{
|
||||||
|
CV_SINGLETON_LAZY_INIT_REF(TLSData<CoreTLSData>, new TLSData<CoreTLSData>())
|
||||||
|
}
|
||||||
|
|
||||||
#if defined CVAPI_EXPORTS && defined WIN32 && !defined WINCE
|
#if defined CVAPI_EXPORTS && defined WIN32 && !defined WINCE
|
||||||
#ifdef WINRT
|
#ifdef WINRT
|
||||||
@ -1037,141 +1204,13 @@ BOOL WINAPI DllMain(HINSTANCE, DWORD fdwReason, LPVOID lpReserved)
|
|||||||
// Not allowed to free resources if lpReserved is non-null
|
// Not allowed to free resources if lpReserved is non-null
|
||||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682583.aspx
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682583.aspx
|
||||||
cv::deleteThreadAllocData();
|
cv::deleteThreadAllocData();
|
||||||
cv::deleteThreadData();
|
cv::getTlsStorage().releaseThread();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#else
|
|
||||||
static pthread_key_t tlsKey = 0;
|
|
||||||
static pthread_once_t tlsKeyOnce = PTHREAD_ONCE_INIT;
|
|
||||||
|
|
||||||
static void deleteTLSStorage(void* data)
|
|
||||||
{
|
|
||||||
delete (TLSStorage*)data;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void makeKey()
|
|
||||||
{
|
|
||||||
int errcode = pthread_key_create(&tlsKey, deleteTLSStorage);
|
|
||||||
CV_Assert(errcode == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline TLSStorage* TLSStorage::get()
|
|
||||||
{
|
|
||||||
pthread_once(&tlsKeyOnce, makeKey);
|
|
||||||
TLSStorage* d = (TLSStorage*)pthread_getspecific(tlsKey);
|
|
||||||
if( !d )
|
|
||||||
{
|
|
||||||
d = new TLSStorage;
|
|
||||||
pthread_setspecific(tlsKey, d);
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class TLSContainerStorage
|
|
||||||
{
|
|
||||||
cv::Mutex mutex_;
|
|
||||||
std::vector<TLSDataContainer*> tlsContainers_;
|
|
||||||
public:
|
|
||||||
TLSContainerStorage() { }
|
|
||||||
~TLSContainerStorage()
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < tlsContainers_.size(); i++)
|
|
||||||
{
|
|
||||||
CV_DbgAssert(tlsContainers_[i] == NULL); // not all keys released
|
|
||||||
tlsContainers_[i] = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int allocateKey(TLSDataContainer* pContainer)
|
|
||||||
{
|
|
||||||
cv::AutoLock lock(mutex_);
|
|
||||||
tlsContainers_.push_back(pContainer);
|
|
||||||
return (int)tlsContainers_.size() - 1;
|
|
||||||
}
|
|
||||||
void releaseKey(int id, TLSDataContainer* pContainer)
|
|
||||||
{
|
|
||||||
cv::AutoLock lock(mutex_);
|
|
||||||
CV_Assert(tlsContainers_[id] == pContainer);
|
|
||||||
tlsContainers_[id] = NULL;
|
|
||||||
// currently, we don't go into thread's TLSData and release data for this key
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroyData(int key, void* data)
|
|
||||||
{
|
|
||||||
cv::AutoLock lock(mutex_);
|
|
||||||
TLSDataContainer* k = tlsContainers_[key];
|
|
||||||
if (!k)
|
|
||||||
return;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
k->deleteDataInstance(data);
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
CV_DbgAssert(k == NULL); // Debug this!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is a wrapper function that will ensure 'tlsContainerStorage' is constructed on first use.
|
|
||||||
// For more information: http://www.parashift.com/c++-faq/static-init-order-on-first-use.html
|
|
||||||
static TLSContainerStorage& getTLSContainerStorage()
|
|
||||||
{
|
|
||||||
CV_SINGLETON_LAZY_INIT_REF(TLSContainerStorage, new TLSContainerStorage())
|
|
||||||
}
|
|
||||||
|
|
||||||
TLSDataContainer::TLSDataContainer()
|
|
||||||
: key_(-1)
|
|
||||||
{
|
|
||||||
key_ = getTLSContainerStorage().allocateKey(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
TLSDataContainer::~TLSDataContainer()
|
|
||||||
{
|
|
||||||
getTLSContainerStorage().releaseKey(key_, this);
|
|
||||||
key_ = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* TLSDataContainer::getData() const
|
|
||||||
{
|
|
||||||
CV_Assert(key_ >= 0);
|
|
||||||
TLSStorage* tlsData = TLSStorage::get();
|
|
||||||
void* data = tlsData->getData(key_);
|
|
||||||
if (!data)
|
|
||||||
{
|
|
||||||
data = this->createDataInstance();
|
|
||||||
CV_DbgAssert(data != NULL);
|
|
||||||
tlsData->setData(key_, data);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
TLSStorage::~TLSStorage()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < (int)tlsData_.size(); i++)
|
|
||||||
{
|
|
||||||
void*& data = tlsData_[i];
|
|
||||||
if (data)
|
|
||||||
{
|
|
||||||
getTLSContainerStorage().destroyData(i, data);
|
|
||||||
data = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tlsData_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
TLSData<CoreTLSData>& getCoreTlsData()
|
|
||||||
{
|
|
||||||
CV_SINGLETON_LAZY_INIT_REF(TLSData<CoreTLSData>, new TLSData<CoreTLSData>())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef CV_COLLECT_IMPL_DATA
|
#ifdef CV_COLLECT_IMPL_DATA
|
||||||
ImplCollector& getImplData()
|
ImplCollector& getImplData()
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user