UNPKG

9.44 kBJavaScriptView Raw
1"use strict";
2/*! *****************************************************************************
3Copyright (c) Microsoft Corporation.
4Licensed under the Apache License, Version 2.0.
5
6See LICENSE file in the project root for details.
7***************************************************************************** */
8Object.defineProperty(exports, "__esModule", { value: true });
9const list_1 = require("./list");
10const cancellation_1 = require("./cancellation");
11const adapter_1 = require("./adapter");
12const disposable_1 = require("@esfx/disposable");
13/**
14 * Coordinates readers and writers for a resource.
15 */
16class ReaderWriterLock {
17 constructor() {
18 this._readers = new list_1.LinkedList();
19 this._upgradeables = new list_1.LinkedList();
20 this._upgrades = new list_1.LinkedList();
21 this._writers = new list_1.LinkedList();
22 this._count = 0;
23 }
24 /**
25 * Asynchronously waits for and takes a read lock on a resource.
26 *
27 * @param token A CancellationToken used to cancel the request.
28 */
29 read(token) {
30 return new Promise((resolve, reject) => {
31 const _token = adapter_1.getToken(token);
32 _token.throwIfCancellationRequested();
33 if (this._canTakeReadLock()) {
34 resolve(this._takeReadLock());
35 return;
36 }
37 const node = this._readers.push(() => {
38 registration.unregister();
39 if (_token.cancellationRequested) {
40 reject(new cancellation_1.CancelError());
41 }
42 else {
43 resolve(this._takeReadLock());
44 }
45 });
46 const registration = _token.register(() => {
47 if (node.list) {
48 node.list.deleteNode(node);
49 reject(new cancellation_1.CancelError());
50 }
51 });
52 });
53 }
54 /**
55 * Asynchronously waits for and takes a read lock on a resource
56 * that can later be upgraded to a write lock.
57 *
58 * @param token A CancellationToken used to cancel the request.
59 */
60 upgradeableRead(token) {
61 return new Promise((resolve, reject) => {
62 const _token = adapter_1.getToken(token);
63 _token.throwIfCancellationRequested();
64 if (this._canTakeUpgradeableReadLock()) {
65 resolve(this._takeUpgradeableReadLock());
66 return;
67 }
68 const node = this._upgradeables.push(() => {
69 registration.unregister();
70 if (_token.cancellationRequested) {
71 reject(new cancellation_1.CancelError());
72 }
73 else {
74 resolve(this._takeUpgradeableReadLock());
75 }
76 });
77 const registration = _token.register(() => {
78 if (node.list) {
79 node.list.deleteNode(node);
80 reject(new cancellation_1.CancelError());
81 }
82 });
83 });
84 }
85 /**
86 * Asynchronously waits for and takes a write lock on a resource.
87 *
88 * @param token A CancellationToken used to cancel the request.
89 */
90 write(token) {
91 return new Promise((resolve, reject) => {
92 const _token = adapter_1.getToken(token);
93 _token.throwIfCancellationRequested();
94 if (this._canTakeWriteLock()) {
95 resolve(this._takeWriteLock());
96 return;
97 }
98 const node = this._writers.push(() => {
99 registration.unregister();
100 if (_token.cancellationRequested) {
101 reject(new cancellation_1.CancelError());
102 }
103 else {
104 resolve(this._takeWriteLock());
105 }
106 });
107 const registration = _token.register(() => {
108 if (node.list) {
109 node.list.deleteNode(node);
110 reject(new cancellation_1.CancelError());
111 }
112 });
113 });
114 }
115 _upgrade(token) {
116 return new Promise((resolve, reject) => {
117 const _token = adapter_1.getToken(token);
118 _token.throwIfCancellationRequested();
119 if (this._canTakeUpgradeLock()) {
120 resolve(this._takeUpgradeLock());
121 return;
122 }
123 const node = this._upgrades.push(() => {
124 registration.unregister();
125 if (_token.cancellationRequested) {
126 reject(new cancellation_1.CancelError());
127 }
128 else {
129 resolve(this._takeUpgradeLock());
130 }
131 });
132 const registration = _token.register(() => {
133 if (node.list) {
134 node.list.deleteNode(node);
135 reject(new cancellation_1.CancelError());
136 }
137 });
138 });
139 }
140 _processLockRequests() {
141 if (this._processWriteLockRequest())
142 return;
143 if (this._processUpgradeRequest())
144 return;
145 this._processUpgradeableReadLockRequest();
146 this._processReadLockRequests();
147 }
148 _canTakeReadLock() {
149 return this._count >= 0
150 && this._writers.size === 0
151 && this._upgrades.size === 0
152 && this._writers.size === 0;
153 }
154 _processReadLockRequests() {
155 if (this._canTakeReadLock()) {
156 this._readers.forEach(resolve => resolve());
157 this._readers.clear();
158 }
159 }
160 _takeReadLock() {
161 let released = false;
162 this._count++;
163 const release = () => {
164 if (released)
165 throw new Error("Lock already released.");
166 released = true;
167 this._releaseReadLock();
168 };
169 return {
170 release,
171 [disposable_1.Disposable.dispose]: release,
172 };
173 }
174 _releaseReadLock() {
175 this._count--;
176 this._processLockRequests();
177 }
178 _canTakeUpgradeableReadLock() {
179 return this._count >= 0 && !this._upgradeable;
180 }
181 _processUpgradeableReadLockRequest() {
182 if (this._canTakeUpgradeableReadLock()) {
183 const resolve = this._upgradeables.shift();
184 if (resolve) {
185 resolve();
186 }
187 }
188 }
189 _takeUpgradeableReadLock() {
190 const upgrade = (token) => {
191 if (this._upgradeable !== hold)
192 throw new Error("Lock already released.");
193 return this._upgrade(token);
194 };
195 const release = () => {
196 if (this._upgradeable !== hold)
197 throw new Error("Lock already released.");
198 this._releaseUpgradeableReadLock();
199 };
200 const hold = {
201 upgrade,
202 release,
203 [disposable_1.Disposable.dispose]: release,
204 };
205 this._count++;
206 this._upgradeable = hold;
207 return hold;
208 }
209 _releaseUpgradeableReadLock() {
210 if (this._count === -1) {
211 this._count = 0;
212 }
213 else {
214 this._count--;
215 }
216 this._upgraded = undefined;
217 this._upgradeable = undefined;
218 this._processLockRequests();
219 }
220 _canTakeUpgradeLock() {
221 return this._count === 1
222 && this._upgradeable
223 && !this._upgraded;
224 }
225 _processUpgradeRequest() {
226 if (this._canTakeUpgradeLock()) {
227 const resolve = this._upgrades.shift();
228 if (resolve) {
229 resolve();
230 return true;
231 }
232 }
233 return false;
234 }
235 _takeUpgradeLock() {
236 const release = () => {
237 if (this._upgraded !== hold)
238 throw new Error("Lock already released.");
239 this._releaseUpgradeLock();
240 };
241 const hold = {
242 release,
243 [disposable_1.Disposable.dispose]: release
244 };
245 this._upgraded = hold;
246 this._count = -1;
247 return hold;
248 }
249 _releaseUpgradeLock() {
250 this._upgraded = undefined;
251 this._count = 1;
252 this._processLockRequests();
253 }
254 _canTakeWriteLock() {
255 return this._count === 0;
256 }
257 _processWriteLockRequest() {
258 if (this._canTakeWriteLock()) {
259 const resolve = this._writers.shift();
260 if (resolve) {
261 resolve();
262 return true;
263 }
264 }
265 return false;
266 }
267 _takeWriteLock() {
268 let released = false;
269 this._count = -1;
270 const release = () => {
271 if (released)
272 throw new Error("Lock already released.");
273 released = true;
274 this._releaseWriteLock();
275 };
276 return {
277 release,
278 [disposable_1.Disposable.dispose]: release,
279 };
280 }
281 _releaseWriteLock() {
282 this._count = 0;
283 this._processLockRequests();
284 }
285}
286exports.ReaderWriterLock = ReaderWriterLock;