You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdepim/libtdepim/weaver.h

451 lines
18 KiB

/*
This file declares the Weaver, Job and Thread classes.
$ Author: Mirko Boehm $
$ Copyright: (C) 2004, Mirko Boehm $
$ Contact: mirko@kde.org
http://www.kde.org
http://www.hackerbuero.org $
$ License: LGPL with the following explicit clarification:
This code may be linked against any version of the TQt toolkit
from Troll Tech, Norway. $
*/
#ifndef WEAVER_H
#define WEAVER_H
extern "C"
{
#include <stdarg.h>
#include <unistd.h>
#include <stdio.h>
}
#include <tqobject.h>
#include <tqptrlist.h>
#include <tqthread.h>
#include <tqwaitcondition.h>
#include <tqmutex.h>
#include <tqevent.h>
#include <kdemacros.h>
namespace KPIM {
namespace ThreadWeaver {
/** This method prints a text message on the screen, if debugging is
enabled. Otherwise, it does nothing. The message is thread safe,
therefore providing that the messages appear in the order they where
issued by the different threads.
All messages are suppressed when Debug is false. All messages with a
lower importance (higher number) than DebugLevel will be suppressed,
too. Debug level 0 messages will always be printed as long as
Debug is true.
We use our own debugging method, since debugging threads is a more
complicated experience than debugging single threaded
contexts. This might change in future in the way that debug
prints it's messages to another logging facility provided by
the platform.
Use setDebugLevel () to integrate adapt debug () to your platform.
*/
TDE_EXPORT extern bool Debug;
TDE_EXPORT extern int DebugLevel;
TDE_EXPORT inline void setDebugLevel (bool debug, int level)
{
Debug = debug;
DebugLevel = level;
}
TDE_EXPORT inline void debug(int severity, const char * cformat, ...)
#ifdef __GNUC__
__attribute__ ( (format (printf, 2, 3 ) ) )
#endif
;
TDE_EXPORT inline void debug(int severity, const char * cformat, ...)
{
if ( Debug == true && ( severity<=DebugLevel || severity == 0) )
{
static TQMutex mutex;
TQString text;
mutex.lock();
va_list ap;
va_start( ap, cformat );
vprintf (cformat, ap);
va_end (ap);
mutex.unlock();
}
}
class Thread;
class Job;
/** A class to represent the events threads generate and send to the
Weaver object. Examples include the start or end of the processing of a
job. Threads create the event objects and discard them after posting
the event, since the event receiver will assume ownership of the
event.
Events are associated to the sending thread and possibly to a
processed job.
Note: Do not create and use SPR/APR events, use Job::triggerSPR or
Job::triggerAPR to create the requests. */
class TDE_EXPORT Event : public TQCustomEvent
{
public:
enum Action {
NoAction = 0,
Finished, /// All jobs in the queue are done.
Suspended, /// Thread queueing halted.
ThreadStarted,
ThreadExiting,
ThreadBusy,
ThreadSuspended,
JobStarted,
JobFinished,
JobSPR, /// Synchronous Process Request
JobAPR /// Asynchronous Process Request
};
Event ( Action = NoAction, Thread * = 0, Job *job = 0);
/** Return the (custom defined) event type. */
static int type ();
/** The ID of the sender thread. */
Thread* thread () const;
/** The associated job. */
Job* job () const;
/** The action. */
Action action () const;
private:
Action m_action;
Thread *m_thread;
Job *m_job;
static const int Type;
};
/** A Job is a simple abstraction of an action that is to be
executed in a thread context.
It is essential for the ThreadWeaver library that as a kind of
convention, the different creators of Job objects do not touch the
protected data members of the Job until somehow notified by the
Job. See the SPR signal for an example.
Jobs may emit process requests as signals. Consider process requests
as a kind of synchronized call to the main thread.
Process Requests are a generic means for Job derivate programmers to have
the jobs interact with the creators (in the main thread) during
processing time. To avoid race
conditions and extensive locking and unlocking, the thread executing the
job is suspended during the period needed to process the request.
There are two kinds of process requests (we introduce abbreviations,
also in the signal names and the code,
only to save typing). Both are emitted by signals in the main thread:
- Synchronous Process Requests (SPR): Synchronous requests expect that the
complete request is performed in the slots connected to the signals. For
example, to update a widget according to the progress of the job, a SPR
may be used. In such cases, the Job's execution will be resumed
immediately after the signal has been processed.
- Asynchronous Process Requests (APR): For APRs, the job emitting the
signal does not assume anything about the amount of time needed to
perform the operation. Therefore, the thread is not waked after the
signal returns. The creator has to wake to thread whenever it is
ready by calling the wakeAPR method.
Note: When using an APR, you better make sure to receive the signal
with some object, otherwise the calling thread will block forever!
*/
class TDE_EXPORT Job : public TQObject
{
TQ_OBJECT
public:
/** Construct a Job object. */
Job(TQObject* parent=0, const char* name=0);
/** Destructor. */
virtual ~Job();
/** Perform the job. The thread in which this job is executed
is given as a parameter.
Do not overload this method to create your own Job
implementation, overload run(). */
virtual void execute(Thread*);
/** Returns true if the jobs's execute method finished. */
virtual bool isFinished() const;
/** Wake the thread after an APR has been processed. */
void wakeAPR ();
/** Process events related to this job (created by the processing
thread or the weaver or whoever). */
virtual void processEvent ( Event* );
signals:
/** This signal is emitted when a thread starts to process a job. */
void started ();
/** This signal is emitted when a job has been finished. */
void done ();
/** This signal is emitted when the job needs some operation done by
the main thread (usually the creator of the job).
It is important to understand that the emitting thread is
suspended until the signal returns.
When
the operation requested has been performed and this signal is
finished, the thread is automatically waked.
What operation needs to be performed has to be negotiated between
the two objects.
Note: This signal is an attempt to provide job programmers with a
generic way to interact while the job is executed. I am interested
in feedback about it's use. */
void SPR ();
/** Perform an Asynchronous Process Request. See SPR and the generic
Job documentation for a comparison. */
void APR ();
protected:
/** Lock this Job's mutex. */
void lock();
/** Unlock this Job's mutex. */
void unlock();
/** The method that actually performs the job. It is called from
execute(). This method is the one to overload it with the
job's task. */
virtual void run () = 0;
/** Return the thread that executes this job.
Returns zero of the job is not currently executed. */
Thread *thread();
/** Call with status = true to mark this job as done. */
virtual void setFinished(bool status);
/** Trigger a SPR.
This emits a signal in the main thread indicating the necessity of
a synchronized operation. */
void triggerSPR ();
/** Trigger an APR.
This emit a signal in the main thread indicating the necessity of
an unsynchronized operation.
The calling thread needs to ensure to wake the thread when the
operation is done. */
void triggerAPR ();
bool m_finished;
TQMutex *m_mutex;
Thread * m_thread;
TQWaitCondition *m_wc;
};
class Weaver;
/** The class Thread is used to represent the worker threads in
the weaver's inventory. It is not meant to be overloaded. */
class TDE_EXPORT Thread : public TQThread
{
public:
/** Create a thread.
These thread objects are only used inside the Weaver parent
object. */
Thread(Weaver *parent);
/** The destructor. */
~Thread();
/** Overloaded to execute the assigned job.
This will NOT return until shutdown() is called. The
thread will try to execute one job after the other, asking
the Weaver parent for a new job when the assigned one is
finished.
If no jobs are available, the thread will suspend.
After shutdown() is called, the thread will end as soon as
the currently assigned job is done.
*/
void run();
/* Provide the msleep() method (protected in TQThread) to be
available for executed jobs. */
void msleep(unsigned long msec);
/** Returns the thread id.
This id marks the respective Thread object, and must
therefore not be confused with, e.g., the pthread thread
ID. */
unsigned int id() const;
/** Post an event, will be received and processed by the Weaver. */
void post (Event::Action, Job* = 0);
private:
Weaver *m_parent;
const unsigned int m_id;
static unsigned int sm_Id;
static unsigned int makeId();
};
/** A weaver is the manager of worker threads (Thread objects) to
which it assigns jobs from it's queue. */
class TDE_EXPORT Weaver : public TQObject
{
TQ_OBJECT
public:
Weaver (TQObject* parent=0, const char* name=0,
int inventoryMin = 4, // minimal number of provided threads
int inventoryMax = 32); // maximum number of provided threads
virtual ~Weaver ();
/** Add a job to be executed. */
virtual void enqueue (Job*);
/** Enqueue all jobs in the given list.
This is an atomic operation, no jobs will start
before all jobs in the list are enqueued.
If you need a couple of jobs done and want to receive the
finished () signal afterwards, use this method to queue
them. Otherwise, when enqueueing your jobs
individually, there is a chance that you receive more than
one finished signal. */
void enqueue (TQPtrList<Job> jobs);
/** Remove a job from the queue.
If the job qas queued but not started so far, it is simple
removed from the queue. For now, it is unsupported to
dequeue a job once its execution has started.
For that case, you will have to provide a method to interrupt your
job's execution (and receive the done signal).
Returns true if the job has been dequeued, false if the
job has already been started or is not found in the
queue. */
virtual bool dequeue (Job*);
/** Remove all queued jobs.
Please note that this will not kill the threads, therefore
all jobs that are being processed will be continued. */
virtual void dequeue ();
/** Get notified when a thread has finished a job.
This is done automatically. */
// virtual void jobFinished(Thread *);
/** Finish all queued operations, then return.
This method is used in imperative programs that cannot react on
events to have the controlling (main) thread wait wait for the
jobs to finish.
Warning: This will suspend your thread!
Warning: If your jobs enter for example an infinite loop, this
will never return! */
virtual void finish();
/** Suspend job execution if state = true, otherwise resume
job execution if it was suspended.
When suspending, all threads are allowed to finish the
currently assigned job but will not receive a new
assignment.
When all threads are done processing the assigned job, the
signal suspended will() be emitted.
If you call suspend (true) and there are no jobs left to
be done, you will immidiately receive the suspended()
signal. */
virtual void suspend (bool state);
/** Is the queue empty? */
bool isEmpty () const;
/** Is the weaver idle?
The weaver is idle if no jobs are queued and no jobs are processed
by the threads (m_active is zero). */
bool isIdle () const;
/** Returns the number of pending jobs. */
int queueLength ();
/** Assign a job to the calling thread.
This is supposed to be called from the Thread objects in
the inventory.
Returns 0 if the weaver is shutting down, telling the
calling thread to finish and exit.
If no jobs are available and shut down is not in progress,
the calling thread is suspended until either condition is
met.
In previous, threads give the job they have completed. If this is
the first job, previous is zero. */
virtual Job* applyForWork (Thread *thread, Job *previous);
/** Lock the mutex for this weaver. The threads in the
inventory need to lock the weaver's mutex to synchronize
the job management. */
void lock ();
/** Unlock. See lock(). */
void unlock ();
/** Post an event that is handled by this object, but in the main
(GUI) thread. Different threads may use this method to communicate
with the main thread.
thread and job mark the objects associated with this event. */
void post (Event::Action, Thread* = 0, Job* = 0);
/** Returns the current number of threads in the inventory. */
int threads () const;
signals:
/** This signal is emitted when the Weaver has finished ALL currently
queued jobs.
If a number of jobs is enqueued sequentially, this signal might be
emitted a couple of times (what happens is that all already queued
jobs have been processed while you still add new ones). This is
not a bug, but the intended behaviour. */
void finished ();
/** Thread queueing has been suspended.
When suspend is called with state = true, all threads are
allowed to finish their job. When the last thread
finished, this signal is emitted. */
void suspended ();
/** This signal is emitted when a job is done. It is up to the
programmer if this signal or the done signal of the job is more
handy. */
void jobDone (Job*);
// The following signals are used mainly for debugging purposes.
void threadCreated (Thread *);
void threadDestroyed (Thread *);
void threadBusy (Thread *);
void threadSuspended (Thread *);
protected:
/** Schedule enqueued jobs to be executed by idle threads.
This will try to distribute as many jobs as possible
to all idle threads. */
void assignJobs();
/** Check incoming events for user defined ones. The threads use user
defined events to communicate with the Weaver. */
bool event ( TQEvent* );
/** The thread inventory. */
TQPtrList<Thread> m_inventory;
/** The job queue. */
TQPtrList<Job> m_assignments;
/** The number of jobs that are assigned to the worker
threads, but not finished. */
int m_active;
/** Stored setting. */
int m_inventoryMin;
/** Stored setting . */
int m_inventoryMax;
/** Wait condition all idle or done threads wait for. */
TQWaitCondition m_jobAvailable;
/** Wait for a job to finish. */
TQWaitCondition m_jobFinished;
/** Indicates if the weaver is shutting down and exiting it's
threads. */
bool m_shuttingDown;
/** m_running is set to true when a job is enqueued and set to false
when the job finishes that was the last in the queue.
E.g., this will flip from false to true to false when you
continuously enqueue one single job. */
bool m_running;
/** If m_suspend is true, no new jobs will be assigned to
threads.
Jobs may be queued, but will not be processed until suspend
(false) is called. */
bool m_suspend;
private:
/** Mutex to serialize operations. */
TQMutex *m_mutex;
};
} // namespace ThreadWeaver
} // namespace KPIM
#endif // defined WEAVER_H