Investigate deadlock (#178)

This commit is contained in:
Andras Schmelczer 2025-12-05 22:34:14 +00:00 committed by GitHub
parent 564d4a6c37
commit 7a13cb57ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 85 additions and 17 deletions

View file

@ -2,10 +2,13 @@ import { choose } from "../utils/choose";
import { v4 as uuidv4 } from "uuid";
import { assert } from "../utils/assert";
import type { RelativePath, SyncSettings } from "sync-client";
import { debugging, Logger, LogLevel } from "sync-client";
import { debugging, Logger, LogLevel, utils } from "sync-client";
import { MockClient } from "./mock-client";
import { sleep } from "../utils/sleep";
import type { LogLine } from "sync-client/dist/types/tracing/logger";
import type { LogLine } from "sync-client";
import { withTimeout } from "../utils/with-timeout";
const TIMEOUT_MS = 10 * 60 * 1000;
export class MockAgent extends MockClient {
private readonly writtenContents: string[] = [];
@ -134,15 +137,26 @@ export class MockAgent extends MockClient {
}
public async finish(): Promise<void> {
await this.client.setSetting("isSyncEnabled", true);
// eslint-disable-next-line no-restricted-properties
await Promise.all(this.pendingActions);
await this.client.waitUntilFinished();
await withTimeout(
(async (): Promise<void> => {
await this.client.setSetting("isSyncEnabled", true);
await utils.awaitAll(this.pendingActions);
await this.client.waitUntilFinished();
})(),
TIMEOUT_MS,
"finish()"
);
}
public async destroy(): Promise<void> {
await this.client.waitUntilFinished();
await this.client.destroy();
await withTimeout(
(async (): Promise<void> => {
await this.client.waitUntilFinished();
await this.client.destroy();
})(),
TIMEOUT_MS,
"destroy()"
);
}
public assertFileSystemsAreConsistent(otherAgent: MockAgent): void {

View file

@ -1,4 +1,5 @@
import type { SyncSettings } from "sync-client";
import { utils } from "sync-client";
import { MockAgent } from "./agent/mock-agent";
import { sleep } from "./utils/sleep";
import { v4 as uuidv4 } from "uuid";
@ -56,14 +57,12 @@ async function runTest({
}
try {
// eslint-disable-next-line no-restricted-properties
await Promise.all(clients.map(async (client) => client.init()));
await utils.awaitAll(clients.map(async (client) => client.init()));
for (let i = 0; i < iterations; i++) {
console.info(`Iteration ${i + 1}/${iterations}`);
// eslint-disable-next-line no-restricted-properties
await Promise.all(clients.map(async (client) => client.act()));
await sleep(100);
await utils.awaitAll(clients.map(async (client) => client.act()));
await sleep(Math.random() * 200);
}
console.info("Stopping agents");
@ -71,6 +70,7 @@ async function runTest({
// Each agent can have unpushed changes which might conflict with eachother so each has to resolve the conflicts & push, and
for (const client of clients) {
try {
console.info(`Finishing up ${client.name}`);
await client.finish();
} catch (err) {
if (!slowFileEvents) {
@ -82,6 +82,7 @@ async function runTest({
// then we need a second pass to ensure that all agents pull the same state.
for (const client of clients) {
try {
console.info(`Destroying ${client.name}`);
await client.destroy();
} catch (err) {
if (!slowFileEvents) {

View file

@ -0,0 +1,16 @@
export async function withTimeout<T>(
promise: Promise<T>,
timeoutMs: number,
operationName: string
): Promise<T> {
return Promise.race([
promise,
new Promise<T>((_, reject) =>
setTimeout(() => {
reject(
new Error(`${operationName} timed out after ${timeoutMs}ms`)
);
}, timeoutMs)
)
]);
}