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
16 changes: 11 additions & 5 deletions src/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@ import { listIndex, packedRange, parsePack } from "./browser/http.ts";
import type { Pack } from "./browser/http.ts";
import type { Backend, Stat } from "./fs.ts";

// The JDK image is read for every boot class, by every VM in the process, so
// each image file stays open once opened.
// The JDK image is read for every boot class, so each image file stays open
// once opened, shared by the VMs that read it. A VM keeps the descriptor it
// started with: an upgrade can replace the file while VMs that read its index
// are still running, and new VMs then open the new file.
const imageFds = new Map<string, number>();
export function imageFd(file: string): number {
let fd = imageFds.get(file);
if (fd === undefined) imageFds.set(file, (fd = fs.openSync(file, "r")));
const key = `${file}\0${fs.statSync(file).ino}`;
let fd = imageFds.get(key);
if (fd === undefined) imageFds.set(key, (fd = fs.openSync(file, "r")));
return fd;
}

export class ImageBackend implements Backend {
readonly writable = false;
// this VM's descriptor of each image file
private readonly fds = new Map<string, number>();
private readonly dir: string;
private readonly pack: Pack;

Expand Down Expand Up @@ -55,7 +60,8 @@ export class ImageBackend implements Backend {
const out = new Uint8Array(len);
let n = 0;
try {
const fd = imageFd(path.join(this.dir, "jdk", at.image));
let fd = this.fds.get(at.image);
if (fd === undefined) this.fds.set(at.image, (fd = imageFd(path.join(this.dir, "jdk", at.image))));
while (n < len) {
const got = fs.readSync(fd, out, n, len - n, at.at + off + n);
if (got === 0) break;
Expand Down
4 changes: 3 additions & 1 deletion src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ export class NodeBackend implements Backend {
}
// Other open files keep their buffered contents even if the host removes them.
canReadRange(p: string) { return !this.writable && p === "/lib/modules"; }
// this VM's descriptor of the JDK image
private imageFd: number | undefined;
readRange(p: string, off: number, len: number): Uint8Array | null {
const image = p === "/lib/modules" && !this.writable;
try {
const fd = image ? imageFd(this.host(p)) : fs.openSync(this.host(p), "r");
const fd = image ? (this.imageFd ??= imageFd(this.host(p))) : fs.openSync(this.host(p), "r");
try {
if (!image && !fs.fstatSync(fd).isFile()) return null;
const out = new Uint8Array(len);
Expand Down
21 changes: 20 additions & 1 deletion test/image.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// dist/ is built.

import { spawnSync } from "node:child_process";
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { existsSync, mkdirSync, mkdtempSync, renameSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";
Expand Down Expand Up @@ -65,6 +65,25 @@ test("ImageBackend reads the index, packed files and module images", () => {
}
});

test("a VM keeps reading the image it started with when an upgrade replaces it", () => {
const dir = mkdtempSync(join(tmpdir(), "jw-image-"));
try {
mkdirSync(join(dir, "jdk"));
writeFileSync(join(dir, "jdk", "image.jar"), "OLD!");
writeFileSync(join(dir, "boot.pack"), pack([["/index.txt", "d /lib\nf /lib/x 4 image:0"]]));
const text = (d: Uint8Array | null) => d && new TextDecoder().decode(d);
const before = new ImageBackend(dir);
assert.equal(text(before.read("/lib/x")), "OLD!");
// as a package manager replaces a file: a new one renamed over it
writeFileSync(join(dir, "jdk", "image.new"), "NEW!");
renameSync(join(dir, "jdk", "image.new"), join(dir, "jdk", "image.jar"));
assert.equal(text(new ImageBackend(dir).read("/lib/x")), "NEW!");
assert.equal(text(before.readRange("/lib/x", 0, 4)), "OLD!");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});

test("compact index keeps paths, sizes, offsets, and directory order", () => {
const buf = pack([["/index.prefix", "0 d /lib\n0 f /lib/one 3 image:0\n7 two 4 image:3 javac\n0 d /modules"]]);
const image = parsePack(buf.buffer as ArrayBuffer);
Expand Down
Loading