Investigate dead-lock
This commit is contained in:
parent
564d4a6c37
commit
8adb8841ef
5 changed files with 69 additions and 11 deletions
4
.github/workflows/e2e.yml
vendored
4
.github/workflows/e2e.yml
vendored
|
|
@ -49,3 +49,7 @@ jobs:
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
scripts/e2e.sh 16
|
scripts/e2e.sh 16
|
||||||
|
|
||||||
|
- name: Cleanup
|
||||||
|
if: always()
|
||||||
|
run: scripts/clean-up.sh
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ import type { RelativePath, SyncSettings } from "sync-client";
|
||||||
import { debugging, Logger, LogLevel } from "sync-client";
|
import { debugging, Logger, LogLevel } from "sync-client";
|
||||||
import { MockClient } from "./mock-client";
|
import { MockClient } from "./mock-client";
|
||||||
import { sleep } from "../utils/sleep";
|
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 {
|
export class MockAgent extends MockClient {
|
||||||
private readonly writtenContents: string[] = [];
|
private readonly writtenContents: string[] = [];
|
||||||
|
|
@ -134,15 +137,27 @@ export class MockAgent extends MockClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async finish(): Promise<void> {
|
public async finish(): Promise<void> {
|
||||||
await this.client.setSetting("isSyncEnabled", true);
|
await withTimeout(
|
||||||
// eslint-disable-next-line no-restricted-properties
|
(async (): Promise<void> => {
|
||||||
await Promise.all(this.pendingActions);
|
await this.client.setSetting("isSyncEnabled", true);
|
||||||
await this.client.waitUntilFinished();
|
// eslint-disable-next-line no-restricted-properties
|
||||||
|
await Promise.all(this.pendingActions);
|
||||||
|
await this.client.waitUntilFinished();
|
||||||
|
})(),
|
||||||
|
TIMEOUT_MS,
|
||||||
|
"finish()"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async destroy(): Promise<void> {
|
public async destroy(): Promise<void> {
|
||||||
await this.client.waitUntilFinished();
|
await withTimeout(
|
||||||
await this.client.destroy();
|
(async (): Promise<void> => {
|
||||||
|
await this.client.waitUntilFinished();
|
||||||
|
await this.client.destroy();
|
||||||
|
})(),
|
||||||
|
TIMEOUT_MS,
|
||||||
|
"destroy()"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public assertFileSystemsAreConsistent(otherAgent: MockAgent): void {
|
public assertFileSystemsAreConsistent(otherAgent: MockAgent): void {
|
||||||
|
|
@ -184,14 +199,14 @@ export class MockAgent extends MockClient {
|
||||||
);
|
);
|
||||||
this.client.logger.info(
|
this.client.logger.info(
|
||||||
"Local files: " +
|
"Local files: " +
|
||||||
Array.from(otherAgent.localFiles.keys()).join(", ")
|
Array.from(otherAgent.localFiles.keys()).join(", ")
|
||||||
);
|
);
|
||||||
otherAgent.client.logger.info(
|
otherAgent.client.logger.info(
|
||||||
"Local data: " + JSON.stringify(otherAgent.data, null, 2)
|
"Local data: " + JSON.stringify(otherAgent.data, null, 2)
|
||||||
);
|
);
|
||||||
otherAgent.client.logger.info(
|
otherAgent.client.logger.info(
|
||||||
"Local files: " +
|
"Local files: " +
|
||||||
Array.from(otherAgent.localFiles.keys()).join(", ")
|
Array.from(otherAgent.localFiles.keys()).join(", ")
|
||||||
);
|
);
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ async function runTest({
|
||||||
// Each agent can have unpushed changes which might conflict with eachother so each has to resolve the conflicts & push, and
|
// 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) {
|
for (const client of clients) {
|
||||||
try {
|
try {
|
||||||
|
console.info(`Finishing up ${client.name}`);
|
||||||
await client.finish();
|
await client.finish();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!slowFileEvents) {
|
if (!slowFileEvents) {
|
||||||
|
|
@ -82,6 +83,7 @@ async function runTest({
|
||||||
// then we need a second pass to ensure that all agents pull the same state.
|
// then we need a second pass to ensure that all agents pull the same state.
|
||||||
for (const client of clients) {
|
for (const client of clients) {
|
||||||
try {
|
try {
|
||||||
|
console.info(`Destroying ${client.name}`);
|
||||||
await client.destroy();
|
await client.destroy();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!slowFileEvents) {
|
if (!slowFileEvents) {
|
||||||
|
|
|
||||||
23
frontend/test-client/src/utils/with-timeout.ts
Normal file
23
frontend/test-client/src/utils/with-timeout.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3,6 +3,9 @@
|
||||||
set -e
|
set -e
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
|
NO_COLOR=1
|
||||||
|
FORCE_COLOR=0
|
||||||
|
|
||||||
node_version=$(node -v | sed 's/^v\([0-9]*\).*/\1/')
|
node_version=$(node -v | sed 's/^v\([0-9]*\).*/\1/')
|
||||||
if [ "$node_version" != "22" ]; then
|
if [ "$node_version" != "22" ]; then
|
||||||
echo "Error: This script requires Node.js version 22, found: $node_version"
|
echo "Error: This script requires Node.js version 22, found: $node_version"
|
||||||
|
|
@ -37,8 +40,18 @@ cd frontend
|
||||||
|
|
||||||
pids=()
|
pids=()
|
||||||
for i in $(seq 1 $process_count); do
|
for i in $(seq 1 $process_count); do
|
||||||
node test-client/dist/cli.js > "../logs/log_${i}.log" 2>&1 &
|
# Create a named pipe for this process
|
||||||
pids+=($!)
|
pipe="/tmp/vaultlink_pipe_$$_$i"
|
||||||
|
mkfifo "$pipe"
|
||||||
|
|
||||||
|
# Start the node process writing to the pipe
|
||||||
|
node test-client/dist/cli.js > "$pipe" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
pids+=($pid)
|
||||||
|
echo "Started process $i with PID: $pid"
|
||||||
|
|
||||||
|
# Read from pipe, prefix with PID, and write to log file
|
||||||
|
(sed "s/^/[PID $pid] /" < "$pipe" > "../logs/log_${i}.log"; rm "$pipe") &
|
||||||
done
|
done
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
|
|
@ -52,6 +65,7 @@ print_failed_log() {
|
||||||
|
|
||||||
# Only consider non-zero exit codes as failures
|
# Only consider non-zero exit codes as failures
|
||||||
if [ $exit_code -ne 0 ]; then
|
if [ $exit_code -ne 0 ]; then
|
||||||
|
echo "----- Log for process ${pids[$i-1]} (log_${i}.log) -----"
|
||||||
cat "$(pwd)/logs/log_${i}.log"
|
cat "$(pwd)/logs/log_${i}.log"
|
||||||
echo "Process ${pids[$i-1]} failed with exit code $exit_code. Log file: $(pwd)/logs/log_${i}.log"
|
echo "Process ${pids[$i-1]} failed with exit code $exit_code. Log file: $(pwd)/logs/log_${i}.log"
|
||||||
return 0
|
return 0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue