Skip to content

Commit

Permalink
improve queue robustness (#848)
Browse files Browse the repository at this point in the history
* explicitly disallow null or undefined items to be added to the queue

* ignore null and undefined items in VideoQueue constructor

* wrap room updates in a try catch
  • Loading branch information
dyc3 authored Apr 5, 2023
1 parent a13f39b commit 3567815
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 11 deletions.
8 changes: 6 additions & 2 deletions server/roommanager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ export async function start() {

export async function update(): Promise<void> {
for (const room of rooms) {
await room.update();
await room.sync();
try {
await room.update();
await room.sync();
} catch (e) {
log.error(`Error updating room ${room.name}: ${e}`);
}

if (room.isStale) {
await UnloadRoom(room.name);
Expand Down
49 changes: 40 additions & 9 deletions server/videoqueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class VideoQueue extends Dirtyable {
this.lock = new Mutex();

if (items) {
this._items = items;
this._items = items.filter(v => v !== null && v !== undefined);
}
}

Expand All @@ -27,18 +27,28 @@ export class VideoQueue extends Dirtyable {
return this._items.length;
}

isAllVideos(items: (Video | QueueItem)[]): boolean {
return !items.some(item => item === null || item === undefined);
}

/** Override all items in the queue */
async set(items: (Video | QueueItem)[]) {
async set(items: (Video | QueueItem)[]): Promise<void> {
await this.lock.protect(() => {
this._items = items;
this.markDirty();
});
}

/** Add the given videos to the bottom of the queue. */
async enqueue(...video: (Video | QueueItem)[]) {
async enqueue(...videos: (Video | QueueItem)[]): Promise<void> {
if (videos.length === 0) {
return;
}
if (!this.isAllVideos(videos)) {
throw new Error("Cannot enqueue null or undefined items");
}
await this.lock.protect(() => {
this._items.push(...video);
this._items.push(...videos);
this.markDirty();
});
}
Expand All @@ -53,15 +63,27 @@ export class VideoQueue extends Dirtyable {
}

/** Push the given videos on to the top of the queue. */
async pushTop(...video: (Video | QueueItem)[]) {
async pushTop(...videos: (Video | QueueItem)[]) {
if (videos.length === 0) {
return;
}
if (!this.isAllVideos(videos)) {
throw new Error("Cannot enqueue null or undefined items");
}
await this.lock.protect(() => {
this._items.unshift(...video);
this._items.unshift(...videos);
this.markDirty();
});
}

/** Insert the given video at the given index. */
async insert(video: Video | QueueItem, index: number) {
if (index < 0 || index > this._items.length) {
throw new Error("Index out of bounds");
}
if (video === null || video === undefined) {
throw new Error("Cannot enqueue null or undefined items");
}
await this.lock.protect(() => {
const newItems = this._items.splice(0, index);
newItems.push(video);
Expand All @@ -74,23 +96,32 @@ export class VideoQueue extends Dirtyable {
/**
* Move the item at index `fromIdx` to index `toIdx`.
*/
async move(fromIdx: number, toIdx: number) {
async move(fromIdx: number, toIdx: number): Promise<void> {
if (fromIdx === toIdx) {
return;
}
if (fromIdx < 0 || fromIdx >= this._items.length) {
throw new Error("'fromIdx' out of bounds");
}
if (toIdx < 0 || toIdx >= this._items.length) {
throw new Error("'toIdx' out of bounds");
}
return this.lock.protect(() => {
const item = this._items.splice(fromIdx, 1)[0];
this._items.splice(toIdx, 0, item);
this.markDirty();
});
}

findIndex(video: VideoId) {
findIndex(video: VideoId): number {
const matchIdx = _.findIndex(
this._items,
item => item.service === video.service && item.id === video.id
);
return matchIdx;
}

contains(video: VideoId) {
contains(video: VideoId): boolean {
const matchIdx = this.findIndex(video);
return matchIdx >= 0;
}
Expand Down

0 comments on commit 3567815

Please sign in to comment.