Skip to content

Commit

Permalink
Wait on blockchain processing completion when polling (#156)
Browse files Browse the repository at this point in the history
* wait on processing completion

* increase polling frequency

* log execution time less frequently

* refactor to iterable polling, fix concurrency

* start/stop scheduling in project lifecycle hooks
  • Loading branch information
bartolomej authored Dec 29, 2022
1 parent be49856 commit 5f5d9c0
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 5 deletions.
64 changes: 64 additions & 0 deletions backend/src/core/async-interval-scheduler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Logger } from "@nestjs/common";

const logger = new Logger("Utils");

export type ExecutableCallback = () => Promise<void>;

export type AsyncIntervalOptions = {
functionToExecute: ExecutableCallback;
name: string;
intervalInMs: number;
};

/**
* Runs the provided function in interval.
* Waits for promise completion until rerunning interval.
*/
export class AsyncIntervalScheduler {
private isRunning: boolean;
private runningTimeoutId: NodeJS.Timeout;
private readonly options: AsyncIntervalOptions;

constructor(options: AsyncIntervalOptions) {
this.options = options;
this.isRunning = false;
}

async start(): Promise<void> {
if (this.isRunning) {
return;
}
this.isRunning = true;
await this.pollIfRunning();
}

stop(): void {
clearTimeout(this.runningTimeoutId);
this.isRunning = false;
}

private async pollIfRunning() {
const { intervalInMs } = this.options;
while (this.isRunning) {
await this.executeCallback();
await new Promise((resolve) => {
this.runningTimeoutId = setTimeout(resolve, intervalInMs);
});
}
}

private async executeCallback() {
const { functionToExecute, name, intervalInMs } = this.options;
try {
const startTime = new Date();
await functionToExecute();
const endTime = new Date();
const runTimeInMs = endTime.getTime() - startTime.getTime();
if (runTimeInMs > intervalInMs) {
logger.debug(`${name} took ${runTimeInMs}ms`);
}
} catch (e) {
logger.error(`${name} failed`, e);
}
}
}
18 changes: 13 additions & 5 deletions backend/src/flow/services/aggregator.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Injectable, Logger } from "@nestjs/common";
import { Interval } from "@nestjs/schedule";
import {
FlowBlock,
FlowCollection,
Expand Down Expand Up @@ -43,6 +42,7 @@ import {
} from "../../processes/managed-process.entity";
import { CommonService } from "../../core/services/common.service";
import { FlowEmulatorService } from "./emulator.service";
import { AsyncIntervalScheduler } from "../../core/async-interval-scheduler";

type BlockData = {
block: FlowBlock;
Expand Down Expand Up @@ -70,6 +70,8 @@ export class FlowAggregatorService implements ProjectContextLifecycle {
private projectContext: ProjectEntity | undefined;
private emulatorProcess: ManagedProcessEntity | undefined;
private readonly logger = new Logger(FlowAggregatorService.name);
private readonly processingIntervalMs = 500;
private processingScheduler: AsyncIntervalScheduler;

constructor(
private blockService: BlocksService,
Expand All @@ -85,9 +87,16 @@ export class FlowAggregatorService implements ProjectContextLifecycle {
private configService: FlowConfigService,
private processManagerService: ProcessManagerService,
private commonService: CommonService
) {}
) {
this.processingScheduler = new AsyncIntervalScheduler({
name: "Blockchain processing",
intervalInMs: this.processingIntervalMs,
functionToExecute: this.processBlockchainData.bind(this),
});
}

onEnterProjectContext(project: ProjectEntity): void {
this.processingScheduler?.start();
this.projectContext = project;
this.emulatorProcess = this.processManagerService.get(
FlowEmulatorService.processId
Expand All @@ -103,6 +112,7 @@ export class FlowAggregatorService implements ProjectContextLifecycle {
}

onExitProjectContext(): void {
this.processingScheduler?.stop();
this.projectContext = undefined;
this.processManagerService.removeListener(
ProcessManagerEvent.PROCESS_ADDED,
Expand Down Expand Up @@ -151,9 +161,7 @@ export class FlowAggregatorService implements ProjectContextLifecycle {
return latestUnprocessedBlockHeight - (nextBlockHeightToProcess - 1);
}

// TODO(milestone-x): Next interval shouldn't start before this function resolves
@Interval(1000)
async fetchDataFromDataSource(): Promise<void> {
async processBlockchainData(): Promise<void> {
if (!this.projectContext) {
return;
}
Expand Down

0 comments on commit 5f5d9c0

Please sign in to comment.