-
Notifications
You must be signed in to change notification settings - Fork 12
/
lock.js
196 lines (178 loc) · 6.57 KB
/
lock.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Simple, standalone lock and condition variable abstractions.
// 2015-01-13 / [email protected]
//////////////////////////////////////////////////////////////////////
//
// Locks.
//
// Locks are JS objects that use some shared memory for private data.
// The number of shared int32 locations needed is given by
// Lock.NUMINTS. The shared memory for a lock should be initialized
// once by calling Lock.initialize() on the memory, before
// constructing the first Lock object in any agent.
//
//
// Implementation note:
// Lock code taken from http://www.akkadia.org/drepper/futex.pdf.
// Lock states:
// 0: unlocked
// 1: locked with no waiters
// 2: locked with possible waiters
//
// This could be built on Synchronic instead, but this code predates
// the Synchronic facility.
"use strict";
// Create a lock object.
//
// 'iab' must be a Int32Array mapping shared memory.
// 'ibase' must be a valid index in iab, the first of Lock.NUMINTS reserved for the lock.
//
// iab and ibase will be exposed on Lock.
function Lock(iab, ibase) {
if (!(iab instanceof Int32Array && ibase|0 == ibase && ibase >= 0 && ibase+Lock.NUMINTS <= iab.length))
throw new Error("Bad arguments to Lock constructor: " + iab + " " + ibase);
this.iab = iab;
this.ibase = ibase;
}
// Number of shared Int32 locations needed by the lock.
Lock.NUMINTS = 1;
// Initialize shared memory for a lock, before constructing the
// worker-local Lock objects on that memory.
//
// 'iab' must be an Int32Array mapping shared memory.
// 'ibase' must be a valid index in iab, the first of Lock.NUMINTS reserved
// for the lock.
//
// Returns 'ibase'.
Lock.initialize =
function (iab, ibase) {
if (!(iab instanceof Int32Array && ibase|0 == ibase && ibase >= 0 && ibase+Lock.NUMINTS <= iab.length))
throw new Error("Bad arguments to Lock initializer: " + iab + " " + ibase);
Atomics.store(iab, ibase, 0);
return ibase;
};
// Acquire the lock, or block until we can. Locking is not recursive:
// you must not hold the lock when calling this.
Lock.prototype.lock =
function () {
const iab = this.iab;
const stateIdx = this.ibase;
var c;
if ((c = Atomics.compareExchange(iab, stateIdx, 0, 1)) != 0) {
do {
if (c == 2 || Atomics.compareExchange(iab, stateIdx, 1, 2) != 0)
Atomics.wait(iab, stateIdx, 2, Number.POSITIVE_INFINITY);
} while ((c = Atomics.compareExchange(iab, stateIdx, 0, 2)) != 0);
}
};
// Attempt to acquire the lock, return true if it was acquired, false
// if not. Locking is not recursive: you must not hold the lock when
// calling this.
Lock.prototype.tryLock =
function () {
const iab = this.iab;
const stateIdx = this.ibase;
return Atomics.compareExchange(iab, stateIdx, 0, 1) == 0;
};
// Unlock a lock that is held. Anyone can unlock a lock that is held;
// nobody can unlock a lock that is not held.
Lock.prototype.unlock =
function () {
const iab = this.iab;
const stateIdx = this.ibase;
var v0 = Atomics.sub(iab, stateIdx, 1);
// Wake up a waiter if there are any
if (v0 != 1) {
Atomics.store(iab, stateIdx, 0);
Atomics.wake(iab, stateIdx, 1);
}
};
Lock.prototype.toString =
function () {
return "Lock:{ibase:" + this.ibase +"}";
};
//////////////////////////////////////////////////////////////////////
//
// Condition variables.
//
// Condition variables are JS objects that use some shared memory for
// private data. The number of shared int32 locations needed is given
// by Cond.NUMINTS. The shared memory for a condition variable should
// be initialized once by calling Cond.initialize() on the memory,
// before constructing the first Cond object in any agent.
//
//
// Implementation note:
// The condvar code is based on http://locklessinc.com/articles/mutex_cv_futex,
// though modified because some optimizations in that code don't quite apply.
//
// Again, using Synchronic might be easier.
// Create a condition variable that can wait on a lock.
//
// 'lock' is an instance of Lock.
// 'ibase' must be a valid index in lock.iab, the first of Cond.NUMINTS reserved
// for the condition.
//
// lock.iab and ibase will be exposed on Cond.
function Cond(lock, ibase) {
if (!(lock instanceof Lock && ibase|0 == ibase && ibase >= 0 && ibase+Cond.NUMINTS <= lock.iab.length))
throw new Error("Bad arguments to Cond constructor: " + lock + " " + ibase);
this.iab = lock.iab;
this.ibase = ibase;
this.lock = lock;
}
// Number of shared Int32 locations needed by the condition variable.
Cond.NUMINTS = 1;
// Initialize shared memory for a condition variable, before
// constructing the worker-local Cond objects on that memory.
//
// Returns 'ibase'.
Cond.initialize =
function (iab, ibase) {
if (!(iab instanceof Int32Array && ibase|0 == ibase && ibase >= 0 && ibase+Cond.NUMINTS <= iab.length))
throw new Error("Bad arguments to Cond initializer: " + iab + " " + ibase);
Atomics.store(iab, ibase, 0);
return ibase;
};
// Atomically unlocks the cond's lock and wait for a wakeup on the
// cond. If there were waiters on lock then they are woken as the
// lock is unlocked.
//
// The caller must hold the lock when calling wait(). When wait()
// returns the lock will once again be held.
Cond.prototype.wait =
function () {
const iab = this.iab;
const seqIndex = this.ibase;
const seq = Atomics.load(iab, seqIndex);
const lock = this.lock;
lock.unlock();
Atomics.wait(iab, seqIndex, seq, Number.POSITIVE_INFINITY);
lock.lock();
};
// Wakes one waiter on cond. The cond's lock must be held by the
// caller of wake().
Cond.prototype.wake =
function () {
const iab = this.iab;
const seqIndex = this.ibase;
Atomics.add(iab, seqIndex, 1);
Atomics.wake(iab, seqIndex, 1);
};
// Wakes all waiters on cond. The cond's lock must be held by the
// caller of wakeAll().
Cond.prototype.wakeAll =
function () {
const iab = this.iab;
const seqIndex = this.ibase;
Atomics.add(iab, seqIndex, 1);
// Optimization opportunity: only wake one, and requeue the others
// (in such a way as to obey the locking protocol properly).
Atomics.wake(iab, seqIndex, 65535);
};
Cond.prototype.toString =
function () {
return "Cond:{ibase:" + this.ibase +"}";
};