diff options
Diffstat (limited to 'subprojects/frontend/src/xtext/UpdateStateTracker.ts')
-rw-r--r-- | subprojects/frontend/src/xtext/UpdateStateTracker.ts | 48 |
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'; |
8 | import { E_CANCELED, Mutex, withTimeout } from 'async-mutex'; | ||
9 | 8 | ||
10 | import type EditorStore from '../editor/EditorStore'; | 9 | import type EditorStore from '../editor/EditorStore'; |
10 | import PriorityMutex from '../utils/PriorityMutex'; | ||
11 | 11 | ||
12 | const WAIT_FOR_UPDATE_TIMEOUT_MS = 1000; | 12 | const WAIT_FOR_UPDATE_TIMEOUT_MS = 1000; |
13 | 13 | ||
@@ -31,7 +31,7 @@ export interface Delta { | |||
31 | } | 31 | } |
32 | 32 | ||
33 | export default class UpdateStateTracker { | 33 | export 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 | } |