summaryrefslogtreecommitdiffstats
path: root/libkdepim/weaver.h
diff options
context:
space:
mode:
Diffstat (limited to 'libkdepim/weaver.h')
-rw-r--r--libkdepim/weaver.h449
1 files changed, 449 insertions, 0 deletions
diff --git a/libkdepim/weaver.h b/libkdepim/weaver.h
new file mode 100644
index 000000000..00c624d84
--- /dev/null
+++ b/libkdepim/weaver.h
@@ -0,0 +1,449 @@
+/* -*- C++ -*-
+
+ 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 Qt toolkit
+ from Troll Tech, Norway. $
+
+*/
+
+#ifndef WEAVER_H
+#define WEAVER_H
+
+extern "C"
+{
+#include <stdarg.h>
+#include <unistd.h>
+#include <stdio.h>
+}
+
+#include <qobject.h>
+#include <qptrlist.h>
+#include <qthread.h>
+#include <qwaitcondition.h>
+#include <qmutex.h>
+#include <qevent.h>
+
+#include <kdepimmacros.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.
+ */
+
+ KDE_EXPORT extern bool Debug;
+ KDE_EXPORT extern int DebugLevel;
+
+ KDE_EXPORT inline void setDebugLevel (bool debug, int level)
+ {
+ Debug = debug;
+ DebugLevel = level;
+ }
+
+ KDE_EXPORT inline void debug(int severity, const char * cformat, ...)
+#ifdef __GNUC__
+ __attribute__ ( (format (printf, 2, 3 ) ) )
+#endif
+;
+
+ KDE_EXPORT inline void debug(int severity, const char * cformat, ...)
+ {
+ if ( Debug == true && ( severity<=DebugLevel || severity == 0) )
+ {
+ static QMutex mutex;
+ QString 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 KDE_EXPORT Event : public QCustomEvent
+ {
+ 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 KDE_EXPORT Job : public QObject
+ {
+ Q_OBJECT
+ public:
+ /** Construct a Job object. */
+ Job(QObject* 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;
+
+ QMutex *m_mutex;
+
+ Thread * m_thread;
+
+ QWaitCondition *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 KDE_EXPORT Thread : public QThread
+ {
+ 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 QThread) 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 KDE_EXPORT Weaver : public QObject
+ {
+ Q_OBJECT
+ public:
+ Weaver (QObject* 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 (QPtrList<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 ( QEvent* );
+ /** The thread inventory. */
+ QPtrList<Thread> m_inventory;
+ /** The job queue. */
+ QPtrList<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. */
+ QWaitCondition m_jobAvailable;
+ /** Wait for a job to finish. */
+ QWaitCondition 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. */
+ QMutex *m_mutex;
+ };
+} // namespace ThreadWeaver
+} // namespace KPIM
+
+#endif // defined WEAVER_H