Fix various issues found with thread sanitizer on macOS
This commit is contained in:
@@ -184,9 +184,8 @@ void SoftAssetReferenceBase::OnUnloaded(Asset* asset)
|
||||
Asset::Asset(const SpawnParams& params, const AssetInfo* info)
|
||||
: ManagedScriptingObject(params)
|
||||
, _refCount(0)
|
||||
, _loadingTask(nullptr)
|
||||
, _isLoaded(false)
|
||||
, _loadFailed(false)
|
||||
, _loadState(0)
|
||||
, _loadingTask(0)
|
||||
, _deleteFileOnUnload(false)
|
||||
, _isVirtual(false)
|
||||
{
|
||||
@@ -225,10 +224,10 @@ void Asset::OnDeleteObject()
|
||||
|
||||
// Unload asset data (in a safe way to protect asset data)
|
||||
Locker.Lock();
|
||||
if (_isLoaded)
|
||||
if (IsLoaded())
|
||||
{
|
||||
unload(false);
|
||||
_isLoaded = false;
|
||||
Platform::AtomicStore(&_loadState, (int64)LoadState::Unloaded);
|
||||
}
|
||||
Locker.Unlock();
|
||||
|
||||
@@ -319,11 +318,6 @@ void Asset::ChangeID(const Guid& newId)
|
||||
Content::onAssetChangeId(this, oldId, newId);
|
||||
}
|
||||
|
||||
bool Asset::LastLoadFailed() const
|
||||
{
|
||||
return _loadFailed != 0;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool Asset::ShouldDeleteFileOnUnload() const
|
||||
@@ -337,7 +331,7 @@ uint64 Asset::GetMemoryUsage() const
|
||||
{
|
||||
uint64 result = sizeof(Asset);
|
||||
Locker.Lock();
|
||||
if (_loadingTask)
|
||||
if (Platform::AtomicRead(&_loadingTask))
|
||||
result += sizeof(ContentLoadTask);
|
||||
result += (OnLoaded.Capacity() + OnReloading.Capacity() + OnUnloaded.Capacity()) * sizeof(EventType::FunctionType);
|
||||
Locker.Unlock();
|
||||
@@ -368,7 +362,7 @@ void Asset::Reload()
|
||||
{
|
||||
// Unload current data
|
||||
unload(true);
|
||||
_isLoaded = false;
|
||||
Platform::AtomicStore(&_loadState, (int64)LoadState::Unloaded);
|
||||
}
|
||||
|
||||
// Start reloading process
|
||||
@@ -426,7 +420,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
|
||||
|
||||
// Check if has missing loading task
|
||||
Platform::MemoryBarrier();
|
||||
const auto loadingTask = _loadingTask;
|
||||
const auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
|
||||
if (loadingTask == nullptr)
|
||||
{
|
||||
LOG(Warning, "WaitForLoaded asset \'{0}\' failed. No loading task attached and asset is not loaded.", ToString());
|
||||
@@ -516,7 +510,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
|
||||
Content::tryCallOnLoaded((Asset*)this);
|
||||
}
|
||||
|
||||
return _isLoaded == 0;
|
||||
return !IsLoaded();
|
||||
}
|
||||
|
||||
void Asset::InitAsVirtual()
|
||||
@@ -525,14 +519,14 @@ void Asset::InitAsVirtual()
|
||||
_isVirtual = true;
|
||||
|
||||
// Be a loaded thing
|
||||
_isLoaded = true;
|
||||
Platform::AtomicStore(&_loadState, (int64)LoadState::Loaded);
|
||||
}
|
||||
|
||||
void Asset::CancelStreaming()
|
||||
{
|
||||
// Cancel loading task but go over asset locker to prevent case if other load threads still loads asset while it's reimported on other thread
|
||||
Locker.Lock();
|
||||
ContentLoadTask* loadTask = _loadingTask;
|
||||
auto loadTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
|
||||
Locker.Unlock();
|
||||
if (loadTask)
|
||||
{
|
||||
@@ -575,10 +569,11 @@ ContentLoadTask* Asset::createLoadingTask()
|
||||
void Asset::startLoading()
|
||||
{
|
||||
ASSERT(!IsLoaded());
|
||||
ASSERT(_loadingTask == nullptr);
|
||||
_loadingTask = createLoadingTask();
|
||||
ASSERT(_loadingTask != nullptr);
|
||||
_loadingTask->Start();
|
||||
ASSERT(Platform::AtomicRead(&_loadingTask) == 0);
|
||||
auto loadingTask = createLoadingTask();
|
||||
ASSERT(loadingTask != nullptr);
|
||||
Platform::AtomicStore(&_loadingTask, (intptr)loadingTask);
|
||||
loadingTask->Start();
|
||||
}
|
||||
|
||||
void Asset::releaseStorage()
|
||||
@@ -593,7 +588,7 @@ bool Asset::IsInternalType() const
|
||||
bool Asset::onLoad(LoadAssetTask* task)
|
||||
{
|
||||
// It may fail when task is cancelled and new one is created later (don't crash but just end with an error)
|
||||
if (task->Asset.Get() != this || _loadingTask == nullptr)
|
||||
if (task->Asset.Get() != this || Platform::AtomicRead(&_loadingTask) == 0)
|
||||
return true;
|
||||
|
||||
Locker.Lock();
|
||||
@@ -606,15 +601,15 @@ bool Asset::onLoad(LoadAssetTask* task)
|
||||
}
|
||||
const bool isLoaded = result == LoadResult::Ok;
|
||||
const bool failed = !isLoaded;
|
||||
_loadFailed = failed;
|
||||
_isLoaded = !failed;
|
||||
LoadState state = LoadState::Loaded;
|
||||
Platform::AtomicStore(&_loadState, (int64)(isLoaded ? LoadState::Loaded : LoadState::LoadFailed));
|
||||
if (failed)
|
||||
{
|
||||
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(result));
|
||||
}
|
||||
|
||||
// Unlink task
|
||||
_loadingTask = nullptr;
|
||||
Platform::AtomicStore(&_loadingTask, 0);
|
||||
ASSERT(failed || IsLoaded() == isLoaded);
|
||||
|
||||
Locker.Unlock();
|
||||
@@ -663,12 +658,12 @@ void Asset::onUnload_MainThread()
|
||||
OnUnloaded(this);
|
||||
|
||||
// Check if is during loading
|
||||
if (_loadingTask != nullptr)
|
||||
auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
|
||||
if (loadingTask != nullptr)
|
||||
{
|
||||
// Cancel loading
|
||||
auto task = _loadingTask;
|
||||
_loadingTask = nullptr;
|
||||
Platform::AtomicStore(&_loadingTask, 0);
|
||||
LOG(Warning, "Cancel loading task for \'{0}\'", ToString());
|
||||
task->Cancel();
|
||||
loadingTask->Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,11 +34,17 @@ public:
|
||||
DECLARE_ENUM_7(LoadResult, Ok, Failed, MissingDataChunk, CannotLoadData, CannotLoadStorage, CannotLoadInitData, InvalidData);
|
||||
|
||||
protected:
|
||||
volatile int64 _refCount;
|
||||
ContentLoadTask* _loadingTask;
|
||||
enum class LoadState : int64
|
||||
{
|
||||
Unloaded,
|
||||
Loaded,
|
||||
LoadFailed,
|
||||
};
|
||||
|
||||
volatile int64 _refCount;
|
||||
volatile int64 _loadState;
|
||||
volatile intptr _loadingTask;
|
||||
|
||||
int8 _isLoaded : 1; // Indicates that asset is loaded
|
||||
int8 _loadFailed : 1; // Indicates that last asset loading has failed
|
||||
int8 _deleteFileOnUnload : 1; // Indicates that asset source file should be removed on asset unload
|
||||
int8 _isVirtual : 1; // Indicates that asset is pure virtual (generated or temporary, has no storage so won't be saved)
|
||||
|
||||
@@ -111,13 +117,16 @@ public:
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE bool IsLoaded() const
|
||||
{
|
||||
return _isLoaded != 0;
|
||||
return Platform::AtomicRead(&_loadState) == (int64)LoadState::Loaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if last asset loading failed, otherwise false.
|
||||
/// </summary>
|
||||
API_PROPERTY() bool LastLoadFailed() const;
|
||||
API_PROPERTY() bool LastLoadFailed() const
|
||||
{
|
||||
return Platform::AtomicRead(&_loadState) == (int64)LoadState::LoadFailed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this asset is virtual (generated or temporary, has no storage so it won't be saved).
|
||||
|
||||
@@ -1434,8 +1434,7 @@ Asset::LoadResult VisualScript::load()
|
||||
if (_instances.HasItems())
|
||||
{
|
||||
// Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use
|
||||
_loadFailed = false;
|
||||
_isLoaded = true;
|
||||
Platform::AtomicStore(&_loadState, (int64)LoadState::Loaded);
|
||||
|
||||
// Setup scripting type
|
||||
CacheScriptingType();
|
||||
|
||||
@@ -31,12 +31,11 @@ public:
|
||||
if (Asset)
|
||||
{
|
||||
Asset->Locker.Lock();
|
||||
if (Asset->_loadingTask == this)
|
||||
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
|
||||
{
|
||||
Asset->_loadFailed = true;
|
||||
Asset->_isLoaded = false;
|
||||
Platform::AtomicStore(&Asset->_loadState, (int64)Asset::LoadState::LoadFailed);
|
||||
Platform::AtomicStore(&Asset->_loadingTask, 0);
|
||||
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
|
||||
Asset->_loadingTask = nullptr;
|
||||
}
|
||||
Asset->Locker.Unlock();
|
||||
}
|
||||
@@ -77,8 +76,8 @@ protected:
|
||||
if (Asset)
|
||||
{
|
||||
Asset->Locker.Lock();
|
||||
if (Asset->_loadingTask == this)
|
||||
Asset->_loadingTask = nullptr;
|
||||
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
|
||||
Platform::AtomicStore(&Asset->_loadingTask, 0);
|
||||
Asset->Locker.Unlock();
|
||||
Asset = nullptr;
|
||||
}
|
||||
@@ -91,8 +90,8 @@ protected:
|
||||
if (Asset)
|
||||
{
|
||||
Asset->Locker.Lock();
|
||||
if (Asset->_loadingTask == this)
|
||||
Asset->_loadingTask = nullptr;
|
||||
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
|
||||
Platform::AtomicStore(&Asset->_loadingTask, 0);
|
||||
Asset->Locker.Unlock();
|
||||
Asset = nullptr;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#define FORCE_NOINLINE __attribute__((noinline))
|
||||
#define NO_RETURN __attribute__((noreturn))
|
||||
#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
|
||||
#define NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
|
||||
#define PACK_BEGIN()
|
||||
#define PACK_END() __attribute__((__packed__))
|
||||
#define ALIGN_BEGIN(_align)
|
||||
@@ -46,6 +47,7 @@
|
||||
#define FORCE_NOINLINE __attribute__((noinline))
|
||||
#define NO_RETURN __attribute__((noreturn))
|
||||
#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
|
||||
#define NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
|
||||
#define PACK_BEGIN()
|
||||
#define PACK_END() __attribute__((__packed__))
|
||||
#define ALIGN_BEGIN(_align)
|
||||
@@ -72,6 +74,7 @@
|
||||
#define FORCE_NOINLINE __declspec(noinline)
|
||||
#define NO_RETURN __declspec(noreturn)
|
||||
#define NO_SANITIZE_ADDRESS
|
||||
#define NO_SANITIZE_THREAD
|
||||
#define PACK_BEGIN() __pragma(pack(push, 1))
|
||||
#define PACK_END() ; __pragma(pack(pop))
|
||||
#define ALIGN_BEGIN(_align) __declspec(align(_align))
|
||||
|
||||
@@ -115,7 +115,7 @@ public:
|
||||
|
||||
// Rollback state and cancel
|
||||
_context = nullptr;
|
||||
_state = TaskState::Queued;
|
||||
SetState(TaskState::Queued);
|
||||
Cancel();
|
||||
}
|
||||
|
||||
@@ -148,8 +148,7 @@ protected:
|
||||
ASSERT(_context != nullptr);
|
||||
_context->OnCancelSync(this);
|
||||
_context = nullptr;
|
||||
|
||||
_state = TaskState::Canceled;
|
||||
SetState(TaskState::Canceled);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
|
||||
void GPUTask::Execute(GPUTasksContext* context)
|
||||
{
|
||||
// Begin
|
||||
ASSERT(IsQueued() && _context == nullptr);
|
||||
_state = TaskState::Running;
|
||||
SetState(TaskState::Running);
|
||||
|
||||
// Perform an operation
|
||||
const auto result = run(context);
|
||||
@@ -19,7 +18,7 @@ void GPUTask::Execute(GPUTasksContext* context)
|
||||
// Process result
|
||||
if (IsCancelRequested())
|
||||
{
|
||||
_state = TaskState::Canceled;
|
||||
SetState(TaskState::Canceled);
|
||||
}
|
||||
else if (result != Result::Ok)
|
||||
{
|
||||
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
/// <summary>
|
||||
/// Locks the critical section.
|
||||
/// </summary>
|
||||
void Lock() const
|
||||
NO_SANITIZE_THREAD void Lock() const
|
||||
{
|
||||
pthread_mutex_lock(_mutexPtr);
|
||||
#if BUILD_DEBUG
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
/// Attempts to enter a critical section without blocking. If the call is successful, the calling thread takes ownership of the critical section.
|
||||
/// </summary>
|
||||
/// <returns>True if calling thread took ownership of the critical section.</returns>
|
||||
bool TryLock() const
|
||||
NO_SANITIZE_THREAD bool TryLock() const
|
||||
{
|
||||
return pthread_mutex_trylock(_mutexPtr) == 0;
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
/// <summary>
|
||||
/// Releases the lock on the critical section.
|
||||
/// </summary>
|
||||
void Unlock() const
|
||||
NO_SANITIZE_THREAD void Unlock() const
|
||||
{
|
||||
#if BUILD_DEBUG
|
||||
((UnixCriticalSection*)this)->_owningThreadId = 0;
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
|
||||
void Task::Start()
|
||||
{
|
||||
if (_state != TaskState::Created)
|
||||
if (GetState() != TaskState::Created)
|
||||
return;
|
||||
|
||||
OnStart();
|
||||
|
||||
// Change state
|
||||
_state = TaskState::Queued;
|
||||
SetState(TaskState::Queued);
|
||||
|
||||
// Add task to the execution queue
|
||||
Enqueue();
|
||||
@@ -110,7 +110,6 @@ Task* Task::ContinueWith(const Function<bool()>& action, Object* target)
|
||||
Task* Task::StartNew(Task* task)
|
||||
{
|
||||
ASSERT(task);
|
||||
|
||||
task->Start();
|
||||
return task;
|
||||
}
|
||||
@@ -137,11 +136,10 @@ Task* Task::StartNew(Function<bool()>::Signature& action, Object* target)
|
||||
|
||||
void Task::Execute()
|
||||
{
|
||||
// Begin
|
||||
if (IsCanceled())
|
||||
return;
|
||||
ASSERT(IsQueued());
|
||||
_state = TaskState::Running;
|
||||
SetState(TaskState::Running);
|
||||
|
||||
// Perform an operation
|
||||
bool failed = Run();
|
||||
@@ -149,7 +147,7 @@ void Task::Execute()
|
||||
// Process result
|
||||
if (IsCancelRequested())
|
||||
{
|
||||
_state = TaskState::Canceled;
|
||||
SetState(TaskState::Canceled);
|
||||
}
|
||||
else if (failed)
|
||||
{
|
||||
@@ -167,10 +165,8 @@ void Task::OnStart()
|
||||
|
||||
void Task::OnFinish()
|
||||
{
|
||||
ASSERT(IsRunning());
|
||||
ASSERT(!IsCancelRequested());
|
||||
|
||||
_state = TaskState::Finished;
|
||||
ASSERT(IsRunning() && !IsCancelRequested());
|
||||
SetState(TaskState::Finished);
|
||||
|
||||
// Send event further
|
||||
if (_continueWith)
|
||||
@@ -181,7 +177,7 @@ void Task::OnFinish()
|
||||
|
||||
void Task::OnFail()
|
||||
{
|
||||
_state = TaskState::Failed;
|
||||
SetState(TaskState::Failed);
|
||||
|
||||
// Send event further
|
||||
if (_continueWith)
|
||||
@@ -209,8 +205,7 @@ void Task::OnCancel()
|
||||
const auto state = GetState();
|
||||
if (state != TaskState::Finished && state != TaskState::Failed)
|
||||
{
|
||||
_state = TaskState::Canceled;
|
||||
|
||||
SetState(TaskState::Canceled);
|
||||
OnEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ class FLAXENGINE_API Task : public Object, public NonCopyable
|
||||
//
|
||||
|
||||
protected:
|
||||
|
||||
/// <summary>
|
||||
/// The cancel flag used to indicate that there is request to cancel task operation.
|
||||
/// </summary>
|
||||
@@ -65,14 +64,18 @@ protected:
|
||||
/// </summary>
|
||||
Task* _continueWith = nullptr;
|
||||
|
||||
public:
|
||||
void SetState(TaskState state)
|
||||
{
|
||||
Platform::AtomicStore((int64 volatile*)&_state, (uint64)state);
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the task state.
|
||||
/// </summary>
|
||||
FORCE_INLINE TaskState GetState() const
|
||||
{
|
||||
return static_cast<TaskState>(Platform::AtomicRead((int64 volatile*)&_state));
|
||||
return (TaskState)Platform::AtomicRead((int64 const volatile*)&_state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,7 +97,6 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Checks if operation failed.
|
||||
/// </summary>
|
||||
@@ -153,7 +155,6 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Starts this task execution (and will continue with all children).
|
||||
/// </summary>
|
||||
@@ -199,7 +200,6 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Continues that task execution with a given task (will call Start on given task after finishing that one).
|
||||
/// </summary>
|
||||
@@ -232,7 +232,6 @@ public:
|
||||
Task* ContinueWith(const Function<bool()>& action, Object* target = nullptr);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Starts the new task.
|
||||
/// </summary>
|
||||
@@ -312,7 +311,6 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/// <summary>
|
||||
/// Executes this task.
|
||||
/// It should be called by the task consumer (thread pool or other executor of this task type).
|
||||
@@ -328,7 +326,6 @@ protected:
|
||||
virtual bool Run() = 0;
|
||||
|
||||
protected:
|
||||
|
||||
virtual void Enqueue() = 0;
|
||||
virtual void OnStart();
|
||||
virtual void OnFinish();
|
||||
|
||||
Reference in New Issue
Block a user