Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add counter to onError trigger to break possible loops #4648

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,12 @@ describe('ConditionalsTests', function () {
it('OnRepromptDialog', async function () {
await TestUtils.runTestScript(resourceExplorer, 'ConditionalsTests_OnRepromptDialog');
});

it('OnError loop limit', async function () {
await TestUtils.runTestScript(resourceExplorer, 'ConditionalsTests_OnErrorLoop');
});

it('OnError default loop limit', async function () {
await TestUtils.runTestScript(resourceExplorer, 'ConditionalsTests_OnErrorLoopDefaultLimit');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"$schema": "../../../tests.schema",
"$kind": "Microsoft.Test.Script",
"dialog": {
"$kind": "Microsoft.AdaptiveDialog",
"id": "ErrorLoop",
"autoEndDialog": true,
"defaultResultProperty": "dialog.result",
"triggers": [
{
"$kind": "Microsoft.OnBeginDialog",
"actions": [
{
"$kind": "Microsoft.SendActivity",
"activity": "Throw Exception in BeginDialog."
},
{
"$kind": "Microsoft.ThrowException",
"errorValue": "Exception in BeginDialog."
}
]
},
{
"$kind": "Microsoft.OnError",
"actions": [
{
"$kind": "Microsoft.SendActivity",
"activity": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.ThrowException",
"errorValue": "Exception in OnError."
}
],
"executionLimit": 3
}
]
},
"script": [
{
"$kind": "Microsoft.Test.UserSays",
"text": "hi"
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in BeginDialog."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Exception in OnError."
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{
"$schema": "../../../tests.schema",
"$kind": "Microsoft.Test.Script",
"dialog": {
"$kind": "Microsoft.AdaptiveDialog",
"id": "ErrorLoop",
"autoEndDialog": true,
"defaultResultProperty": "dialog.result",
"triggers": [
{
"$kind": "Microsoft.OnBeginDialog",
"actions": [
{
"$kind": "Microsoft.SendActivity",
"activity": "Throw Exception in BeginDialog."
},
{
"$kind": "Microsoft.ThrowException",
"errorValue": "Exception in BeginDialog."
}
]
},
{
"$kind": "Microsoft.OnError",
"actions": [
{
"$kind": "Microsoft.SendActivity",
"activity": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.ThrowException",
"errorValue": "Exception in OnError."
}
]
}
]
},
"script": [
{
"$kind": "Microsoft.Test.UserSays",
"text": "hi"
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in BeginDialog."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Throw Exception in OnError."
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "Exception in OnError."
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -1794,6 +1794,10 @@ export class OnError extends OnDialogEvent {
// (undocumented)
static $kind: string;
constructor(actions?: Dialog[], condition?: string);
// (undocumented)
currentExecutionLimit: () => number;
execute(actionContext: ActionContext): Promise<ActionChangeList[]>;
executionLimit: NumberExpression;
onCreateChangeList(actionContext: ActionContext, dialogOptions?: any): ActionChangeList;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,16 @@
"$role": [ "implements(Microsoft.ITrigger)", "extends(Microsoft.OnCondition)" ],
"title": "On error",
"description": "Action to perform when an 'Error' dialog event occurs.",
"type": "object"
"type": "object",
"properties": {
"executionLimit": {
"$ref": "schema:#/definitions/numberExpression",
"title": "Execution limit",
"description": "Number of executions allowed for this trigger. Used to break loops in case of errors inside the trigger.",
"examples": [
3,
"=f(x)"
]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"form": {
"order": [
"condition",
"*"
"*",
"executionLimit"
],
"hidden": [
"actions"
Expand Down
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;
};
}
2 changes: 2 additions & 0 deletions libraries/botbuilder-dialogs/etc/botbuilder-dialogs.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,8 @@ export class TurnPath {
// (undocumented)
static readonly dialogEvent = "turn.dialogEvent";
// (undocumented)
static readonly executionLimit = "turn.executionLimit";
// (undocumented)
static readonly interrupted = "turn.interrupted";
// (undocumented)
static readonly lastResult = "turn.lastresult";
Expand Down
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';
}
9 changes: 9 additions & 0 deletions libraries/tests.schema
Original file line number Diff line number Diff line change
Expand Up @@ -6260,6 +6260,15 @@
}
},
"properties": {
"executionLimit": {
"$ref": "#/definitions/numberExpression",
"title": "Execution limit",
"description": "Number of executions allowed for this trigger. Used to break loops in case of errors inside the trigger.",
"examples": [
3,
"=f(x)"
]
},
"condition": {
"$ref": "#/definitions/condition",
"title": "Condition",
Expand Down
3 changes: 2 additions & 1 deletion libraries/tests.uischema
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,8 @@
"label": "Error occurred",
"order": [
"condition",
"*"
"*",
"executionLimit"
],
"subtitle": "Error event"
}
Expand Down
Loading