diff options
author | Kristóf Marussy <kristof@marussy.com> | 2022-08-26 17:19:36 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2022-08-26 17:45:20 +0200 |
commit | d510b07aededd59443e877c4e7c7b6e2b9822dfe (patch) | |
tree | 64e4675ac200d4d85f5818472acd908666758969 /subprojects/frontend/src/xtext | |
parent | refactor(frontend): simplify UpdateService further (diff) | |
download | refinery-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')
-rw-r--r-- | subprojects/frontend/src/xtext/UpdateService.ts | 23 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/UpdateStateTracker.ts | 48 |
2 files changed, 28 insertions, 43 deletions
diff --git a/subprojects/frontend/src/xtext/UpdateService.ts b/subprojects/frontend/src/xtext/UpdateService.ts index d8782d90..f1abce52 100644 --- a/subprojects/frontend/src/xtext/UpdateService.ts +++ b/subprojects/frontend/src/xtext/UpdateService.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import type { ChangeDesc, Transaction } from '@codemirror/state'; | 1 | import type { ChangeDesc, Transaction } from '@codemirror/state'; |
2 | import { E_CANCELED, E_TIMEOUT } from 'async-mutex'; | ||
3 | import { debounce } from 'lodash-es'; | 2 | import { debounce } from 'lodash-es'; |
4 | import { nanoid } from 'nanoid'; | 3 | import { nanoid } from 'nanoid'; |
5 | 4 | ||
6 | import type EditorStore from '../editor/EditorStore'; | 5 | import type EditorStore from '../editor/EditorStore'; |
6 | import CancelledError from '../utils/CancelledError'; | ||
7 | import TimeoutError from '../utils/TimeoutError'; | ||
7 | import getLogger from '../utils/getLogger'; | 8 | import getLogger from '../utils/getLogger'; |
8 | 9 | ||
9 | import UpdateStateTracker from './UpdateStateTracker'; | 10 | import UpdateStateTracker from './UpdateStateTracker'; |
@@ -66,7 +67,7 @@ export default class UpdateService { | |||
66 | this.updateFullTextOrThrow().catch((error) => { | 67 | this.updateFullTextOrThrow().catch((error) => { |
67 | // Let E_TIMEOUT errors propagate, since if the first update times out, | 68 | // Let E_TIMEOUT errors propagate, since if the first update times out, |
68 | // we can't use the connection. | 69 | // we can't use the connection. |
69 | if (error === E_CANCELED) { | 70 | if (error instanceof CancelledError) { |
70 | // Content assist will perform a full-text update anyways. | 71 | // Content assist will perform a full-text update anyways. |
71 | log.debug('Full text update cancelled'); | 72 | log.debug('Full text update cancelled'); |
72 | return; | 73 | return; |
@@ -87,7 +88,7 @@ export default class UpdateService { | |||
87 | } | 88 | } |
88 | if (!this.tracker.lockedForUpdate) { | 89 | if (!this.tracker.lockedForUpdate) { |
89 | this.updateOrThrow().catch((error) => { | 90 | this.updateOrThrow().catch((error) => { |
90 | if (error === E_CANCELED || error === E_TIMEOUT) { | 91 | if (error instanceof CancelledError || error instanceof TimeoutError) { |
91 | log.debug('Idle update cancelled'); | 92 | log.debug('Idle update cancelled'); |
92 | return; | 93 | return; |
93 | } | 94 | } |
@@ -163,11 +164,15 @@ export default class UpdateService { | |||
163 | return this.fetchContentAssistFetchOnly(params, this.xtextStateId); | 164 | return this.fetchContentAssistFetchOnly(params, this.xtextStateId); |
164 | } | 165 | } |
165 | try { | 166 | try { |
166 | return await this.tracker.runExclusiveHighPriority(() => | 167 | return await this.tracker.runExclusive( |
167 | this.fetchContentAssistExclusive(params, signal), | 168 | () => this.fetchContentAssistExclusive(params, signal), |
169 | true, | ||
168 | ); | 170 | ); |
169 | } catch (error) { | 171 | } catch (error) { |
170 | if ((error === E_CANCELED || error === E_TIMEOUT) && signal.aborted) { | 172 | if ( |
173 | (error instanceof CancelledError || error instanceof TimeoutError) && | ||
174 | signal.aborted | ||
175 | ) { | ||
171 | return []; | 176 | return []; |
172 | } | 177 | } |
173 | throw error; | 178 | throw error; |
@@ -261,9 +266,7 @@ export default class UpdateService { | |||
261 | } | 266 | } |
262 | 267 | ||
263 | formatText(): Promise<void> { | 268 | formatText(): Promise<void> { |
264 | return this.tracker.runExclusiveWithRetries(() => | 269 | return this.tracker.runExclusive(() => this.formatTextExclusive()); |
265 | this.formatTextExclusive(), | ||
266 | ); | ||
267 | } | 270 | } |
268 | 271 | ||
269 | private async formatTextExclusive(): Promise<void> { | 272 | private async formatTextExclusive(): Promise<void> { |
@@ -294,7 +297,7 @@ export default class UpdateService { | |||
294 | try { | 297 | try { |
295 | await this.updateOrThrow(); | 298 | await this.updateOrThrow(); |
296 | } catch (error) { | 299 | } catch (error) { |
297 | if (error === E_CANCELED || error === E_TIMEOUT) { | 300 | if (error instanceof CancelledError || error instanceof TimeoutError) { |
298 | return { cancelled: true }; | 301 | return { cancelled: true }; |
299 | } | 302 | } |
300 | throw error; | 303 | throw error; |
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 | } |