Skip to content

Commit

Permalink
Merge pull request #993 from golemfactory/feature/JST-991/destroy-exe…
Browse files Browse the repository at this point in the history
…-unit

Creation of exe-unit during resource-rental startup
  • Loading branch information
grisha87 authored Jun 27, 2024
2 parents 3251a66 + 923ea83 commit 4a59590
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 16 deletions.
68 changes: 68 additions & 0 deletions src/resource-rental/resource-rental.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { imock, instance, mock, reset, when, verify, _ } from "@johanblumenberg/ts-mockito";
import { Agreement, MarketModule } from "../market";
import { StorageProvider } from "../shared/storage";
import { AgreementPaymentProcess } from "../payment/agreement_payment_process";
import { ResourceRental, ResourceRentalOptions } from ".";
import { ActivityModule, ExeUnit } from "../activity";
import { Logger } from "../shared/utils";

const mockAgreement = mock(Agreement);
const mockStorageProvider = imock<StorageProvider>();
const mockPaymentProcess = mock(AgreementPaymentProcess);
const mockMarketModule = imock<MarketModule>();
const mockActivityModule = imock<ActivityModule>();
const mockLogger = imock<Logger>();
const mockResourceRentalOptions = imock<ResourceRentalOptions>();

let resourceRental: ResourceRental;

beforeEach(() => {
reset(mockAgreement);
reset(mockStorageProvider);
reset(mockPaymentProcess);
reset(mockMarketModule);
reset(mockActivityModule);
reset(mockLogger);
reset(mockResourceRentalOptions);
when(mockActivityModule.createExeUnit(_, _)).thenResolve(instance(mock(ExeUnit)));
resourceRental = new ResourceRental(
instance(mockAgreement),
instance(mockStorageProvider),
instance(mockPaymentProcess),
instance(mockMarketModule),
instance(mockActivityModule),
instance(mockLogger),
instance(mockResourceRentalOptions),
);
});

describe("ResourceRental", () => {
describe("ExeUnit", () => {
it("should create an exe unit on startup and use it later", async () => {
expect(resourceRental["currentExeUnit"]).toBeDefined();
verify(mockActivityModule.createExeUnit(_, _)).once();
await resourceRental.getExeUnit();
verify(mockActivityModule.createExeUnit(_, _)).once();
});

it("should reuse the same promise if called multiple times", async () => {
expect(resourceRental["currentExeUnit"]).toBeDefined();
const promise1 = resourceRental.getExeUnit();
const promise2 = resourceRental.getExeUnit();
const promise3 = resourceRental.getExeUnit();
await Promise.all([promise1, promise2, promise3]);
verify(mockActivityModule.createExeUnit(_, _)).once();
});

it("should reuse the same promise if called multiple time after destroy exe-unit created on strtup", async () => {
expect(resourceRental["currentExeUnit"]).toBeDefined();
await resourceRental.destroyExeUnit();
const promise1 = resourceRental.getExeUnit();
const promise2 = resourceRental.getExeUnit();
const promise3 = resourceRental.getExeUnit();
expect(resourceRental["exeUnitPromise"]).toBeDefined();
await Promise.all([promise1, promise2, promise3]);
verify(mockActivityModule.createExeUnit(_, _)).twice();
});
});
});
51 changes: 35 additions & 16 deletions src/resource-rental/resource-rental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class ResourceRental {
private currentExeUnit: ExeUnit | null = null;
private abortController = new AbortController();
private finalizePromise?: Promise<void>;
private exeUnitPromise?: Promise<ExeUnit>;

public constructor(
public readonly agreement: Agreement,
Expand All @@ -45,7 +46,7 @@ export class ResourceRental {
private readonly resourceRentalOptions?: ResourceRentalOptions,
) {
this.networkNode = this.resourceRentalOptions?.networkNode;

this.createExeUnit(this.abortController.signal);
// TODO: Listen to agreement events to know when it goes down due to provider closing it!
}

Expand Down Expand Up @@ -114,10 +115,9 @@ export class ResourceRental {
if (this.finalizePromise || this.abortController.signal.aborted) {
throw new GolemUserError("The resource rental is not active. It may have been aborted or finalized");
}
if (this.currentExeUnit) {
if (this.currentExeUnit !== null) {
return this.currentExeUnit;
}

const abortController = new AbortController();
this.abortController.signal.addEventListener("abort", () =>
abortController.abort(this.abortController.signal.reason),
Expand All @@ -129,29 +129,48 @@ export class ResourceRental {
abortController.abort(signalOrTimeout.reason);
}
}

const activity = await this.activityModule.createActivity(this.agreement);
this.currentExeUnit = await this.activityModule.createExeUnit(activity, {
storageProvider: this.storageProvider,
networkNode: this.resourceRentalOptions?.networkNode,
executionOptions: this.resourceRentalOptions?.activity,
signalOrTimeout: abortController.signal,
...this.resourceRentalOptions?.exeUnit,
});

return this.currentExeUnit;
return this.createExeUnit(abortController.signal);
}

/**
* Destroy previously created exe-unit.
* Please note that if ResourceRental is left without ExeUnit for some time (default 90s)
* the provider will terminate the Agreement and ResourceRental will be unuseble
*/
async destroyExeUnit() {
if (this.currentExeUnit) {
if (this.currentExeUnit !== null) {
await this.activityModule.destroyActivity(this.currentExeUnit.activity);
this.currentExeUnit = null;
} else {
throw new Error(`There is no activity to destroy.`);
throw new GolemUserError(`There is no exe-unit to destroy.`);
}
}

async fetchAgreementState() {
return this.marketModule.fetchAgreement(this.agreement.id).then((agreement) => agreement.getState());
}

private async createExeUnit(abortSignal: AbortSignal) {
if (!this.exeUnitPromise) {
this.exeUnitPromise = (async () => {
const activity = await this.activityModule.createActivity(this.agreement);
this.currentExeUnit = await this.activityModule.createExeUnit(activity, {
storageProvider: this.storageProvider,
networkNode: this.resourceRentalOptions?.networkNode,
executionOptions: this.resourceRentalOptions?.activity,
signalOrTimeout: abortSignal,
...this.resourceRentalOptions?.exeUnit,
});
return this.currentExeUnit;
})()
.catch((error) => {
this.logger.error(`Failed to create exe-unit. ${error}`, { agreementId: this.agreement.id });
throw error;
})
.finally(() => {
this.exeUnitPromise = undefined;
});
}
return this.exeUnitPromise;
}
}

0 comments on commit 4a59590

Please sign in to comment.