DSM exceptions. SEMS-59.

actions and conditions should be able to throw exceptions. if an exceptions happens, execution of the current actions is interrupted, and special exception transitions are executed. 



git-svn-id: http://svn.berlios.de/svnroot/repos/sems/trunk@1554 8eb893ce-cfd4-0310-b710-fb5ebe64c474
sayer/1.4-spce2.6
Stefan Sayer 16 years ago
parent 0c66ebf7c5
commit 5352e9abb7

@ -340,6 +340,7 @@ bool DSMChartReader::decode(DSMStateDiagram* e, const string& chart,
}
tr->precond = cl->conditions;
tr->is_exception = cl->is_exception;
delete cl;
// start AL_trans action list
@ -354,6 +355,11 @@ bool DSMChartReader::decode(DSMStateDiagram* e, const string& chart,
continue;
}
if (token == "exception") {
cl->is_exception = true;
continue;
}
// DBG("new condition: '%s'\n", token.c_str());
DSMCondition* c = conditionFromToken(token, cl->invert_next);
cl->invert_next = false;

@ -69,9 +69,10 @@ class ActionList : public DSMElement {
};
struct DSMConditionList : public DSMElement {
DSMConditionList() : invert_next(false) { }
DSMConditionList() : invert_next(false), is_exception(false) { }
vector<DSMCondition*> conditions;
bool invert_next;
bool is_exception;
};
class DSMChartReader {

@ -44,6 +44,8 @@ DSMAction* DSMCoreModule::getAction(const string& from_str) {
DEF_CMD("callFSM", SCCallFSMAction);
DEF_CMD("returnFSM", SCReturnFSMAction);
DEF_CMD("throw", SCThrowAction);
DEF_CMD("stop", SCStopAction);
DEF_CMD("playPrompt", SCPlayPromptAction);
@ -257,6 +259,29 @@ EXEC_ACTION_START(SCDisableDTMFDetection) {
sess->setDtmfDetectionEnabled(false);
} EXEC_ACTION_END;
CONST_ACTION_2P(SCThrowAction, ',', true);
EXEC_ACTION_START(SCThrowAction) {
map<string, string> e_args;
e_args["type"] = resolveVars(par1, sess, sc_sess, event_params);
DBG("throwing DSMException type '%s'\n", e_args["type"].c_str());
string e_params = resolveVars(par2, sess, sc_sess, event_params);
// inefficient param-split
vector<string> params = explode(e_params, ";");
for (vector<string>::iterator it=
params.begin(); it != params.end(); it++) {
vector<string> n = explode(*it, "=");
if (n.size()==2) {
e_args[n[0]]=n[1];
}
}
throw DSMException(e_args);
} EXEC_ACTION_END;
EXEC_ACTION_START(SCStopAction) {
if (resolveVars(arg, sess, sc_sess, event_params) == "true") {
DBG("sending bye\n");

@ -70,6 +70,7 @@ DEF_SCModSEStrArgAction(SCJumpFSMAction);
DEF_SCModSEStrArgAction(SCCallFSMAction);
DEF_SCModSEStrArgAction(SCReturnFSMAction);
DEF_ACTION_2P(SCThrowAction);
DEF_ACTION_2P(SCSetAction);
DEF_ACTION_2P(SCAppendAction);

@ -156,9 +156,9 @@ bool DSMStateEngine::onInvite(const AmSipRequest& req, DSMSession* sess) {
}
bool DSMStateEngine::runactions(vector<DSMAction*>::iterator from,
vector<DSMAction*>::iterator to,
AmSession* sess, DSMCondition::EventType event,
map<string,string>* event_params, bool& is_consumed) {
vector<DSMAction*>::iterator to,
AmSession* sess, DSMCondition::EventType event,
map<string,string>* event_params, bool& is_consumed) {
// DBG("running %zd actions\n", to - from);
for (vector<DSMAction*>::iterator it=from; it != to; it++) {
DBG("executing '%s'\n", (*it)->name.c_str());
@ -205,11 +205,19 @@ void DSMStateEngine::addModules(vector<DSMModule*> modules) {
bool DSMStateEngine::init(AmSession* sess, const string& startDiagram,
DSMCondition::EventType init_event) {
if (!jumpDiag(startDiagram, sess, init_event, NULL)) {
ERROR("initializing with start diag '%s'\n",
startDiagram.c_str());
return false;
}
try {
if (!jumpDiag(startDiagram, sess, init_event, NULL)) {
ERROR("initializing with start diag '%s'\n",
startDiagram.c_str());
return false;
}
} catch (DSMException& e) {
DBG("Exception type '%s' occured while initializing! Run init event as exception...\n",
e.params["type"].c_str());
runEvent(sess, init_event, &e.params, true);
return true;
}
DBG("run init event...\n");
runEvent(sess, init_event, NULL);
@ -245,98 +253,119 @@ bool DSMCondition::match(AmSession* sess,
}
void DSMStateEngine::runEvent(AmSession* sess,
DSMCondition::EventType event,
map<string,string>* event_params) {
DSMCondition::EventType event,
map<string,string>* event_params,
bool run_exception) {
if (!current || !current_diag)
return;
DSMCondition::EventType active_event = event;
map<string,string>* active_params = event_params;
map<string,string> exception_params;
bool is_exception = run_exception;
bool is_consumed = true;
do {
is_consumed = true;
try {
is_consumed = true;
for (vector<DSMTransition>::iterator tr = current->transitions.begin();
tr != current->transitions.end();tr++) {
DBG("checking transition '%s'\n", tr->name.c_str());
vector<DSMCondition*>::iterator con=tr->precond.begin();
while (con!=tr->precond.end()) {
if (!(*con)->_match(sess, event, event_params))
break;
con++;
}
if (con == tr->precond.end()) {
DBG("transition '%s' matched.\n", tr->name.c_str());
for (vector<DSMTransition>::iterator tr = current->transitions.begin();
tr != current->transitions.end();tr++) {
if (tr->is_exception != is_exception)
continue;
// matched all preconditions
// find target state
State* target_st = current_diag->getState(tr->to_state);
if (!target_st) {
ERROR("script writer error: transition '%s' from "
"state '%s' to unknown state '%s'\n",
tr->name.c_str(),
current->name.c_str(),
tr->to_state.c_str());
}
DBG("checking transition '%s'\n", tr->name.c_str());
// run post-actions
if (current->post_actions.size()) {
DBG("running %zd post_actions of state '%s'\n",
current->post_actions.size(), current->name.c_str());
if (runactions(current->post_actions.begin(),
current->post_actions.end(),
sess, event, event_params, is_consumed)) {
vector<DSMCondition*>::iterator con=tr->precond.begin();
while (con!=tr->precond.end()) {
if (!(*con)->_match(sess, active_event, active_params))
break;
}
con++;
}
// run transition actions
if (tr->actions.size()) {
DBG("running %zd actions of transition '%s'\n",
tr->actions.size(), tr->name.c_str());
if (runactions(tr->actions.begin(),
tr->actions.end(),
sess, event, event_params, is_consumed)) {
if (con == tr->precond.end()) {
DBG("transition '%s' matched.\n", tr->name.c_str());
// matched all preconditions
// find target state
State* target_st = current_diag->getState(tr->to_state);
if (!target_st) {
ERROR("script writer error: transition '%s' from "
"state '%s' to unknown state '%s'\n",
tr->name.c_str(),
current->name.c_str(),
tr->to_state.c_str());
}
// run post-actions
if (current->post_actions.size()) {
DBG("running %zd post_actions of state '%s'\n",
current->post_actions.size(), current->name.c_str());
if (runactions(current->post_actions.begin(),
current->post_actions.end(),
sess, active_event, active_params, is_consumed)) {
break;
}
}
// run transition actions
if (tr->actions.size()) {
DBG("running %zd actions of transition '%s'\n",
tr->actions.size(), tr->name.c_str());
if (runactions(tr->actions.begin(),
tr->actions.end(),
sess, active_event, active_params, is_consumed)) {
break;
}
}
// go into new state
if (!target_st) {
break;
}
}
// go into new state
if (!target_st) {
break;
}
DBG("changing to new state '%s'\n", target_st->name.c_str());
MONITORING_LOG(sess->getLocalTag().c_str(), "dsm_state", target_st->name.c_str());
DBG("changing to new state '%s'\n", target_st->name.c_str());
#ifdef USE_MONITORING
if (DSMFactory::MonitoringFullTransitions) {
MONITORING_LOG_ADD(sess->getLocalTag().c_str(),
"dsm_stategraph",
("> "+ tr->name + " >").c_str());
}
if (DSMFactory::MonitoringFullCallgraph) {
MONITORING_LOG_ADD(sess->getLocalTag().c_str(),
"dsm_stategraph",
(current_diag->getName() +"/"+ target_st->name).c_str());
}
MONITORING_LOG(sess->getLocalTag().c_str(), "dsm_state", target_st->name.c_str());
if (DSMFactory::MonitoringFullTransitions) {
MONITORING_LOG_ADD(sess->getLocalTag().c_str(),
"dsm_stategraph",
("> "+ tr->name + " >").c_str());
}
if (DSMFactory::MonitoringFullCallgraph) {
MONITORING_LOG_ADD(sess->getLocalTag().c_str(),
"dsm_stategraph",
(current_diag->getName() +"/"+ target_st->name).c_str());
}
#endif
current = target_st;
// execute pre-actions
if (current->pre_actions.size()) {
DBG("running %zd pre_actions of state '%s'\n",
current->pre_actions.size(), current->name.c_str());
if (runactions(current->pre_actions.begin(),
current->pre_actions.end(),
sess, event, event_params, is_consumed)) {
break;
current = target_st;
// execute pre-actions
if (current->pre_actions.size()) {
DBG("running %zd pre_actions of state '%s'\n",
current->pre_actions.size(), current->name.c_str());
if (runactions(current->pre_actions.begin(),
current->pre_actions.end(),
sess, active_event, active_params, is_consumed)) {
break;
}
}
break;
}
break;
}
} catch (DSMException& e) {
DBG("DSMException occured, type = %s\n", e.params["type"].c_str());
is_consumed = false;
is_exception = true; // continue to process as exception event
exception_params = e.params;
active_params = &exception_params;
active_event = DSMCondition::DSMException;
}
} while (!is_consumed);
}

@ -73,7 +73,9 @@ class DSMCondition
PlaylistSeparator,
B2BOtherReply,
B2BOtherBye
B2BOtherBye,
DSMException
};
bool invert;
@ -137,6 +139,8 @@ class DSMTransition
vector<DSMAction*> actions;
string from_state;
string to_state;
bool is_exception;
};
class DSMModule;
@ -158,6 +162,19 @@ class DSMStateDiagram {
const string& getName() { return name; }
};
class DSMException {
public:
DSMException(const string& e_type)
{ params["type"] = e_type; }
DSMException(map<string, string>& params)
: params(params) { }
~DSMException() { }
map<string, string> params;
};
class DSMStateEngine {
State* current;
DSMStateDiagram* current_diag;
@ -189,7 +206,8 @@ class DSMStateEngine {
void runEvent(AmSession* sess,
DSMCondition::EventType event,
map<string,string>* event_params);
map<string,string>* event_params,
bool run_exception = false);
/** @return whether call should be accepted */
bool onInvite(const AmSipRequest& req, DSMSession* sess);

@ -26,8 +26,9 @@ A session (call) in the DonkeySM has a set of named (string) variables.
The variables may be used as parameter to most conditions and
actions, by prepending the variable name with a dollar sign. The
parameters of an event (e.g. the key on key press) may be accessed
by prepending the name with a hash. There are also 'selects' with
which a set of dialog properties can be accessed (e.g. @local_tag).
by prepending the name with a hash (e.g. #key). There are also
'selects' with which a set of dialog properties can be accessed
(e.g. @local_tag).
The DonkeySM can be extended by modules, which add new conditions
and actions to the language. This way, menuing system etc can be
@ -37,7 +38,14 @@ initialization function that is called when the module is loaded.
DonkeySM also has built in actions to call
DI methods from other modules.
It can cache a set of prompts, configured at start, in memory
Actions (and conditions) can throw exceptions. Once an exception occurs,
execution of the current actions is interrupted. Exceptions are handled
this way that special "exception" transitions are executed. Exception
transitions are marked with "exception" in the conditions list. Once the
FSM is in exception handling, only exception transitions are followed.
DSMs may throw exceptions with the throw(<type>) action.
DSM can cache a set of prompts, configured at start, in memory
using PromptCollection.
A patch for fmsc 1.0.4 from the graphical FSM editor fsme
@ -47,6 +55,8 @@ click-n-drag fashion and compiled to SEMS DSM diagrams.
DI commands
===========
DI commands allow interaction with DSM calls, and DSM script reload:
postDSMEvent(string call_id, [ [[param0,val0],[param1,val1],...] ]
post a DSM event into a call. can be used to interact with running
calls in DSM. See DSM + monitoring + DI example in
@ -95,7 +105,7 @@ The DSMStateEngine has a set of DSM diagrams which are loaded by
the DSMStateDiagramCollection from text file and interpreted by
the DSMChartReader, a simple stack based tokenizing compiler.
DSMDialogs, which implement the DSMSession interface (additionally
DSMCall, which implement the DSMSession interface (additionally
to being an AmSession), run DSMStateEngine::runEvent for every event
that occurs that should be processed by the engine (e.g. Audio event,
onBye, ...).
@ -107,7 +117,7 @@ executed. The DSMCondition::match and DSMAction::execute functions
get the event parameters and the session as parameters, so that they
can operate on variables, implement selects etc.
The DSMDialog implementation is very simple, it uses a playlist and
The DSMCall implementation is very simple, it uses a playlist and
has PromptCollection to simply play prompts etc.
DSMCoreModule is a 'built in' module that implements the basic
@ -123,9 +133,6 @@ interface, other session types can easily be implemented, and their
functionality be exposed to the DSM interpreter by custom actions and
conditions that interact with that specific session type.
Another direction is python/lua/... interpreter module, so that
conditions and actions can be expressed in a more powerful language.
A set of modules exposing more of the core functionality.
As the call state representation is nicely encapsulated here, this can
@ -171,7 +178,9 @@ each condition of each transition is way too heavy!
SEMS has a dynamically typed type (AmArg), why not use that one for
variables? That would also make DI simpler.
a patch is very welcome, best to semsdev list: semsdev@iptel.org or
the tracker: http://tracker.iptel.org
the tracker: http://tracker.iptel.org.
There is also the avar array ("AmArg-Var"), which can hold AmArg
variables.
some performance numbers?
unfortunately not yet for running DSMs. DSM processing is actually fast:

@ -0,0 +1,10 @@
initial state lobby
enter {
playFile(wav/default_en.wav)
throw(bla,blub=blu;i=somevalue);
};
transition "thrown error" lobby - exception; test(#blub=="blu"); /
log(1, #i); stop(true) -> end;
transition "file error" lobby - exception; test(#type="file") / stop(true) -> end;
Loading…
Cancel
Save