Skip to content

Commit 3e0c049

Browse files
authored
Merge pull request #399 from proto-kit/refactor/busy-pattern
Refactor/busy pattern
2 parents 41674b6 + 4cd45a6 commit 3e0c049

5 files changed

Lines changed: 61 additions & 84 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { log } from "@proto-kit/common";
2+
/**
3+
* Decorator that ensures a function/method is not currently in use.
4+
* Mostly useful for production of blocks, batches and tasks.
5+
*/
6+
export function ensureNotBusy<T>() {
7+
let inProgress = false;
8+
9+
return function innerFunction(
10+
_target: T,
11+
methodName: string,
12+
descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise<any>>
13+
): void {
14+
const originalMethod = descriptor.value!;
15+
16+
descriptor.value = async function wrapped(this: T, ...args: unknown[]) {
17+
if (inProgress) {
18+
log.trace(`${methodName} is in use at the moment.`);
19+
return undefined;
20+
}
21+
22+
inProgress = true;
23+
try {
24+
return await originalMethod.apply(this, args);
25+
} finally {
26+
inProgress = false;
27+
}
28+
};
29+
};
30+
}

packages/sequencer/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from "./helpers/utils";
2+
export * from "./helpers/BusyGuard";
23
export * from "./mempool/Mempool";
34
export * from "./mempool/PendingTransaction";
45
export * from "./mempool/CompressedSignature";

packages/sequencer/src/protocol/production/BatchProducerModule.ts

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { BlockWithResult } from "../../storage/model/Block";
1717
import type { Database } from "../../storage/Database";
1818
import { AsyncLinkedLeafStore } from "../../state/async/AsyncLinkedLeafStore";
1919
import { CachedLinkedLeafStore } from "../../state/lmt/CachedLinkedLeafStore";
20+
import { ensureNotBusy } from "../../helpers/BusyGuard";
2021

2122
import { BlockProofSerializer } from "./tasks/serializers/BlockProofSerializer";
2223
import { BatchTracingService } from "./tracing/BatchTracingService";
@@ -44,8 +45,6 @@ const errors = {
4445
*/
4546
@sequencerModule()
4647
export class BatchProducerModule extends SequencerModule {
47-
private productionInProgress = false;
48-
4948
public constructor(
5049
@inject("AsyncLinkedLeafStore")
5150
private readonly merkleStore: AsyncLinkedLeafStore,
@@ -62,47 +61,11 @@ export class BatchProducerModule extends SequencerModule {
6261
/**
6362
* Main function to call when wanting to create a new block based on the
6463
* transactions that are present in the mempool. This function should also
65-
* be the one called by BlockTriggerss
64+
* be the one called by BlockTriggers.
6665
*/
66+
@ensureNotBusy()
6767
public async createBatch(
6868
blocks: BlockWithResult[]
69-
): Promise<SettleableBatch | undefined> {
70-
if (!this.productionInProgress) {
71-
try {
72-
this.productionInProgress = true;
73-
74-
const batch = await this.tryProduceBatch(blocks);
75-
76-
this.productionInProgress = false;
77-
78-
return batch;
79-
} catch (error: unknown) {
80-
this.productionInProgress = false;
81-
// TODO Check if that still makes sense
82-
if (error instanceof Error) {
83-
if (
84-
!error.message.includes(
85-
"Can't create a block with zero transactions"
86-
)
87-
) {
88-
log.error(error);
89-
}
90-
91-
throw error;
92-
} else {
93-
log.error(error);
94-
}
95-
}
96-
} else {
97-
log.debug(
98-
"Skipping new block production because production is still in progress"
99-
);
100-
}
101-
return undefined;
102-
}
103-
104-
private async tryProduceBatch(
105-
blocks: BlockWithResult[]
10669
): Promise<SettleableBatch | undefined> {
10770
log.info("Producing batch...");
10871

packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { IncomingMessagesService } from "../../../settlement/messages/IncomingMe
2727
import { Tracer } from "../../../logging/Tracer";
2828
import { trace } from "../../../logging/trace";
2929
import { AsyncLinkedLeafStore } from "../../../state/async/AsyncLinkedLeafStore";
30+
import { ensureNotBusy } from "../../../helpers/BusyGuard";
3031

3132
import { BlockProductionService } from "./BlockProductionService";
3233
import { BlockResultService } from "./BlockResultService";
@@ -38,8 +39,6 @@ export interface BlockConfig {
3839

3940
@sequencerModule()
4041
export class BlockProducerModule extends SequencerModule<BlockConfig> {
41-
private productionInProgress = false;
42-
4342
public constructor(
4443
@inject("Mempool") private readonly mempool: Mempool,
4544
@inject("IncomingMessagesService", { isOptional: true })
@@ -140,37 +139,25 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
140139
return result;
141140
}
142141

142+
@ensureNotBusy()
143143
public async tryProduceBlock(): Promise<Block | undefined> {
144-
if (!this.productionInProgress) {
145-
try {
146-
const block = await this.produceBlock();
147-
148-
if (block === undefined) {
149-
if (!this.allowEmptyBlock()) {
150-
log.info("No transactions in mempool, skipping production");
151-
} else {
152-
log.error("Something wrong happened, skipping block");
153-
}
154-
return undefined;
155-
}
144+
const block = await this.produceBlock();
156145

157-
log.info(
158-
`Produced block #${block.height.toBigInt()} (${block.transactions.length} txs)`
159-
);
160-
this.prettyPrintBlockContents(block);
161-
162-
return block;
163-
} catch (error: unknown) {
164-
if (error instanceof Error) {
165-
throw error;
166-
} else {
167-
log.error(error);
168-
}
169-
} finally {
170-
this.productionInProgress = false;
146+
if (block === undefined) {
147+
if (!this.allowEmptyBlock()) {
148+
log.info("No transactions in mempool, skipping production");
149+
} else {
150+
log.error("Something wrong happened, skipping block");
171151
}
152+
return undefined;
172153
}
173-
return undefined;
154+
155+
log.info(
156+
`Produced block #${block.height.toBigInt()} (${block.transactions.length} txs)`
157+
);
158+
this.prettyPrintBlockContents(block);
159+
160+
return block;
174161
}
175162

176163
// TODO Move to different service, to remove dependency on mempool and messagequeue
@@ -220,8 +207,6 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
220207

221208
@trace("block")
222209
private async produceBlock(): Promise<Block | undefined> {
223-
this.productionInProgress = true;
224-
225210
const { txs, metadata } = await this.collectProductionData();
226211

227212
// Skip production if no transactions are available for now
@@ -263,8 +248,6 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
263248
);
264249
}
265250

266-
this.productionInProgress = false;
267-
268251
return blockResult?.block;
269252
}
270253

packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
BridgingModule,
1313
SettlementTokenConfig,
1414
} from "../../../settlement/BridgingModule";
15+
import { ensureNotBusy } from "../../../helpers/BusyGuard";
1516

1617
import { BlockEvents, BlockTriggerBase } from "./BlockTrigger";
1718

@@ -45,9 +46,6 @@ export class TimedBlockTrigger
4546

4647
private interval?: any;
4748

48-
// TODO Move that logic to somewhere proper
49-
private settlementInProgress = false;
50-
5149
public constructor(
5250
@inject("BatchProducerModule", { isOptional: true })
5351
batchProducerModule: BatchProducerModule | undefined,
@@ -123,15 +121,9 @@ export class TimedBlockTrigger
123121
// otherwise treat as unproven-only
124122
if (
125123
settlementInterval !== undefined &&
126-
totalTime % settlementInterval === 0 &&
127-
!this.settlementInProgress
124+
totalTime % settlementInterval === 0
128125
) {
129-
this.settlementInProgress = true;
130-
const batch = await this.produceBatch();
131-
if (batch !== undefined) {
132-
await this.settle(batch, this.config.settlementTokenConfig);
133-
}
134-
this.settlementInProgress = false;
126+
await this.tryProduceSettlement();
135127
}
136128
} catch (error) {
137129
log.error(error);
@@ -151,6 +143,14 @@ export class TimedBlockTrigger
151143
}
152144
}
153145

146+
@ensureNotBusy()
147+
private async tryProduceSettlement(): Promise<void> {
148+
const batch = await this.produceBatch();
149+
if (batch !== undefined) {
150+
await this.settle(batch, this.config.settlementTokenConfig);
151+
}
152+
}
153+
154154
public async close(): Promise<void> {
155155
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
156156
clearInterval(this.interval);

0 commit comments

Comments
 (0)