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" #include "Platform.h"
#ifdef DEBUG void AbandonableTask::doWork_wrapper()
# 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()
{ {
thDone = false; Task::doWork_wrapper();
done = false; pthread_cond_signal(&done_cv);
thAbandoned = false;
progress = 0;
status = "";
before();
pthread_mutex_init (&taskMutex, NULL);
pthread_create(&doWorkThread, 0, &AbandonableTask::doWork_helper, this);
#ifdef DEBUGTHREADS pthread_mutex_lock(&taskMutex);
std::cerr << "AbandonableTask @ " << this << " created" << std::endl; bool abandoned = thAbandoned;
#endif pthread_mutex_unlock(&taskMutex);
}
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);
if (abandoned) if (abandoned)
{ {
pthread_detach(task->doWorkThread); delete this;
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;
} }
return NULL;
} }
void AbandonableTask::Finish() void AbandonableTask::Finish()
@ -87,13 +26,11 @@ void AbandonableTask::Finish()
pthread_mutex_unlock(&taskMutex); pthread_mutex_unlock(&taskMutex);
// Poll to make sure that the rest of the Task knows that it's // 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(); Poll();
#ifdef DEBUGTHREADS
std::cerr << "AbandonableTask @ " << this << " finished" << std::endl;
#endif
delete this; delete this;
} }
@ -106,65 +43,32 @@ void AbandonableTask::Abandon()
// If thDone is true, the thread has already finished. We're // If thDone is true, the thread has already finished. We're
// not calling Poll because it may call callbacks, which // not calling Poll because it may call callbacks, which
// an abandoned task shouldn't do. Instead we just delete the // an abandoned task shouldn't do. Instead we just delete the
// AbandonableTask after unlocking the mutex, which will // AbandonableTask after unlocking the mutex.
// take care of the thread if another Poll hasn't already
// (i.e. if done is false despite thDone being true).
delete_this = true; delete_this = true;
} }
else else
{ {
// If at this point thDone is still false, the thread is still // If at this point thDone is still false, the thread is still
// running, meaning we can safely set thAbandoned and let // running, meaning we can safely set thAbandoned and let
// AbandonableTask::doWork_helper detach the thread // AbandonableTask::doWork_wrapper delete the AbandonableTask later.
// and delete the AbandonableTask later.
thAbandoned = true; thAbandoned = true;
} }
pthread_mutex_unlock(&taskMutex); pthread_mutex_unlock(&taskMutex);
#ifdef DEBUGTHREADS
std::cerr << "AbandonableTask @ " << this << " abandoned" << std::endl;
#endif
if (delete_this) if (delete_this)
{ {
delete this; delete this;
} }
} }
void AbandonableTask::Poll() AbandonableTask::AbandonableTask() :
{ thAbandoned(false)
#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()
{ {
pthread_cond_init(&done_cv, NULL); pthread_cond_init(&done_cv, NULL);
#ifdef DEBUGTHREADS
std::cerr << "AbandonableTask @ " << this << " ctor" << std::endl;
#endif
} }
AbandonableTask::~AbandonableTask() 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); pthread_cond_destroy(&done_cv);
} }

View File

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

View File

@ -11,14 +11,11 @@ void Task::AddTaskListener(TaskListener * listener)
void Task::Start() void Task::Start()
{ {
thDone = false;
done = false;
progress = 0;
status = "";
//taskMutex = PTHREAD_MUTEX_INITIALIZER;
before(); 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_create(&doWorkThread, 0, &Task::doWork_helper, this);
pthread_detach(doWorkThread);
} }
int Task::GetProgress() int Task::GetProgress()
@ -83,24 +80,25 @@ void Task::Poll()
if(newDone!=done) if(newDone!=done)
{ {
done = newDone; done = newDone;
pthread_join(doWorkThread, NULL);
pthread_mutex_destroy(&taskMutex);
after(); after();
notifyDoneMain(); notifyDoneMain();
} }
} }
} }
Task::Task() :
progress(0),
done(false),
thProgress(0),
thDone(false),
listener(NULL)
{
pthread_mutex_init(&taskMutex, NULL);
}
Task::~Task() Task::~Task()
{ {
if(!done) pthread_mutex_destroy(&taskMutex);
{
pthread_join(doWorkThread, NULL);
pthread_mutex_destroy(&taskMutex);
}
} }
void Task::before() void Task::before()
@ -123,13 +121,18 @@ void Task::after()
} }
TH_ENTRY_POINT void * Task::doWork_helper(void * ref) void Task::doWork_wrapper()
{ {
bool newSuccess = ((Task*)ref)->doWork(); bool newSuccess = doWork();
pthread_mutex_lock(&((Task*)ref)->taskMutex); pthread_mutex_lock(&taskMutex);
((Task*)ref)->thSuccess = newSuccess; thSuccess = newSuccess;
((Task*)ref)->thDone = true; thDone = true;
pthread_mutex_unlock(&((Task*)ref)->taskMutex); pthread_mutex_unlock(&taskMutex);
}
TH_ENTRY_POINT void *Task::doWork_helper(void *ref)
{
((Task *)ref)->doWork_wrapper();
return NULL; return NULL;
} }

View File

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