Fix AbandonableTask being utterly broken

This commit is contained in:
Tamás Bálint Misius 2019-03-22 15:21:13 +01:00
parent ad712272a3
commit 5192356b76
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
4 changed files with 42 additions and 137 deletions

View File

@ -2,79 +2,18 @@
#include "Platform.h"
#ifdef DEBUG
# define DEBUGTHREADS
// * Uncomment above if AbandonableTasks cause issues.
// * Three outputs should be possible:
// * The task is properly finished:
// AbandonableTask @ [ptr] ctor
// AbandonableTask @ [ptr] created
// ...
// AbandonableTask @ [ptr] finished
// AbandonableTask @ [ptr] joined
// AbandonableTask @ [ptr] dtor
// * The task is abandoned but its thread has finished:
// AbandonableTask @ [ptr] ctor
// AbandonableTask @ [ptr] created
// ...
// AbandonableTask @ [ptr] abandoned
// AbandonableTask @ [ptr] joined
// AbandonableTask @ [ptr] dtor
// * The task is abandoned before its thread has finished:
// AbandonableTask @ [ptr] ctor
// AbandonableTask @ [ptr] created
// ...
// AbandonableTask @ [ptr] abandoned
// ...
// AbandonableTask @ [ptr] detached
// AbandonableTask @ [ptr] dtor
// * Anything other than those means something is broken.
#endif
#ifdef DEBUGTHREADS
# include <iostream>
#endif
void AbandonableTask::Start()
void AbandonableTask::doWork_wrapper()
{
thDone = false;
done = false;
thAbandoned = false;
progress = 0;
status = "";
before();
pthread_mutex_init (&taskMutex, NULL);
pthread_create(&doWorkThread, 0, &AbandonableTask::doWork_helper, this);
Task::doWork_wrapper();
pthread_cond_signal(&done_cv);
#ifdef DEBUGTHREADS
std::cerr << "AbandonableTask @ " << this << " created" << std::endl;
#endif
}
TH_ENTRY_POINT void * AbandonableTask::doWork_helper(void * ref)
{
Task::doWork_helper(ref);
AbandonableTask *task = (AbandonableTask *)ref;
pthread_mutex_lock(&task->taskMutex);
pthread_cond_signal(&task->done_cv);
bool abandoned = task->thAbandoned;
pthread_mutex_unlock(&task->taskMutex);
pthread_mutex_lock(&taskMutex);
bool abandoned = thAbandoned;
pthread_mutex_unlock(&taskMutex);
if (abandoned)
{
pthread_detach(task->doWorkThread);
pthread_mutex_destroy(&task->taskMutex);
#ifdef DEBUGTHREADS
std::cerr << "AbandonableTask @ " << ref << " detached" << std::endl;
#endif
// We've done Task::~Task's job already.
task->done = true;
delete task;
delete this;
}
return NULL;
}
void AbandonableTask::Finish()
@ -87,13 +26,11 @@ void AbandonableTask::Finish()
pthread_mutex_unlock(&taskMutex);
// Poll to make sure that the rest of the Task knows that it's
// done, not just us.
// done, not just us. This has to be done because the thread that started
// the AbandonableTask may or may not call Poll before calling Finish.
// This may call callbacks.
Poll();
#ifdef DEBUGTHREADS
std::cerr << "AbandonableTask @ " << this << " finished" << std::endl;
#endif
delete this;
}
@ -106,65 +43,32 @@ void AbandonableTask::Abandon()
// If thDone is true, the thread has already finished. We're
// not calling Poll because it may call callbacks, which
// an abandoned task shouldn't do. Instead we just delete the
// AbandonableTask after unlocking the mutex, which will
// take care of the thread if another Poll hasn't already
// (i.e. if done is false despite thDone being true).
// AbandonableTask after unlocking the mutex.
delete_this = true;
}
else
{
// If at this point thDone is still false, the thread is still
// running, meaning we can safely set thAbandoned and let
// AbandonableTask::doWork_helper detach the thread
// and delete the AbandonableTask later.
// AbandonableTask::doWork_wrapper delete the AbandonableTask later.
thAbandoned = true;
}
pthread_mutex_unlock(&taskMutex);
#ifdef DEBUGTHREADS
std::cerr << "AbandonableTask @ " << this << " abandoned" << std::endl;
#endif
if (delete_this)
{
delete this;
}
}
void AbandonableTask::Poll()
{
#ifdef DEBUGTHREADS
bool old_done = done;
#endif
Task::Poll();
#ifdef DEBUGTHREADS
if (done != old_done)
{
std::cerr << "AbandonableTask @ " << this << " joined" << std::endl;
}
#endif
}
AbandonableTask::AbandonableTask()
AbandonableTask::AbandonableTask() :
thAbandoned(false)
{
pthread_cond_init(&done_cv, NULL);
#ifdef DEBUGTHREADS
std::cerr << "AbandonableTask @ " << this << " ctor" << std::endl;
#endif
}
AbandonableTask::~AbandonableTask()
{
#ifdef DEBUGTHREADS
if (!done)
{
// Actually it'll be joined later in Task::~Task, but the debug
// messages look more consistent this way.
std::cerr << "AbandonableTask @ " << this << " joined" << std::endl;
}
std::cerr << "AbandonableTask @ " << this << " dtor" << std::endl;
#endif
pthread_cond_destroy(&done_cv);
}

View File

@ -8,16 +8,14 @@ class AbandonableTask : public Task
pthread_cond_t done_cv;
public:
void Start() override;
void Finish();
void Abandon();
void Poll() override;
AbandonableTask();
virtual ~AbandonableTask();
protected:
void doWork_wrapper() override;
bool thAbandoned;
TH_ENTRY_POINT static void * doWork_helper(void * ref);
};
#endif /* ABANDONABLETASK_H_ */

View File

@ -11,14 +11,11 @@ void Task::AddTaskListener(TaskListener * listener)
void Task::Start()
{
thDone = false;
done = false;
progress = 0;
status = "";
//taskMutex = PTHREAD_MUTEX_INITIALIZER;
before();
pthread_mutex_init (&taskMutex, NULL);
// This would use a lambda if we didn't use pthreads and if I dared omit
// the TH_ENTRY_POINT from the function type.
pthread_create(&doWorkThread, 0, &Task::doWork_helper, this);
pthread_detach(doWorkThread);
}
int Task::GetProgress()
@ -83,25 +80,26 @@ void Task::Poll()
if(newDone!=done)
{
done = newDone;
pthread_join(doWorkThread, NULL);
pthread_mutex_destroy(&taskMutex);
after();
notifyDoneMain();
}
}
}
Task::Task() :
progress(0),
done(false),
thProgress(0),
thDone(false),
listener(NULL)
{
pthread_mutex_init(&taskMutex, NULL);
}
Task::~Task()
{
if(!done)
{
pthread_join(doWorkThread, NULL);
pthread_mutex_destroy(&taskMutex);
}
}
void Task::before()
{
@ -123,13 +121,18 @@ void Task::after()
}
void Task::doWork_wrapper()
{
bool newSuccess = doWork();
pthread_mutex_lock(&taskMutex);
thSuccess = newSuccess;
thDone = true;
pthread_mutex_unlock(&taskMutex);
}
TH_ENTRY_POINT void *Task::doWork_helper(void *ref)
{
bool newSuccess = ((Task*)ref)->doWork();
pthread_mutex_lock(&((Task*)ref)->taskMutex);
((Task*)ref)->thSuccess = newSuccess;
((Task*)ref)->thDone = true;
pthread_mutex_unlock(&((Task*)ref)->taskMutex);
((Task *)ref)->doWork_wrapper();
return NULL;
}

View File

@ -17,7 +17,7 @@ public:
String GetError();
String GetStatus();
virtual void Poll();
Task() : listener(NULL) { progress = 0; thProgress = 0; }
Task();
virtual ~Task();
protected:
int progress;
@ -35,12 +35,12 @@ protected:
TaskListener * listener;
pthread_t doWorkThread;
pthread_mutex_t taskMutex;
pthread_cond_t taskCond;
virtual void before();
virtual void after();
virtual bool doWork();
virtual void doWork_wrapper();
TH_ENTRY_POINT static void * doWork_helper(void * ref);
virtual void notifyProgress(int progress);