Skip to content

Commit

Permalink
Add ExecutionLimit property to break loops
Browse files Browse the repository at this point in the history
  • Loading branch information
ceciliaavila committed Apr 12, 2024
1 parent 2f45467 commit fbab913
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 2 deletions.
30 changes: 29 additions & 1 deletion libraries/botbuilder-dialogs-adaptive/src/conditions/onError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { Dialog } from 'botbuilder-dialogs';
import { Dialog, TurnPath } from 'botbuilder-dialogs';
import { OnDialogEvent } from './onDialogEvent';
import { ActionContext } from '../actionContext';
import { AdaptiveEvents } from '../adaptiveEvents';
import { ActionChangeList } from '../actionChangeList';
import { ActionChangeType } from '../actionChangeType';
import { NumberExpression } from 'adaptive-expressions';

/**
* Actions triggered when an error event has been emitted.
*/
export class OnError extends OnDialogEvent {
static $kind = 'Microsoft.OnError';

/**
* Gets or sets the number of executions allowed. Used to avoid infinite loops in case of error (OPTIONAL).
*/
executionLimit: NumberExpression = new NumberExpression(0);

/**
* Initializes a new instance of the [OnError](xref:botbuilder-dialogs-adaptive.OnError) class.
*
Expand All @@ -28,6 +34,20 @@ export class OnError extends OnDialogEvent {
super(AdaptiveEvents.error, actions, condition);
}

/**
* Method called to execute the condition's actions.
*
* @param actionContext Context.
* @returns A promise with plan change list.
*/
async execute(actionContext: ActionContext): Promise<ActionChangeList[]> {
const limit = this.currentExecutionLimit();

actionContext.state.setValue(TurnPath.executionLimit, limit);

return await super.execute(actionContext);
}

/**
* Called when a change list is created.
*
Expand All @@ -43,4 +63,12 @@ export class OnError extends OnDialogEvent {
changeList.changeType = ActionChangeType.replaceSequence;
return changeList;
}

currentExecutionLimit = function (): number {
if (this.executionLimit > 0) {
return this.executionLimit;
}
//10 is the default number of executions we'll allow before breaking the loop.
return 10;
};
}
16 changes: 15 additions & 1 deletion libraries/botbuilder-dialogs/src/dialogHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,29 @@ export async function internalRun(
// or we have had an exception AND there was an OnError action which captured the error. We need to continue the
// turn based on the actions the OnError handler introduced.
let endOfTurn = false;
let errorHandlerCalled = 0;
const TURN_STATE = 'turn';

while (!endOfTurn) {
try {
dialogTurnResult = await innerRun(context, dialogId, dialogContext);

// turn successfully completed, break the loop
endOfTurn = true;
} catch (err) {
errorHandlerCalled++;

// fire error event, bubbling from the leaf.
const handled = await dialogContext.emitEvent(DialogEvents.error, err, true, true);
let handled = await dialogContext.emitEvent(DialogEvents.error, err, true, true);

let executionLimit = 0;
executionLimit = context.turnState.get(TURN_STATE).executionLimit;

if (executionLimit > 0 && errorHandlerCalled > executionLimit) {
// if the errorHandler has being called multiple times, there's an error inside the onError.
// We should throw the exception and break the loop.
handled = false;
}

if (!handled) {
// error was NOT handled, throw the exception and end the turn.
Expand Down
3 changes: 3 additions & 0 deletions libraries/botbuilder-dialogs/src/memory/turnPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,7 @@ export class TurnPath {

/// This is a bool which if set means that the turncontext.activity has been consumed by some component in the system.
static readonly activityProcessed = 'turn.activityProcessed';

/// Used to limit the execution of a trigger avoiding infinite loops in case of errors.
static readonly executionLimit = 'turn.executionLimit';
}

0 comments on commit fbab913

Please sign in to comment.