Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,9 @@ jobs:
- name: Report CLI Rust build cache
if: steps.plan.outputs.cli_package == 'true'
shell: bash
run: kache report --format github >> "$GITHUB_STEP_SUMMARY"
run: |
set -o pipefail
kache report --format github | tee -a "$GITHUB_STEP_SUMMARY"

- name: Save CLI Rust build cache
if: steps.plan.outputs.cli_package == 'true' && github.ref_name == github.event.repository.default_branch
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/cli-package-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,9 @@ jobs:
node native/runtime-host-windows-task-launcher/build.mjs
- name: Report Rust build cache
shell: bash
run: kache report --format github >> "$GITHUB_STEP_SUMMARY"
run: |
set -o pipefail
kache report --format github | tee -a "$GITHUB_STEP_SUMMARY"
- name: Save Rust build cache
if: github.ref_name == github.event.repository.default_branch
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/gitoxide-helper-admission.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ jobs:
run: cargo test --locked
- name: Report Rust build cache
shell: bash
run: kache report --format github >> "$GITHUB_STEP_SUMMARY"
run: |
set -o pipefail
kache report --format github | tee -a "$GITHUB_STEP_SUMMARY"
- name: Save Rust build cache
if: github.ref_name == github.event.repository.default_branch
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/release-windows-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ jobs:
run: npm run test:windows-archives

- name: Report Rust build cache
run: kache report --format github >> "$GITHUB_STEP_SUMMARY"
run: |
set -o pipefail
kache report --format github | tee -a "$GITHUB_STEP_SUMMARY"

- name: Save Rust build cache
if: github.ref_name == github.event.repository.default_branch
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/runtime-host-peer-admission.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ jobs:
run: cargo test --locked
- name: Report Rust build cache
shell: bash
run: kache report --format github >> "$GITHUB_STEP_SUMMARY"
run: |
set -o pipefail
kache report --format github | tee -a "$GITHUB_STEP_SUMMARY"
- name: Save Rust build cache
if: github.ref_name == github.event.repository.default_branch
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/windows-sandbox-w0.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ jobs:
run: cargo test --locked
- name: Report Rust build cache
shell: bash
run: kache report --format github >> "$GITHUB_STEP_SUMMARY"
run: |
set -o pipefail
kache report --format github | tee -a "$GITHUB_STEP_SUMMARY"
- name: Save Rust build cache
if: github.ref_name == github.event.repository.default_branch
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
Expand Down
20 changes: 20 additions & 0 deletions packages/runtime-host/src/__tests__/resumable-peer-stream.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { setImmediate as tick, setTimeout as delay } from 'node:timers/promises'
import { test } from 'node:test';
import { connect, createServer, type Socket } from 'node:net';
import { once } from 'node:events';
import { performance } from 'node:perf_hooks';
import { createRuntimeHostPeerListener } from '../server/peer-listener.js';
import { RuntimeHostConnectionSession } from '../server/connection-session.js';
import { LOCAL_OWNER_CONNECTION_AUTHORITY } from '../server/connection-authority.js';
Expand Down Expand Up @@ -468,6 +469,19 @@ test('close has a hard deadline even when a healthy peer never drains its receiv
test('failed proactive upgrade preserves transit; a later direct attachment keeps the logical stream', {
timeout: 13_000,
}, async (t) => {
let now = 0;
t.mock.method(performance, 'now', () => now);
t.mock.timers.enable({ apis: ['setInterval'] });
const advance = async (milliseconds: number): Promise<void> => {
const target = now + milliseconds;
while (now < target) {
const step = Math.min(250, target - now);
now += step;
t.mock.timers.tick(step);
// Flush real duplex I/O between heartbeats instead of simulating a blackhole.
await tick();
}
};
let right!: ResumablePeerStream;
let upgrades = 0;
const left = new ResumablePeerStream({
Expand Down Expand Up @@ -515,10 +529,16 @@ test('failed proactive upgrade preserves transit; a later direct attachment keep
});
await left.write(Buffer.from('before-upgrade'));
assert.deepEqual(await right.read(), Buffer.from('before-upgrade'));
await advance(4_999);
assert.equal(upgrades, 0);
await advance(1);
assert.deepEqual(await left.read(), Buffer.from('rejected-upgrade-retains-old'));
assert.equal(left.path?.kind, 'transit');
await left.write(Buffer.from('old-path-still-live'));
assert.deepEqual(await right.read(), Buffer.from('old-path-still-live'));
await advance(4_999);
assert.equal(upgrades, 1);
await advance(1);
assert.deepEqual(await left.read(), Buffer.from('during-path-change'));
assert.equal(left.path?.kind, 'direct');
assert.equal(upgrades, 2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1363,9 +1363,13 @@ test('tolerates a short Host stall without abandoning the connection', {

test('closes an unresponsive request path even while Host notifications continue', {
timeout: 12_000,
}, async () => {
}, async (t) => {
let received = 0;
let probes = 0;
const probeReceived = deferred<void>();
const notificationsReceived = deferred<void>();
const finalNotificationReceived = deferred<void>();
let sendFinalNotification!: () => Promise<void>;
await withProtocolPeer(
async (transport, hostEpoch, rootId) => {
await transport.read(1_000);
Expand All @@ -1381,6 +1385,12 @@ test('closes an unresponsive request path even while Host notifications continue
state: 'ready',
});
let revision = 0;
sendFinalNotification = () =>
writeProtocolFrame(transport, {
kind: 'session.catalog.changed',
revision: ++revision,
sessionId: 'final-notification',
});
const notifications = setInterval(() => {
void writeProtocolFrame(transport, {
kind: 'session.catalog.changed',
Expand All @@ -1392,15 +1402,30 @@ test('closes an unresponsive request path even while Host notifications continue
const probe = decodeClientFrame(await transport.read(1_000));
assert.ok(!('kind' in probe));
assert.equal(probe.operation, 'host.status');
probeReceived.resolve();
await transport.closed;
} finally {
clearInterval(notifications);
}
},
async (connection) => {
connection.subscribeSessionCatalogChanges(() => {
let closed = false;
void connection.closed.then(() => {
closed = true;
});
connection.subscribeSessionCatalogChanges((event) => {
received += 1;
if (received > 10) notificationsReceived.resolve();
if (event.sessionId === 'final-notification') finalNotificationReceived.resolve();
});
t.mock.timers.tick(20);
await probeReceived.promise;
await notificationsReceived.promise;
t.mock.timers.tick(7_999);
await sendFinalNotification().catch(() => undefined);
await Promise.race([finalNotificationReceived.promise, connection.closed]);
assert.equal(closed, false, 'inbound events must not end the pending probe early');
t.mock.timers.tick(1);
await connection.closed;
assert.ok(received > 10, 'inbound events must remain active during the failed probe');
assert.equal(probes, 0, 'one-way events cannot acknowledge a probe');
Expand All @@ -1411,6 +1436,7 @@ test('closes an unresponsive request path even while Host notifications continue
probes += 1;
},
},
() => t.mock.timers.enable({ apis: ['setTimeout'] }),
);
});

Expand All @@ -1422,6 +1448,7 @@ async function withProtocolPeer(
readonly onLivenessProbe?: () => void;
readonly onHostStatus?: (status: HostStatusResult) => void;
} = {},
beforeConnect?: () => void,
): Promise<void> {
const base = await mkdtemp(join(tmpdir(), 'maka-runtime-host-subscription-'));
const capability = await resolveStorageRoot({
Expand Down Expand Up @@ -1459,6 +1486,7 @@ async function withProtocolPeer(
pid: process.pid,
createdAt: new Date().toISOString(),
});
beforeConnect?.();
const connected = await connectRuntimeHost({
rootPath: join(base, 'root'),
protocol: PROTOCOL,
Expand Down
7 changes: 7 additions & 0 deletions scripts/ci-workflow-policy.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,13 @@ test('Rust build caches publish immutable source generations only from the defau
name,
);
assert.doesNotMatch(workflow, /kache report [^\n]*--since/u, name);
const reports = [...workflow.matchAll(/^\s+(?:run: )?(kache report[^\n]*)$/gmu)].map(
([, command]) => command,
);
assert.equal(reports.length, 1, name);
// The report must reach the raw log, not only the rendered summary panel.
assert.equal(reports[0], 'kache report --format github | tee -a "$GITHUB_STEP_SUMMARY"', name);
assert.match(workflow, /run: \|\n\s+set -o pipefail\n\s+kache report/u, name);
}
});

Expand Down
Loading