Skip to content

Commit d4c8cf7

Browse files
committed
rename RoomResource#loading to processing, and avoid TaskCancelation error
1 parent da1e0b1 commit d4c8cf7

File tree

6 files changed

+64
-27
lines changed

6 files changed

+64
-27
lines changed

packages/host/app/components/ai-assistant/panel.gts

+2-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { service } from '@ember/service';
77
import Component from '@glimmer/component';
88
import { tracked, cached } from '@glimmer/tracking';
99

10-
import { restartableTask, timeout } from 'ember-concurrency';
10+
import { allSettled, restartableTask, timeout } from 'ember-concurrency';
1111
import { Velcro } from 'ember-velcro';
1212
import window from 'ember-window-mock';
1313

@@ -446,9 +446,7 @@ export default class AiAssistantPanel extends Component<Signature> {
446446

447447
private loadRoomsTask = restartableTask(async () => {
448448
await this.matrixService.flushAll;
449-
await Promise.allSettled(
450-
[...this.roomResources.values()].map((r) => r.loading),
451-
);
449+
await allSettled([...this.roomResources.values()].map((r) => r.processing));
452450
await this.enterRoomInitially();
453451
});
454452

packages/host/app/components/matrix/room.gts

+2-2
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ export default class Room extends Component<Signature> {
162162
/>
163163
<LLMSelect
164164
@selected={{@roomResource.activeLLM}}
165-
@onChange={{@roomResource.activateLLM}}
165+
@onChange={{perform @roomResource.activateLLMTask}}
166166
@options={{this.supportedLLMs}}
167167
@disabled={{@roomResource.isActivatingLLM}}
168168
/>
@@ -555,7 +555,7 @@ export default class Room extends Component<Signature> {
555555
private doMatrixEventFlush = restartableTask(async () => {
556556
await this.matrixService.flushMembership;
557557
await this.matrixService.flushTimeline;
558-
await this.args.roomResource.loading;
558+
await this.args.roomResource.processing;
559559
});
560560

561561
private get messages() {

packages/host/app/resources/room.ts

+13-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { getOwner } from '@ember/owner';
22
import { service } from '@ember/service';
33
import { tracked, cached } from '@glimmer/tracking';
44

5-
import { restartableTask, timeout } from 'ember-concurrency';
5+
import { TaskInstance, restartableTask, timeout } from 'ember-concurrency';
66
import { Resource } from 'ember-resources';
77

88
import { TrackedMap } from 'tracked-built-ins';
@@ -74,7 +74,7 @@ export class RoomResource extends Resource<Args> {
7474
private _isDisplayingViewCodeMap: TrackedMap<string, boolean> =
7575
new TrackedMap();
7676
@tracked matrixRoom: Room | undefined;
77-
@tracked loading: Promise<void> | undefined;
77+
@tracked processing: TaskInstance<void> | undefined;
7878
@tracked roomId: string | undefined;
7979

8080
// To avoid delay, instead of using `roomResource.activeLLM`, we use a tracked property
@@ -89,10 +89,17 @@ export class RoomResource extends Resource<Args> {
8989
return;
9090
}
9191
this.roomId = named.roomId;
92-
this.loading = this.load.perform(named.roomId);
92+
this.processing = this.processRoomTask.perform(named.roomId);
9393
}
9494

95-
private load = restartableTask(async (roomId: string) => {
95+
get isProcessing() {
96+
return this.processRoomTask.isRunning;
97+
}
98+
99+
processingLastStarteAt = 0;
100+
101+
private processRoomTask = restartableTask(async (roomId: string) => {
102+
this.processingLastStarteAt = Date.now();
96103
try {
97104
this.matrixRoom = roomId
98105
? await this.matrixService.getRoomData(roomId)
@@ -271,15 +278,12 @@ export class RoomResource extends Resource<Args> {
271278
return this.llmBeingActivated ?? this.matrixRoom?.activeLLM ?? DEFAULT_LLM;
272279
}
273280

274-
activateLLM(model: string) {
275-
this.activateLLMTask.perform(model);
276-
}
277-
278281
get isActivatingLLM() {
279282
return this.activateLLMTask.isRunning;
280283
}
281284

282-
private activateLLMTask = restartableTask(async (model: string) => {
285+
activateLLMTask = restartableTask(async (model: string) => {
286+
await this.processing;
283287
if (this.activeLLM === model) {
284288
return;
285289
}

packages/host/app/services/command-service.ts

+40-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getOwner, setOwner } from '@ember/owner';
2+
import { debounce } from '@ember/runloop';
23
import Service, { service } from '@ember/service';
34
import { isTesting } from '@embroider/macros';
45

@@ -16,6 +17,7 @@ import {
1617
CommandContextStamp,
1718
getClass,
1819
identifyCard,
20+
delay,
1921
} from '@cardstack/runtime-common';
2022

2123
import type MatrixService from '@cardstack/host/services/matrix-service';
@@ -48,6 +50,7 @@ export default class CommandService extends Service {
4850
@service declare private realmServer: RealmServerService;
4951
currentlyExecutingCommandRequestIds = new TrackedSet<string>();
5052
private commandProcessingEventQueue: string[] = [];
53+
private flushCommandProcessingQueue: Promise<void> | undefined;
5154

5255
private commands: Map<
5356
string,
@@ -63,7 +66,7 @@ export default class CommandService extends Service {
6366
return name;
6467
}
6568

66-
public async queueEventForCommandProcessing(event: Partial<IEvent>) {
69+
public queueEventForCommandProcessing(event: Partial<IEvent>) {
6770
let eventId = event.event_id;
6871
if (event.content?.['m.relates_to']?.rel_type === 'm.replace') {
6972
eventId = event.content?.['m.relates_to']!.event_id;
@@ -85,24 +88,52 @@ export default class CommandService extends Service {
8588
}
8689

8790
this.commandProcessingEventQueue.push(compoundKey);
88-
let roomResource = this.matrixService.roomResources.get(event.room_id!);
89-
await roomResource?.loading;
9091

91-
this.drainCommandProcessingQueue();
92+
debounce(this, this.drainCommandProcessingQueue, 100);
9293
}
9394

9495
private async drainCommandProcessingQueue() {
95-
while (this.commandProcessingEventQueue.length > 0) {
96-
let [roomId, eventId] = this.commandProcessingEventQueue
97-
.shift()!
98-
.split('|');
96+
await this.flushCommandProcessingQueue;
97+
98+
let finishedProcessingCommands: () => void;
99+
this.flushCommandProcessingQueue = new Promise(
100+
(res) => (finishedProcessingCommands = res),
101+
);
102+
103+
let commandSpecs = [...this.commandProcessingEventQueue];
104+
this.commandProcessingEventQueue = [];
105+
106+
while (commandSpecs.length > 0) {
107+
let [roomId, eventId] = commandSpecs.shift()!.split('|');
99108

100109
let roomResource = this.matrixService.roomResources.get(roomId!);
101110
if (!roomResource) {
102111
throw new Error(
103112
`Room resource not found for room id ${roomId}, this should not happen`,
104113
);
105114
}
115+
let timeout = Date.now() + 60_000; // reset the timer to avoid a long wait if the room resource is processing
116+
let currentRoomProcessingTimestamp = roomResource.processingLastStarteAt;
117+
while (
118+
roomResource.isProcessing &&
119+
currentRoomProcessingTimestamp ===
120+
roomResource.processingLastStarteAt &&
121+
Date.now() < timeout
122+
) {
123+
// wait for the room resource to finish processing
124+
await delay(100);
125+
}
126+
if (
127+
roomResource.isProcessing &&
128+
currentRoomProcessingTimestamp === roomResource.processingLastStarteAt
129+
) {
130+
// room seems to be stuck processing, so we will log and skip this event
131+
console.error(
132+
`Room resource for room ${roomId} seems to be stuck processing, skipping event ${eventId}`,
133+
);
134+
continue;
135+
}
136+
106137
let message = roomResource.messages.find((m) => m.eventId === eventId);
107138
if (!message) {
108139
continue;
@@ -127,6 +158,7 @@ export default class CommandService extends Service {
127158
}
128159
}
129160
}
161+
finishedProcessingCommands!();
130162
}
131163

132164
get commandContext(): CommandContext {

packages/host/app/services/matrix-service.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -1529,11 +1529,11 @@ export default class MatrixService extends Service {
15291529
});
15301530
}
15311531

1532-
async setLLMForCodeMode() {
1532+
setLLMForCodeMode() {
15331533
this.setLLMModel('anthropic/claude-3.5-sonnet');
15341534
}
15351535

1536-
private async setLLMModel(model: string) {
1536+
private setLLMModel(model: string) {
15371537
if (!DEFAULT_LLM_LIST.includes(model)) {
15381538
throw new Error(`Cannot find LLM model: ${model}`);
15391539
}
@@ -1544,8 +1544,7 @@ export default class MatrixService extends Service {
15441544
if (!roomResource) {
15451545
return;
15461546
}
1547-
await roomResource.loading;
1548-
roomResource.activateLLM(model);
1547+
roomResource.activateLLMTask.perform(model);
15491548
}
15501549
}
15511550

packages/runtime-common/utils.ts

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ export async function retry<T>(
2222
return null;
2323
}
2424

25+
export async function delay(ms: number): Promise<void> {
26+
return new Promise((resolve) => setTimeout(resolve, ms));
27+
}
28+
2529
/**
2630
* Encodes a string to web-safe base64 format.
2731
* Standard base64 uses '+', '/' and '=' which can cause issues in URLs and other web contexts.

0 commit comments

Comments
 (0)