diff options
author | 2021-10-30 17:08:11 +0200 | |
---|---|---|
committer | 2021-10-31 19:26:14 +0100 | |
commit | 80b99f1f2ae86f0ce9652266daa22040b26a5894 (patch) | |
tree | 50cd24b2cfb91606cfc5c6bdd5209fabc93cd5b5 /language-web/src/main/js/xtext/UpdateService.ts | |
parent | chore(web): move logger to utils/ (diff) | |
download | refinery-80b99f1f2ae86f0ce9652266daa22040b26a5894.tar.gz refinery-80b99f1f2ae86f0ce9652266daa22040b26a5894.tar.zst refinery-80b99f1f2ae86f0ce9652266daa22040b26a5894.zip |
chore(web): refactor UpdateService
extract utils/ConditionVariable from xtext/UpdateService as a generally
useful utility class for waiting for conditions
Diffstat (limited to 'language-web/src/main/js/xtext/UpdateService.ts')
-rw-r--r-- | language-web/src/main/js/xtext/UpdateService.ts | 52 |
1 files changed, 13 insertions, 39 deletions
diff --git a/language-web/src/main/js/xtext/UpdateService.ts b/language-web/src/main/js/xtext/UpdateService.ts index 3ab1daf9..838f9d5b 100644 --- a/language-web/src/main/js/xtext/UpdateService.ts +++ b/language-web/src/main/js/xtext/UpdateService.ts | |||
@@ -7,8 +7,8 @@ import { nanoid } from 'nanoid'; | |||
7 | 7 | ||
8 | import type { EditorStore } from '../editor/EditorStore'; | 8 | import type { EditorStore } from '../editor/EditorStore'; |
9 | import type { XtextWebSocketClient } from './XtextWebSocketClient'; | 9 | import type { XtextWebSocketClient } from './XtextWebSocketClient'; |
10 | import { ConditionVariable } from '../utils/ConditionVariable'; | ||
10 | import { getLogger } from '../utils/logger'; | 11 | import { getLogger } from '../utils/logger'; |
11 | import { PendingTask } from '../utils/PendingTask'; | ||
12 | import { Timer } from '../utils/Timer'; | 12 | import { Timer } from '../utils/Timer'; |
13 | import { | 13 | import { |
14 | IContentAssistEntry, | 14 | IContentAssistEntry, |
@@ -40,7 +40,10 @@ export class UpdateService { | |||
40 | 40 | ||
41 | private webSocketClient: XtextWebSocketClient; | 41 | private webSocketClient: XtextWebSocketClient; |
42 | 42 | ||
43 | private updateListeners: PendingTask<void>[] = []; | 43 | private updatedCondition = new ConditionVariable( |
44 | () => this.pendingUpdate === null && this.xtextStateId !== null, | ||
45 | WAIT_FOR_UPDATE_TIMEOUT_MS, | ||
46 | ); | ||
44 | 47 | ||
45 | private idleUpdateTimer = new Timer(() => { | 48 | private idleUpdateTimer = new Timer(() => { |
46 | this.handleIdleUpdate(); | 49 | this.handleIdleUpdate(); |
@@ -59,9 +62,8 @@ export class UpdateService { | |||
59 | } | 62 | } |
60 | 63 | ||
61 | onTransaction(transaction: Transaction): void { | 64 | onTransaction(transaction: Transaction): void { |
62 | const { changes } = transaction; | 65 | if (transaction.docChanged) { |
63 | if (!changes.empty) { | 66 | this.dirtyChanges = this.dirtyChanges.composeDesc(transaction.changes.desc); |
64 | this.dirtyChanges = this.dirtyChanges.composeDesc(changes.desc); | ||
65 | this.idleUpdateTimer.reschedule(); | 67 | this.idleUpdateTimer.reschedule(); |
66 | } | 68 | } |
67 | } | 69 | } |
@@ -221,13 +223,7 @@ export class UpdateService { | |||
221 | [newStateId, result] = await callback(); | 223 | [newStateId, result] = await callback(); |
222 | this.xtextStateId = newStateId; | 224 | this.xtextStateId = newStateId; |
223 | this.pendingUpdate = null; | 225 | this.pendingUpdate = null; |
224 | // Copy `updateListeners` so that we don't get into a race condition | 226 | this.updatedCondition.notifyAll(); |
225 | // if one of the listeners adds another listener. | ||
226 | const listeners = this.updateListeners; | ||
227 | this.updateListeners = []; | ||
228 | listeners.forEach((listener) => { | ||
229 | listener.resolve(); | ||
230 | }); | ||
231 | return result; | 227 | return result; |
232 | } catch (e) { | 228 | } catch (e) { |
233 | log.error('Error while update', e); | 229 | log.error('Error while update', e); |
@@ -238,39 +234,17 @@ export class UpdateService { | |||
238 | } | 234 | } |
239 | this.pendingUpdate = null; | 235 | this.pendingUpdate = null; |
240 | this.webSocketClient.forceReconnectOnError(); | 236 | this.webSocketClient.forceReconnectOnError(); |
241 | const listeners = this.updateListeners; | 237 | this.updatedCondition.rejectAll(e); |
242 | this.updateListeners = []; | ||
243 | listeners.forEach((listener) => { | ||
244 | listener.reject(e); | ||
245 | }); | ||
246 | throw e; | 238 | throw e; |
247 | } | 239 | } |
248 | } | 240 | } |
249 | 241 | ||
250 | private async prepareForDeltaUpdate() { | 242 | private async prepareForDeltaUpdate() { |
251 | if (this.pendingUpdate === null) { | 243 | // If no update is pending, but the full text hasn't been uploaded to the server yet, |
252 | if (this.xtextStateId === null) { | 244 | // we must start a full text upload. |
253 | return; | 245 | if (this.pendingUpdate === null && this.xtextStateId === null) { |
254 | } | ||
255 | await this.updateFullText(); | 246 | await this.updateFullText(); |
256 | } | 247 | } |
257 | let nowMs = Date.now(); | 248 | await this.updatedCondition.waitFor(); |
258 | const endMs = nowMs + WAIT_FOR_UPDATE_TIMEOUT_MS; | ||
259 | while (this.pendingUpdate !== null && nowMs < endMs) { | ||
260 | const timeoutMs = endMs - nowMs; | ||
261 | const promise = new Promise((resolve, reject) => { | ||
262 | const task = new PendingTask(resolve, reject, timeoutMs); | ||
263 | this.updateListeners.push(task); | ||
264 | }); | ||
265 | // We must keep waiting uptil the update has completed, | ||
266 | // so the tasks can't be started in parallel. | ||
267 | // eslint-disable-next-line no-await-in-loop | ||
268 | await promise; | ||
269 | nowMs = Date.now(); | ||
270 | } | ||
271 | if (this.pendingUpdate !== null || this.xtextStateId === null) { | ||
272 | log.error('No successful update in', WAIT_FOR_UPDATE_TIMEOUT_MS, 'ms'); | ||
273 | throw new Error('Failed to wait for successful update'); | ||
274 | } | ||
275 | } | 249 | } |
276 | } | 250 | } |