mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(gateway,api): reach NATS over IPv4 and survive boot races (#3189)
This commit is contained in:
@@ -17,6 +17,16 @@ export interface MeilisearchTask {
|
||||
};
|
||||
}
|
||||
|
||||
export class MeilisearchTaskError extends Error {
|
||||
readonly code: string | undefined;
|
||||
|
||||
constructor(message: string, code: string | undefined) {
|
||||
super(message);
|
||||
this.name = 'MeilisearchTaskError';
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
export interface MeilisearchClient {
|
||||
request<TResponse>(method: string, path: string, body?: unknown): Promise<TResponse>;
|
||||
waitForTask(taskUid: number): Promise<void>;
|
||||
@@ -84,7 +94,10 @@ export class MeilisearchHttpClient implements MeilisearchClient {
|
||||
return;
|
||||
}
|
||||
if (task.status === 'failed' || task.status === 'canceled') {
|
||||
throw new Error(task.error?.message ?? `Meilisearch task ${taskUid} ${task.status}`);
|
||||
throw new MeilisearchTaskError(
|
||||
task.error?.message ?? `Meilisearch task ${taskUid} ${task.status}`,
|
||||
task.error?.code,
|
||||
);
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, TASK_POLL_INTERVAL_MS));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {MeilisearchClient, MeilisearchTask} from '@app/api/search/meilisearch/MeilisearchClient';
|
||||
import {MeilisearchTaskError} from '@app/api/search/meilisearch/MeilisearchClient';
|
||||
import {MeilisearchMessageAdapter} from '@app/api/search/meilisearch/MeilisearchDomainAdapters';
|
||||
import {MEILISEARCH_MAX_TRACKED_BULK_TASKS} from '@app/api/search/meilisearch/MeilisearchIndexAdapter';
|
||||
import type {SearchableMessage} from '@fluxer/schema/src/contracts/search/SearchDocumentTypes';
|
||||
@@ -15,6 +16,7 @@ interface RecordedMeilisearchRequest {
|
||||
class FakeMeilisearchClient implements MeilisearchClient {
|
||||
readonly requests: Array<RecordedMeilisearchRequest> = [];
|
||||
readonly waitedTaskUids: Array<number> = [];
|
||||
readonly failedTasks = new Map<number, MeilisearchTaskError>();
|
||||
private nextTaskUid = 1;
|
||||
indexExists = false;
|
||||
|
||||
@@ -50,6 +52,10 @@ class FakeMeilisearchClient implements MeilisearchClient {
|
||||
|
||||
async waitForTask(taskUid: number): Promise<void> {
|
||||
this.waitedTaskUids.push(taskUid);
|
||||
const failure = this.failedTasks.get(taskUid);
|
||||
if (failure) {
|
||||
throw failure;
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
@@ -87,6 +93,26 @@ describe('MeilisearchMessageAdapter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('treats an index created concurrently by another process as created', async () => {
|
||||
const client = new FakeMeilisearchClient();
|
||||
client.failedTasks.set(1, new MeilisearchTaskError('Index `messages` already exists.', 'index_already_exists'));
|
||||
const adapter = new MeilisearchMessageAdapter({client});
|
||||
|
||||
await adapter.initialize();
|
||||
|
||||
expect(adapter.isAvailable()).toBe(true);
|
||||
expect(client.waitedTaskUids).toEqual([1, 2, 3, 4, 5]);
|
||||
});
|
||||
|
||||
it('still fails when creating the index fails for another reason', async () => {
|
||||
const client = new FakeMeilisearchClient();
|
||||
client.failedTasks.set(1, new MeilisearchTaskError('Index uid is invalid.', 'invalid_index_uid'));
|
||||
const adapter = new MeilisearchMessageAdapter({client});
|
||||
|
||||
await expect(adapter.initialize()).rejects.toThrow('Index uid is invalid.');
|
||||
expect(adapter.isAvailable()).toBe(false);
|
||||
});
|
||||
|
||||
it('builds Meilisearch search requests from message filters', async () => {
|
||||
const client = new FakeMeilisearchClient();
|
||||
client.indexExists = true;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {MeilisearchClient, MeilisearchTask} from '@app/api/search/meilisearch/MeilisearchClient';
|
||||
import {MeilisearchTaskError} from '@app/api/search/meilisearch/MeilisearchClient';
|
||||
import type {MeilisearchFilter} from '@app/api/search/meilisearch/MeilisearchFilterUtils';
|
||||
import {joinMeiliFilters} from '@app/api/search/meilisearch/MeilisearchFilterUtils';
|
||||
import type {MeilisearchIndexDefinition} from '@app/api/search/meilisearch/MeilisearchIndexDefinitions';
|
||||
@@ -58,7 +59,13 @@ export class MeilisearchIndexAdapter<
|
||||
uid,
|
||||
primaryKey: this.indexDefinition.primaryKey,
|
||||
});
|
||||
await this.client.waitForTask(task.taskUid);
|
||||
try {
|
||||
await this.client.waitForTask(task.taskUid);
|
||||
} catch (error) {
|
||||
if (!(error instanceof MeilisearchTaskError && error.code === 'index_already_exists')) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all([
|
||||
this.applySetting('PUT', 'searchable-attributes', this.indexDefinition.searchableAttributes),
|
||||
|
||||
@@ -414,7 +414,8 @@ export class JetStreamWorkerQueue {
|
||||
this.requireConsumerConfiguration(existing, lane.consumerName);
|
||||
const updated = await jsm.consumers.update(STREAM_NAME, lane.consumerName, config);
|
||||
this.requireConsumerConfiguration(updated, lane.consumerName);
|
||||
if (updated.created !== existing.created) {
|
||||
const current = await this.readConsumer(jsm, lane.consumerName);
|
||||
if (current?.created !== existing.created) {
|
||||
throw new Error(`Worker consumer ${lane.consumerName} was replaced during startup`);
|
||||
}
|
||||
Logger.info({lane: lane.name, consumer: lane.consumerName}, 'Consumer updated without resetting delivery state');
|
||||
|
||||
Reference in New Issue
Block a user