Skip to content

Commit e8e2b3a

Browse files
committed
Refactor Mutex
1 parent da17f4e commit e8e2b3a

File tree

1 file changed

+55
-59
lines changed

1 file changed

+55
-59
lines changed

src/lib/sync/mutex.ts

Lines changed: 55 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,32 @@ export const INTERNAL_MUTEX_CONTROLLER = Symbol(
1212
"Thread.InternalMutexController",
1313
);
1414

15-
// Defines the capabilities hidden from the user but available to the Guard and Condvar
15+
const INDEX = 0;
16+
const LOCKED = 1;
17+
const UNLOCKED = 0;
18+
1619
export interface MutexController {
1720
unlock(): void;
1821
blockingLock(): void;
1922
lock(): Promise<void>;
2023
}
21-
const LOCKED = 1;
22-
const UNLOCKED = 0;
2324

2425
export class MutexGuard<T extends SharedMemoryView | void>
2526
implements Disposable {
2627
#data: T;
28+
#mutex: MutexController;
2729
#released = false;
28-
[INTERNAL_MUTEX_CONTROLLER]!: MutexController;
2930

30-
constructor(data: T, controller: MutexController) {
31+
constructor(data: T, mutex: MutexController) {
3132
this.#data = data;
32-
this.#released = false;
33-
34-
Object.defineProperty(this, INTERNAL_MUTEX_CONTROLLER, {
35-
value: controller,
36-
writable: false,
37-
enumerable: false,
38-
configurable: false,
39-
});
33+
this.#mutex = mutex;
34+
}
35+
36+
/**
37+
* Internal accessor for Condvar support
38+
*/
39+
get [INTERNAL_MUTEX_CONTROLLER](): MutexController {
40+
return this.#mutex;
4041
}
4142

4243
get value(): T {
@@ -46,14 +47,13 @@ export class MutexGuard<T extends SharedMemoryView | void>
4647

4748
[Symbol.dispose]() {
4849
if (!this.#released) {
49-
const controller = this[INTERNAL_MUTEX_CONTROLLER];
50-
controller.unlock();
5150
this.#released = true;
51+
this.#mutex.unlock();
5252
}
5353
}
5454

5555
dispose() {
56-
return this[Symbol.dispose]();
56+
this[Symbol.dispose]();
5757
}
5858
}
5959

@@ -63,72 +63,73 @@ export class Mutex<T extends SharedMemoryView | void = void>
6363
register(0, this);
6464
}
6565

66-
// Strict private fields
67-
#lockState: Int32Array<SharedArrayBuffer>;
6866
#data: T;
67+
#lockState: Int32Array<SharedArrayBuffer>;
68+
#controller: MutexController;
6969

7070
constructor(data?: T, _existingLockBuffer?: SharedArrayBuffer) {
7171
super();
7272
this.#data = data as T;
73-
74-
if (_existingLockBuffer) {
75-
this.#lockState = new Int32Array(_existingLockBuffer);
76-
} else {
77-
this.#lockState = new Int32Array(new SharedArrayBuffer(4));
78-
}
73+
this.#lockState = _existingLockBuffer
74+
? new Int32Array(_existingLockBuffer)
75+
: new Int32Array(new SharedArrayBuffer(4));
76+
this.#controller = {
77+
unlock: () => this.#unlock(),
78+
blockingLock: () => this.#performBlockingLock(),
79+
lock: () => this.#performAsyncLock(),
80+
};
7981
}
8082

8183
#tryLock(): boolean {
8284
return (
83-
Atomics.compareExchange(this.#lockState, 0, UNLOCKED, LOCKED) === UNLOCKED
85+
Atomics.compareExchange(this.#lockState, INDEX, UNLOCKED, LOCKED) ===
86+
UNLOCKED
8487
);
8588
}
8689

87-
#blockingLock(): void {
90+
#unlock(): void {
91+
if (
92+
Atomics.compareExchange(this.#lockState, INDEX, LOCKED, UNLOCKED) !==
93+
LOCKED
94+
) {
95+
throw new Error("Mutex was not locked or locked by another thread");
96+
}
97+
Atomics.notify(this.#lockState, INDEX, 1);
98+
}
99+
100+
/**
101+
* Shared logic for blocking lock.
102+
* Used by both public blockingLock() and the Controller (for Condvar)
103+
*/
104+
#performBlockingLock(): void {
88105
while (true) {
89106
if (this.#tryLock()) return;
90-
Atomics.wait(this.#lockState, 0, LOCKED);
107+
Atomics.wait(this.#lockState, INDEX, LOCKED);
91108
}
92109
}
93110

94-
async #lock(): Promise<void> {
111+
/**
112+
* Shared logic for async lock.
113+
* Used by both public lock() and the Controller (for Condvar)
114+
*/
115+
async #performAsyncLock(): Promise<void> {
95116
while (true) {
96117
if (this.#tryLock()) return;
97-
const result = Atomics.waitAsync(this.#lockState, 0, LOCKED);
118+
const result = Atomics.waitAsync(this.#lockState, INDEX, LOCKED);
98119
if (result.async) {
99120
await result.value;
100121
}
101122
}
102123
}
103124

104-
#unlock(): void {
105-
if (
106-
Atomics.compareExchange(this.#lockState, 0, LOCKED, UNLOCKED) !== LOCKED
107-
) {
108-
throw new Error("Mutex was not locked or locked by another thread");
109-
}
110-
Atomics.notify(this.#lockState, 0, 1);
111-
}
112-
113-
/**
114-
* Creates the closure that allows the Guard to control this Mutex.
115-
*/
116-
#createController(): MutexController {
117-
return {
118-
unlock: () => this.#unlock(),
119-
blockingLock: () => this.#blockingLock(),
120-
lock: () => this.#lock(),
121-
};
122-
}
123-
124125
public blockingLock(): MutexGuard<T> {
125-
this.#blockingLock();
126-
return new MutexGuard(this.#data, this.#createController());
126+
this.#performBlockingLock();
127+
return new MutexGuard(this.#data, this.#controller);
127128
}
128129

129130
public async lock(): Promise<MutexGuard<T>> {
130-
await this.#lock();
131-
return new MutexGuard(this.#data, this.#createController());
131+
await this.#performAsyncLock();
132+
return new MutexGuard(this.#data, this.#controller);
132133
}
133134

134135
[toSerialized]() {
@@ -153,12 +154,7 @@ export class Mutex<T extends SharedMemoryView | void = void>
153154
static override [toDeserialized](
154155
obj: ReturnType<Mutex<any>[typeof toSerialized]>["value"],
155156
) {
156-
let data;
157-
158-
if (obj.data !== undefined) {
159-
data = deserialize(obj.data);
160-
}
161-
157+
const data = obj.data !== undefined ? deserialize(obj.data) : undefined;
162158
return new Mutex(data, obj.lockBuffer);
163159
}
164160
}

0 commit comments

Comments
 (0)