aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/xtext/UpdateStateTracker.ts
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-08-26 17:19:36 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-08-26 17:45:20 +0200
commitd510b07aededd59443e877c4e7c7b6e2b9822dfe (patch)
tree64e4675ac200d4d85f5818472acd908666758969 /subprojects/frontend/src/xtext/UpdateStateTracker.ts
parentrefactor(frontend): simplify UpdateService further (diff)
downloadrefinery-d510b07aededd59443e877c4e7c7b6e2b9822dfe.tar.gz
refinery-d510b07aededd59443e877c4e7c7b6e2b9822dfe.tar.zst
refinery-d510b07aededd59443e877c4e7c7b6e2b9822dfe.zip
refactor(frontend): custom mutex implementation
Lets us track priorities of tasks without cancellation.
Diffstat (limited to 'subprojects/frontend/src/xtext/UpdateStateTracker.ts')
-rw-r--r--subprojects/frontend/src/xtext/UpdateStateTracker.ts48
1 files changed, 15 insertions, 33 deletions
diff --git a/subprojects/frontend/src/xtext/UpdateStateTracker.ts b/subprojects/frontend/src/xtext/UpdateStateTracker.ts
index a529f9a0..5d4ce49e 100644
--- a/subprojects/frontend/src/xtext/UpdateStateTracker.ts
+++ b/subprojects/frontend/src/xtext/UpdateStateTracker.ts
@@ -5,9 +5,9 @@ import {
5 StateEffect, 5 StateEffect,
6 type Transaction, 6 type Transaction,
7} from '@codemirror/state'; 7} from '@codemirror/state';
8import { E_CANCELED, Mutex, withTimeout } from 'async-mutex';
9 8
10import type EditorStore from '../editor/EditorStore'; 9import type EditorStore from '../editor/EditorStore';
10import PriorityMutex from '../utils/PriorityMutex';
11 11
12const WAIT_FOR_UPDATE_TIMEOUT_MS = 1000; 12const WAIT_FOR_UPDATE_TIMEOUT_MS = 1000;
13 13
@@ -31,7 +31,7 @@ export interface Delta {
31} 31}
32 32
33export default class UpdateStateTracker { 33export default class UpdateStateTracker {
34 xtextStateId: string | undefined; 34 private _xtextStateId: string | undefined;
35 35
36 /** 36 /**
37 * The changes marked for synchronization to the server if a full or delta text update 37 * The changes marked for synchronization to the server if a full or delta text update
@@ -54,12 +54,16 @@ export default class UpdateStateTracker {
54 /** 54 /**
55 * Locked when we try to modify the state on the server. 55 * Locked when we try to modify the state on the server.
56 */ 56 */
57 private readonly mutex = withTimeout(new Mutex(), WAIT_FOR_UPDATE_TIMEOUT_MS); 57 private readonly mutex = new PriorityMutex(WAIT_FOR_UPDATE_TIMEOUT_MS);
58 58
59 constructor(private readonly store: EditorStore) { 59 constructor(private readonly store: EditorStore) {
60 this.dirtyChanges = this.newEmptyChangeSet(); 60 this.dirtyChanges = this.newEmptyChangeSet();
61 } 61 }
62 62
63 get xtextStateId(): string | undefined {
64 return this._xtextStateId;
65 }
66
63 private get hasDirtyChanges(): boolean { 67 private get hasDirtyChanges(): boolean {
64 return !this.dirtyChanges.empty; 68 return !this.dirtyChanges.empty;
65 } 69 }
@@ -69,7 +73,7 @@ export default class UpdateStateTracker {
69 } 73 }
70 74
71 get lockedForUpdate(): boolean { 75 get lockedForUpdate(): boolean {
72 return this.mutex.isLocked(); 76 return this.mutex.locked;
73 } 77 }
74 78
75 get hasPendingChanges(): boolean { 79 get hasPendingChanges(): boolean {
@@ -111,7 +115,7 @@ export default class UpdateStateTracker {
111 } 115 }
112 116
113 invalidateStateId(): void { 117 invalidateStateId(): void {
114 this.xtextStateId = undefined; 118 this._xtextStateId = undefined;
115 } 119 }
116 120
117 /** 121 /**
@@ -180,7 +184,7 @@ export default class UpdateStateTracker {
180 if (remoteChanges !== undefined) { 184 if (remoteChanges !== undefined) {
181 this.applyRemoteChangesExclusive(remoteChanges); 185 this.applyRemoteChangesExclusive(remoteChanges);
182 } 186 }
183 this.xtextStateId = newStateId; 187 this._xtextStateId = newStateId;
184 this.pendingChanges = undefined; 188 this.pendingChanges = undefined;
185 } 189 }
186 190
@@ -205,7 +209,10 @@ export default class UpdateStateTracker {
205 } 209 }
206 } 210 }
207 211
208 runExclusive<T>(callback: () => Promise<T>): Promise<T> { 212 runExclusive<T>(
213 callback: () => Promise<T>,
214 highPriority = false,
215 ): Promise<T> {
209 return this.mutex.runExclusive(async () => { 216 return this.mutex.runExclusive(async () => {
210 try { 217 try {
211 return await callback(); 218 return await callback();
@@ -215,31 +222,6 @@ export default class UpdateStateTracker {
215 this.pendingChanges = undefined; 222 this.pendingChanges = undefined;
216 } 223 }
217 } 224 }
218 }); 225 }, highPriority);
219 }
220
221 runExclusiveHighPriority<T>(callback: () => Promise<T>): Promise<T> {
222 this.mutex.cancel();
223 return this.runExclusive(callback);
224 }
225
226 async runExclusiveWithRetries<T>(
227 callback: () => Promise<T>,
228 maxRetries = 5,
229 ): Promise<T> {
230 let retries = 0;
231 while (retries < maxRetries) {
232 try {
233 // eslint-disable-next-line no-await-in-loop -- Use a loop for sequential retries.
234 return await this.runExclusive(callback);
235 } catch (error) {
236 // Let timeout errors propagate to give up retrying on a flaky connection.
237 if (error !== E_CANCELED) {
238 throw error;
239 }
240 retries += 1;
241 }
242 }
243 throw E_CANCELED;
244 } 226 }
245} 227}