Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/array-prototype-session-tables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"capnweb": patch
---

Fix session shutdown and `getStats()` when `Array.prototype` has enumerable properties.
22 changes: 22 additions & 0 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1866,6 +1866,28 @@ describe("map() over RPC", () => {
});
});

describe("session tables", () => {
it("ignore enumerable properties added to Array.prototype", async () => {
Object.defineProperty(Array.prototype, "capnwebTestExtension", {
value: () => {},
enumerable: true,
configurable: true,
writable: true,
});
try {
let harness = new TestHarness(new TestTarget());
expect(await harness.stub.square(3)).toBe(9);
await pumpMicrotasks();
harness.checkAllDisposed();

harness.stub[Symbol.dispose]();
await pumpMicrotasks();
} finally {
delete (Array.prototype as any).capnwebTestExtension;
}
});
});

describe("stub disposal over RPC", () => {
it("disposes remote RpcTarget when stub is disposed", async () => {
let targetDisposedCount = 0;
Expand Down
22 changes: 9 additions & 13 deletions src/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -966,21 +966,21 @@ class RpcSessionImpl implements Importer, Exporter {
}
}

// WATCH OUT: these are sparse arrays. `for/let/of` will iterate only positive indexes
// including deleted indexes -- bad. We need to use `for/let/in` instead.
for (let i in this.onBrokenCallbacks) {
// WATCH OUT: these are sparse arrays. `Object.keys()` visits only their own entries, skipping
// deleted indexes and any enumerable properties added to `Array.prototype`.
for (let i of Object.keys(this.onBrokenCallbacks)) {
try {
this.onBrokenCallbacks[i](error);
} catch (err) {
// Treat as unhandled rejection.
Promise.resolve(err);
}
}
for (let i in this.imports) {
this.imports[i].abort(error);
for (let i of Object.keys(this.imports)) {
this.imports[Number(i)].abort(error);
}
for (let i in this.exports) {
this.exports[i].hook.dispose();
for (let i of Object.keys(this.exports)) {
this.exports[Number(i)].hook.dispose();
}
}

Expand Down Expand Up @@ -1142,12 +1142,8 @@ class RpcSessionImpl implements Importer, Exporter {
getStats(): {imports: number, exports: number} {
let result = {imports: 0, exports: 0};
// We can't just use `.length` because the arrays can be sparse and can have negative indexes.
for (let i in this.imports) {
++result.imports;
}
for (let i in this.exports) {
++result.exports;
}
result.imports = Object.keys(this.imports).length;
result.exports = Object.keys(this.exports).length;
return result;
}
}
Expand Down
Loading