he Scheduler accepts
directives via instances of CreateEvent, DeleteEvent and UpdateEvent classes
supplied to the operator() of the Scheduler's interface. Before any action is
taken, the Scheduler needs to process a CreateEvent. Consider the following
C++ code.
01\
Scheduler
sch;
02\
int
counter=1;
03\
CreateEvent
ce1("Node1",Proxy(),Origin(),counter);
04\
sch(ce1);
05\
ce1.origin().waitOn(counter,1);
Line 01: Create the Scheduler object.
Line 02: Set a synchronization counter value.
Line 03: Create a CreateEvent object. Proxy() and Origin() are default
implementations of IProxy and IOrigin interfaces. These will be explained in
the moment. The "Node1" is the identification string for the dependency node
that is to be created.
Line 04: The Scheduler accepts the CreateEvent.
Line 05: Wait for a notification that the processing of the CreateEvent has
been completed. Once the Line 05 releases control, the Scheduler is ready to
receive UpdateEvent and DeleteEvent objects directed to the "Node1".
An IOrigin-implementing object is a reference to the client application that
originally supplied the CreateEvent. It is a sink for all replies and
diagnostic messages. An IProxy-implementing object is the reference to the
business object that implements the business logic. It receives "execute now"
messages. These two interfaces are the hooks that the user has to customize to
adapt the Scheduler to do anything useful. We next explain these interfaces,
default implementation and provide an example of IProxy and
IOrigin-implementing objects that record all supplied information into a text
file. The following section
(
Acceptance test for the
Scheduler section
) describes a working example.
The following code illustrates the IOrigin and its default implementation.
1\namespace ots { namespace scheduler {
2\
3\
class
Origin
4\
{
5\
public:
6\
typedef
config::SynchCounter::type SynchCounter;
7\
enum
event
8\
{
9\
_firstEvent=1,
10\
created,
11\
nodeIsAbsent,
12\
failedToDelete,
13\
failedToCreate,
14\
aParentAbsent,
...
27\
_lastEvent
28\
};
29\
static
std::string eventToString( event e );
30\
struct
IOrigin
31\
{
32\
typedef
config::SynchCounter::type SynchCounter;
33\
virtual
void notify( const SynchCounter&, event) volatile =0;
34\
virtual
void notify( event) volatile =0;
35\
virtual
event waitOn( const SynchCounter&, int ) volatile =0;
36\
virtual
std::string toString() volatile const =0;
37\
};
38\
public:
39\
class
DefaultImpl : public IOrigin
40\
{
...
60\
protected:
61\
void
defaultNotify( const SynchCounter&, event) volatile;
62\
void
defaultNotify( event) volatile;
63\
event
defaultWaitOn( const SynchCounter&, int ) volatile;
64\
};
65\
private:
66\
boost::shared_ptr<volatile
IOrigin> theImpl;
67\
public:
68\
Origin()
: theImpl(new DefaultImpl()) {}
69\
explicit
Origin( const boost::shared_ptr<IOrigin>& impl ) : theImpl(impl)
{}
70\
Origin(
IOrigin* impl ) : theImpl(impl) {}
71\
Origin(
const Origin& origin ) : theImpl(origin.theImpl) {}
72\
Origin&
operator=( const Origin& origin ) { theImpl=origin.theImpl; return *this;
}
73\
void
notify( const SynchCounter& c, event e );
74\
void
notify( event e );
75\
event
waitOn( const SynchCounter& c, int times ) { return
theImpl->waitOn(c,times); }
76\
std::string
toString() const;
77\
};
The lines 30-37 are the definition of the interface IOrigin. The notify
functions are called by the Scheduler when anything note-worthy happens. The
enum event in the lines 7-28 describes the possibilities. The function waitOn
is a synchronization tool. It blocks the current thread until it detects that
the current Origin object received int-many calls to notify with the
synchronization counter at least as large the one supplied as the argument.
The lines 68-76 contain the implementation of the IOrigin interface and
copy-by-value semantics.
The following code illustrates possible custom implementation of the IOrigin.
The function makeFileOrigin, lines 38-41 below, returns an Origin object that
sends all calls to notify into a test file.
1\
class
FileOrigin : public Origin::DefaultImpl
2\
{
3\
public:
4\
typedef
config::ConditionVariableGuard<FileOrigin>::type Guard;
5\
typedef
config::ConstConditionVariableGuard<FileOrigin>::type ConstGuard;
6\
private:
7\
std::string
theName;
8\
std::ofstream
theFile;
9\
public:
10\
FileOrigin(
const std::string& name, const std::string& fileName )
11\
:
12\
theName(name),
13\
theFile(fileName.c_str())
14\
{}
15\
virtual
void notify( const SynchCounter& c, Origin::event e ) volatile
16\
{
17\
defaultNotify(c,e);
18\
{
19\
Guard
guard(this,theMutex);
20\
guard->theFile<<guard->theName<<"
: "<<c<<" : "<<Origin::eventToString(e)<<std::endl;
21\
}
22\
}
23\
virtual
void notify( Origin::event e ) volatile
24\
{
25\
defaultNotify(e);
26\
{
27\
Guard
guard(this,theMutex);
28\
guard->theFile<<guard->theName<<"
: "<<Origin::eventToString(e)<<std::endl;
29\
}
30\
}
31\
virtual
std::string toString() const volatile
32\
{
33\
ConstGuard
guard(this,theMutex);
34\
return
std::string("FileOrigin(")+guard->theName+std::string(")");
35\
}
36\
};
37\
38\
Origin
makeFileOrigin( const std::string& name, const std::string& fileName
)
39\
{
40\
return
Origin(new FileOrigin(name,fileName));
41\
}
The following code illustrates the IProxy and default implementation.
1\namespace ots { namespace scheduler {
2\
3\
struct
IProxy
4\
{
5\
virtual
bool update() =0;
6\
virtual
void cancelUpdate() =0;
7\
virtual
void dispose() =0;
8\
virtual
std::string toString() const =0;
9\
};
10\
11\
class
Proxy
12\
{
13\
private:
14\
struct
Null : public
IProxy
15\
{
16\
virtual
bool update() { return false; }
17\
virtual
void cancelUpdate() {}
18\
virtual
void dispose() {}
19\
virtual
std::string toString() const { return std::string("Proxy::Null()"); }
20\
};
21\
boost::shared_ptr<IProxy>
theImpl;
22\
public:
23\
Proxy(
IProxy* impl ) : theImpl(impl) {}
24\
explicit
Proxy( const boost::shared_ptr<IProxy>& impl ) : theImpl(impl)
{}
25\
Proxy()
: theImpl(new Null()) {}
26\
Proxy(
const Proxy& proxy ) : theImpl(proxy.theImpl) {}
27\
Proxy&
operator=( const Proxy& proxy ) { theImpl=proxy.theImpl; return *this;
}
28\
inline
bool update() { return theImpl->update(); }
29\
inline
void cancelUpdate() { theImpl->cancelUpdate(); }
30\
inline
void dispose() { theImpl->dispose(); }
31\
inline
std::string toString() const { return theImpl->toString(); }
32\
};
33\
34\}}
The lines 3-9 are the definition of IProxy interface. The function update() is
called when the Scheduler finds that it is time to update the business object.
When the update() returns control the Scheduler assumes that the business
object finished updating. The function cancelUpdate() is called when the
Scheduler is recovering from some catastrophic situation such as
boost::thread_resource_error. In such situation the Scheduler is discarding
all update events and notifies both Origin and Proxy about such action. The
function dispose() is called when the dependency node is removed. This is the
result of a DeleteEvent processing.
The following code is an example of custom implementation of Proxy. The
function makeFileProxy, lines 28-31 below, returns a Proxy object that
registers all calls in a text file.
1\
class
FileProxy : public IProxy
2\
{
3\
private:
4\
std::string
theName;
5\
std::ofstream
theFile;
6\
public:
7\
FileProxy(
const std::string& name, const std::string& fileName ) :
theName(name),theFile(fileName.c_str()) {}
8\
virtual
~FileProxy() {}
9\
bool
update()
10\
{
11\
theFile<<theName<<"
: Updated"<<std::endl;
12\
return
true;
13\
}
14\
void
cancelUpdate()
15\
{
16\
theFile<<theName<<"
: UpdateCanceled"<<std::endl;
17\
}
18\
void
dispose()
19\
{
20\
theFile<<theName<<"
: Disposed"<<std::endl;
21\
}
22\
std::string
toString() const
23\
{
24\
return
std::string("ConsoleProxy(")+theName+std::string(")");
25\
}
26\
};
27\
28\
Proxy
makeFileProxy( const std::string& name, const std::string& fileName
)
29\
{
30\
return
Proxy(new FileProxy(name,fileName));
31\
}
Note that the Origin's implementation has calls to synchronization primitives.
The Proxy, on the other hand, is thread-oblivious.
|