diff --git a/package-lock.json b/package-lock.json index 0c1b1a61523..f5d4ceb3f1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21484,8 +21484,7 @@ "ethereum-cryptography": "^1.1.2", "level": "^8.0.0", "memory-level": "^1.0.0", - "readable-stream": "^3.6.0", - "semaphore-async-await": "^1.5.1" + "readable-stream": "^3.6.0" }, "devDependencies": { "@types/benchmark": "^1.0.33", @@ -22913,7 +22912,6 @@ "memory-level": "^1.0.0", "micro-bmark": "0.2.0", "readable-stream": "^3.6.0", - "semaphore-async-await": "^1.5.1", "web3": "^1.7.5" } }, diff --git a/packages/trie/package.json b/packages/trie/package.json index 1fe35c9d8c6..47097ea139f 100644 --- a/packages/trie/package.json +++ b/packages/trie/package.json @@ -51,8 +51,7 @@ "ethereum-cryptography": "^1.1.2", "level": "^8.0.0", "memory-level": "^1.0.0", - "readable-stream": "^3.6.0", - "semaphore-async-await": "^1.5.1" + "readable-stream": "^3.6.0" }, "devDependencies": { "0x": "^4.9.1", diff --git a/packages/trie/src/trie/trie.ts b/packages/trie/src/trie/trie.ts index 8041b4c3280..3564e84f245 100644 --- a/packages/trie/src/trie/trie.ts +++ b/packages/trie/src/trie/trie.ts @@ -1,12 +1,12 @@ import { RLP_EMPTY_STRING, isFalsy, isTruthy } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak' -import Semaphore from 'semaphore-async-await' import { LevelDB } from '../db' import { verifyRangeProof } from '../proof/range' import { ROOT_DB_KEY } from '../types' import { bufferToNibbles, doKeysMatch, matchingNibbleLength } from '../util/nibbles' import { TrieReadStream as ReadStream } from '../util/readStream' +import { Semaphore } from '../util/semaphore' import { WalkController } from '../util/walkController' import { BranchNode, ExtensionNode, LeafNode, decodeNode, decodeRawNode, isRawNode } from './node' diff --git a/packages/trie/src/util/semaphore.ts b/packages/trie/src/util/semaphore.ts new file mode 100644 index 00000000000..680ae31d910 --- /dev/null +++ b/packages/trie/src/util/semaphore.ts @@ -0,0 +1,52 @@ +// Based on https://github.com/jsoendermann/semaphore-async-await/blob/master/src/Semaphore.ts +export class Semaphore { + private permits: number + private promiseResolverQueue: Array<(v: boolean) => void> = [] + + /** + * Creates a semaphore. + * @param permits The number of permits, i.e. strands of execution being allowed + * to run in parallel. + * This number can be initialized with a negative integer. + */ + constructor(permits: number) { + this.permits = permits + } + + /** + * Returns a promise used to wait for a permit to become available. This method should be awaited on. + * @returns A promise that gets resolved when execution is allowed to proceed. + */ + public async wait(): Promise<boolean> { + if (this.permits > 0) { + this.permits -= 1 + return Promise.resolve(true) + } + + // If there is no permit available, we return a promise that resolves once the semaphore gets + // signaled enough times that permits is equal to one. + return new Promise<boolean>((resolver) => this.promiseResolverQueue.push(resolver)) + } + + /** + * Increases the number of permits by one. If there are other functions waiting, one of them will + * continue to execute in a future iteration of the event loop. + */ + public signal(): void { + this.permits += 1 + + if (this.permits > 1 && this.promiseResolverQueue.length > 0) { + // eslint-disable-next-line no-console + console.warn('Semaphore.permits should never be > 0 when there is someone waiting.') + } else if (this.permits === 1 && this.promiseResolverQueue.length > 0) { + // If there is someone else waiting, immediately consume the permit that was released + // at the beginning of this function and let the waiting function resume. + this.permits -= 1 + + const nextResolver = this.promiseResolverQueue.shift() + if (nextResolver) { + nextResolver(true) + } + } + } +} diff --git a/packages/trie/test/util/semaphore.spec.ts b/packages/trie/test/util/semaphore.spec.ts new file mode 100644 index 00000000000..818141dbb00 --- /dev/null +++ b/packages/trie/test/util/semaphore.spec.ts @@ -0,0 +1,29 @@ +// Based on https://github.com/jsoendermann/semaphore-async-await/blob/master/__tests__/Semaphore.spec.ts +import * as tape from 'tape' + +import { Semaphore } from '../../src/util/semaphore' + +const wait = (ms: number) => new Promise((r) => setTimeout(r, ms)) + +tape('Semaphore', (t) => { + t.test('should lock', async (st) => { + let global = 0 + const lock = new Semaphore(1) + + const f = async () => { + await lock.wait() + const local = global + await wait(500) + global = local + 1 + lock.signal() + } + + void f() + void f() + await wait(1500) + + st.equal(global, 2) + st.end() + }) + t.end() +})