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

Implement Retry Mechanism for HttpExtractor #404

Merged
merged 7 commits into from
Jul 20, 2023
2 changes: 1 addition & 1 deletion libs/execution/src/lib/blocks/execution-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import * as E from 'fp-ts/lib/Either';
import { AstNode, DiagnosticInfo } from 'langium';

interface ExecutionErrorDetails<N extends AstNode = AstNode> {
export interface ExecutionErrorDetails<N extends AstNode = AstNode> {
message: string;
diagnostic: DiagnosticInfo<N>;
}
Expand Down
40 changes: 34 additions & 6 deletions libs/extensions/std/exec/src/http-extractor-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-only

import { strict as assert } from 'assert';
import * as http from 'http';
import * as https from 'https';
import * as path from 'path';
Expand All @@ -12,12 +13,14 @@ import {
BinaryFile,
BlockExecutorClass,
ExecutionContext,
ExecutionErrorDetails,
FileExtension,
MimeType,
None,
implementsStatic,
} from '@jvalue/jayvee-execution';
import { IOType, PrimitiveValuetypes } from '@jvalue/jayvee-language-server';
import { AstNode } from 'langium';

import {
inferFileExtensionFromContentTypeString,
Expand All @@ -43,14 +46,39 @@ export class HttpExtractorExecutor extends AbstractBlockExecutor<
context: ExecutionContext,
): Promise<R.Result<BinaryFile>> {
const url = context.getPropertyValue('url', PrimitiveValuetypes.Text);

const file = await this.fetchRawDataAsFile(url, context);

if (R.isErr(file)) {
return file;
const retries = context.getPropertyValue(
'retries',
PrimitiveValuetypes.Integer,
);
const retryBackoff = context.getPropertyValue(
'retryBackoff',
georg-schwarz marked this conversation as resolved.
Show resolved Hide resolved
PrimitiveValuetypes.Integer,
);

let failure: ExecutionErrorDetails<AstNode> | undefined;
assert(retries >= 0); // loop executes at least once
for (let attempt = 0; attempt <= retries; ++attempt) {
const isLastAttempt = attempt === retries;
const file = await this.fetchRawDataAsFile(url, context);

if (R.isOk(file)) {
return R.ok(file.right);
}

failure = file.left;

if (!isLastAttempt) {
context.logger.logDebug(failure.message);
context.logger.logDebug(
`Waiting ${retryBackoff}ms before trying again...`,
);
await new Promise((p) => setTimeout(p, retryBackoff));
continue;
}
}

return R.ok(file.right);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return R.err(failure!);
}

private fetchRawDataAsFile(
Expand Down
73 changes: 73 additions & 0 deletions libs/extensions/std/lang/src/http-extractor-meta-inf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
BlockMetaInformation,
IOType,
PrimitiveValuetypes,
evaluatePropertyValue,
} from '@jvalue/jayvee-language-server';

export class HttpExtractorMetaInformation extends BlockMetaInformation {
Expand All @@ -28,7 +29,79 @@ export class HttpExtractorMetaInformation extends BlockMetaInformation {
],
},
},
retries: {
type: PrimitiveValuetypes.Integer,
defaultValue: 0,
docs: {
description:
'Configures how many retries should be executed after a failure fetching the data.',
examples: [
{
code: 'retries: 3',
description:
'Executes up to 3 retries if the original retry fails (so in total max. 4 requests).',
},
],
},
validation: (property, validationContext, evaluationContext) => {
const encodingValue = evaluatePropertyValue(
property,
evaluationContext,
PrimitiveValuetypes.Integer,
);
if (encodingValue === undefined) {
return;
}

if (encodingValue < 0) {
validationContext.accept(
'error',
'Only not negative integers allowed',
{
node: property,
property: 'value',
},
);
}
},
},
retryBackoff: {
type: PrimitiveValuetypes.Integer,
defaultValue: 1000,
docs: {
description:
'Configures the wait time in milliseconds before executing a retry.',
examples: [
{
code: 'retryBackoff: 5000',
description: 'Waits 5s (5000 ms) before executing a retry.',
},
],
},
validation: (property, validationContext, evaluationContext) => {
const encodingValue = evaluatePropertyValue(
property,
evaluationContext,
PrimitiveValuetypes.Integer,
);
if (encodingValue === undefined) {
return;
}

if (encodingValue < 0) {
validationContext.accept(
'error',
'Only not negative integers allowed',
{
node: property,
property: 'value',
},
);
}
},
},
},

// Input type:
IOType.NONE,

Expand Down
Loading