n the section (
Scheduler chapter
) we
presented the idea of Scheduler and general requirements. The picture
(
Scheduler general design
) represents
the general design.
Scheduler general
design
|
There are two copies of the dependency tree. Each tree consists of proxies
that hold references to actual business objects located elsewhere (outside of
Scheduler class or out-of-process or on a different machine). The Switch
directs new events into the first copy. The second copy does processing of
previously accumulated requests (events) and sends replies to clients and
processing orders to actual business objects. When the second copy finishes
the processing then the Switch reverts its direction: the first copy starts
processing and the second copy accepts new events (requests).
There are three types of requests (events) that are processed in substantially
different manner. The DeleteEvent is a request to remove a proxy from
dependency tree. The CreateEvent is a request to insert a proxy and establish
dependencies. The UpdateEvent is the signal that an existing proxy now points
to an out-of-date business object. There is no logic inside of Scheduler that
detects a situation when some client sends an UpdateEvent or DeleteEvent to a
proxy that does not exists. The Scheduler will not crash but the Event will be
lost. Hence, it is client's responsibility to wait for a reply that confirms
creation or deletion of a proxy.
The Scheduler class is a light reference to a Singleton. This reflects the
assumption that there is no reason to have more then one Scheduler per
process. However, there may be need to have more the one Scheduler in an
application if there is a class of business objects that needs quicker
processing.
The following is the prototype of the Scheduler class.
1\namespace ots { namespace scheduler {
2\
3\
class
CreateEvent;
4\
class
DeleteEvent;
5\
class
UpdateEvent;
6\
7\
class
Scheduler
8\
{
9\
public:
10\
Scheduler();
11\
Scheduler(
const Scheduler& sch );
12\
Scheduler&
operator= (const Scheduler* sch);
13\
public:
14\
void
acceptCreateEvent( const CreateEvent& event ) volatile;
15\
void
acceptDeleteEvent( const DeleteEvent& event ) volatile;
16\
void
acceptUpdateEvent( const UpdateEvent& event ) volatile;
17\
void
run();
18\
void
doNotQuit() volatile;
19\
void
doQuit() volatile;
20\
};
21\
22\}} //namespace ots,scheduler
The example member function implementations are as follows.
Scheduler::Scheduler( const Scheduler& sch )
{}
Scheduler& Scheduler::operator= (const Scheduler*
sch) { return *this; }
void Scheduler::run() { instance(); }
void Scheduler::acceptCreateEvent( const
CreateEvent& event ) volatile { instance().acceptCreateEvent(event);
}
The instance() is the Meyer singleton function that returns an Impl class
defined in a local compiler scope in a cpp-file.
1\namespace ots { namespace scheduler {
2\
3\class Switch : boost::noncopyable
4\{
5\public:
6\...
7\
enum
{ numberOfStates=2 };
8\
unsigned
char inputIndex() const volatile;
9\
unsigned
char processingIndex() const volatile;
10\...
11\};
12\
13\class Impl : boost::noncopyable
14\{
15\...
16\public:
17\
typedef
config::RWMutex::type QuitMutex;
18\
typedef
config::ErrorLog::type ErrorLog;
19\
typedef
config::ConsecutiveQueue<DeleteEvent>::type DeleteQueue;
20\
typedef
config::ConsecutiveQueue<CreateEvent>::type CreateQueue;
21\
typedef
config::SignaledBarrier::type SignaledBarrier;
22\
typedef
config::CycleCounter::type CycleCounter;
23\
typedef
NonBlockingQueue<Node::Impl> UpdateQueue;
24\
typedef
NonBlockingQueue<Node::Impl> WaitingQueue;
25\private:
26\
mutable
QuitMutex theQuitMutex;
27\
bool
theQuit;
28\
Switch
theSwitch;
29\
CycleCounter
theCycleCounter;
30\
DeleteQueue
theDeleteQueues[Switch::numberOfStates];
31\
CreateQueue
theCreateQueues[Switch::numberOfStates];
32\
UpdateQueue
theUpdateQueue;
33\
WaitingQueue
theWaitingQueues[Switch::numberOfStates];
34\
NodeRegistry
theRegistry[Switch::numberOfStates];
35\
SignaledBarrier
theInputDataBarriers[Switch::numberOfStates];
36\...
37\};
38\
39\volatile Impl& instance()
40\{
41\
volatile
static Impl impl;
42\
return
impl;
43\}
Lines 6, 10, 15, 36. At this point we are concentrating on the member fields
of the Impl class. We omitted the member functions and implementation details.
Lines 19,20. The ConsequtiveQueue is a simple store-by-value container with a
mutex that has to be locked before every access operation. We use such object
to store CreateEvents and DeleteEvents because we expect that there will be
very few of those. The application would be loaded before the work day starts.
Afterwards, the new Create and Delete requests result from rare manual
operations.
Lines 24 and 33. The new UpdateEvent objects are stored in the WaitingQueue.
The WaitingQueue type is a typedef of NonBlockingQueue. Hence, submission of a
new UpdateEvent is a quick operation without memory overhead for repeated
submission.
Lines 23 and 32. The UpdateQueue is the queue that contains at all times the
Nodes (of the dependency tree) that are ready for immediate processing. These
are, by definition, the out-of-date Nodes that do not have out-of-date parents
in the dependency tree.
Line 34. The NodeRegistry is a map from InstanceNames (identifier of business
object and its proxy) to Nodes. It is the container that assumes ownership of
Nodes for purposes of memory management.
Line 35. theInputDataBarriers are included to prevent idle cycling if there is
no new requests to process. theInputDataBarrier is closed when there is no new
data and open otherwise.
Lines 26,27. theQuit field indicates whether there has been a request to exit
processing. theQuitMutex is the mutex that protects theQuit.
Note that all accept* functions have only basic exception guarantee. This is
so because of possible thread_resource_error. Hence, business objects should
be resilient to repeated submission of the same update request.
|