Skip to content

Commit 6f1604e

Browse files
committed
Moved over LazyFile
`OverlayFS` no longer uses a mutex Improved `FSRequest` types in `Port` Fixed `PortFS.read` buffer transfer issues Implemented `readSync` and `writeSync` on `Async`
1 parent 165f48d commit 6f1604e

File tree

7 files changed

+69
-69
lines changed

7 files changed

+69
-69
lines changed

src/backends/overlay.ts

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import type { Backend } from './backend.js';
55
import type { InodeLike } from './store/inode.js';
66

77
import { Errno, ErrnoError } from '../error.js';
8-
import { PreloadFile, parseFlag } from '../file.js';
8+
import { LazyFile, parseFlag } from '../file.js';
99
import { FileSystem } from '../filesystem.js';
10-
import { Mutexed } from '../mixins/mutexed.js';
1110
import { canary, decodeUTF8, encodeUTF8 } from '../utils.js';
1211
import { dirname, join } from '../vfs/path.js';
1312

@@ -36,7 +35,7 @@ export interface OverlayOptions {
3635
*
3736
* @internal
3837
*/
39-
export class UnmutexedOverlayFS extends FileSystem {
38+
export class OverlayFS extends FileSystem {
4039
async ready(): Promise<void> {
4140
await this.readable.ready();
4241
await this.writable.ready();
@@ -203,23 +202,16 @@ export class UnmutexedOverlayFS extends FileSystem {
203202
if (await this.writable.exists(path)) {
204203
return this.writable.openFile(path, flag);
205204
}
206-
// Create an OverlayFile.
207-
const file = await this.readable.openFile(path, parseFlag('r'));
208-
const stats = await file.stat();
209-
const { buffer } = await file.read(new Uint8Array(stats.size));
210-
return new PreloadFile(this, path, flag, stats, buffer);
205+
const stats = await this.readable.stat(path);
206+
return new LazyFile(this, path, flag, stats);
211207
}
212208

213209
public openFileSync(path: string, flag: string): File {
214210
if (this.writable.existsSync(path)) {
215211
return this.writable.openFileSync(path, flag);
216212
}
217-
// Create an OverlayFile.
218-
const file = this.readable.openFileSync(path, parseFlag('r'));
219-
const stats = file.statSync();
220-
const data = new Uint8Array(stats.size);
221-
file.readSync(data);
222-
return new PreloadFile(this, path, flag, stats, data);
213+
const stats = this.readable.statSync(path);
214+
return new LazyFile(this, path, flag, stats);
223215
}
224216

225217
public async createFile(path: string, flag: string, mode: number, options: CreationOptions): Promise<File> {
@@ -555,14 +547,6 @@ export class UnmutexedOverlayFS extends FileSystem {
555547
}
556548
}
557549

558-
/**
559-
* OverlayFS makes a read-only filesystem writable by storing writes on a second,
560-
* writable file system. Deletes are persisted via metadata stored on the writable
561-
* file system.
562-
* @internal
563-
*/
564-
export class OverlayFS extends Mutexed(UnmutexedOverlayFS) {}
565-
566550
const _Overlay = {
567551
name: 'Overlay',
568552
options: {

src/backends/port/fs.ts

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import { pick, type ExtractProperties } from 'utilium';
2+
import type { ExtractProperties } from 'utilium';
33
import type { MountConfiguration } from '../../config.js';
44
import type { CreationOptions, FileSystemMetadata } from '../../filesystem.js';
55
import type { Backend, FilesystemOf } from '../backend.js';
6+
import type { Inode, InodeLike } from '../store/inode.js';
7+
import type { File } from '../../file.js';
68

7-
import type { TransferListItem } from 'node:worker_threads';
9+
import { pick } from 'utilium';
810
import { resolveMountConfig } from '../../config.js';
911
import { Errno, ErrnoError } from '../../error.js';
10-
import { File } from '../../file.js';
1112
import { FileSystem } from '../../filesystem.js';
1213
import { Async } from '../../mixins/async.js';
1314
import { Stats } from '../../stats.js';
15+
import { decodeUTF8 } from '../../utils.js';
1416
import { InMemory } from '../memory.js';
15-
import type { Inode, InodeLike } from '../store/inode.js';
1617
import * as RPC from './rpc.js';
1718

1819
type FSMethods = ExtractProperties<FileSystem, (...args: any[]) => Promise<any> | FileSystemMetadata>;
1920
type FSMethod = keyof FSMethods;
20-
/** @internal */
21-
export interface FSRequest<TMethod extends FSMethod = FSMethod> extends RPC.Request {
22-
method: TMethod;
23-
args: Parameters<FSMethods[TMethod]>;
24-
}
21+
22+
export type FSRequest<TMethod extends FSMethod = FSMethod> = RPC.Message &
23+
{
24+
[M in TMethod]: {
25+
method: M;
26+
args: Parameters<FSMethods[M]>;
27+
};
28+
}[TMethod];
2529

2630
/**
2731
* PortFS lets you access an FS instance that is running in a port, or the other way around.
@@ -54,7 +58,7 @@ export class PortFS extends Async(FileSystem) {
5458
}
5559

5660
protected rpc<const T extends FSMethod>(method: T, ...args: Parameters<FSMethods[T]>): Promise<Awaited<ReturnType<FSMethods[T]>>> {
57-
return RPC.request<FSRequest<T>, Awaited<ReturnType<FSMethods[T]>>>({ method, args }, { ...this.options, fs: this });
61+
return RPC.request<FSRequest<T>, Awaited<ReturnType<FSMethods[T]>>>({ method, args } as Omit<FSRequest<T>, 'id' | 'stack' | '_zenfs'>, { ...this.options, fs: this });
5862
}
5963

6064
public async ready(): Promise<void> {
@@ -107,8 +111,9 @@ export class PortFS extends Async(FileSystem) {
107111
return this.rpc('link', srcpath, dstpath);
108112
}
109113

110-
public read(path: string, buffer: Uint8Array, offset: number, length: number): Promise<void> {
111-
return this.rpc('read', path, buffer, offset, length);
114+
public async read(path: string, buffer: Uint8Array, offset: number, length: number): Promise<void> {
115+
const _buf = (await this.rpc('read', path, buffer, offset, length)) as unknown as Uint8Array;
116+
buffer.set(_buf);
112117
}
113118

114119
public write(path: string, buffer: Uint8Array, offset: number): Promise<void> {
@@ -125,31 +130,29 @@ export async function handleRequest(port: RPC.Port, fs: FileSystem & { _descript
125130
let value,
126131
error: boolean = false;
127132

128-
const transfer: TransferListItem[] = [];
129-
130133
try {
131134
// @ts-expect-error 2556
132135
value = await fs[method](...args);
133-
if (value instanceof File) {
134-
await using file = await fs.openFile(args[0] as string, 'r+');
135-
const stats = await file.stat();
136-
const data = new Uint8Array(stats.size);
137-
138-
await file.read(data);
139-
value = {
140-
path: value.path,
141-
flag: args[1] as string,
142-
stats,
143-
buffer: data.buffer,
144-
} satisfies RPC.FileData;
145-
transfer.push(data.buffer);
136+
switch (method) {
137+
case 'openFile':
138+
case 'createFile': {
139+
value = {
140+
path: args[0],
141+
flag: args[1],
142+
stats: await fs.stat(args[0]),
143+
} satisfies RPC.FileData;
144+
break;
145+
}
146+
case 'read':
147+
value = args[1];
148+
break;
146149
}
147150
} catch (e: any) {
148151
value = e instanceof ErrnoError ? e.toJSON() : pick(e, 'message', 'stack');
149152
error = true;
150153
}
151154

152-
port.postMessage({ _zenfs: true, id, error, method, stack, value }, transfer);
155+
port.postMessage({ _zenfs: true, id, error, method, stack, value });
153156
}
154157

155158
export function attachFS(port: RPC.Port, fs: FileSystem): void {

src/backends/port/rpc.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { Backend, FilesystemOf } from '../backend.js';
77
import type { PortFS } from './fs.js';
88

99
import { Errno, ErrnoError } from '../../error.js';
10-
import { PreloadFile } from '../../file.js';
10+
import { LazyFile } from '../../file.js';
1111
import { Stats, type StatsLike } from '../../stats.js';
1212
import { handleRequest } from './fs.js';
1313

@@ -57,13 +57,18 @@ interface _ResponseWithValue<T> extends Message {
5757
value: Awaited<T> extends File ? FileData : Awaited<T>;
5858
}
5959

60-
export type Response<T = unknown> = _ResponseWithError | _ResponseWithValue<T>;
60+
interface _ResponseRead extends Message {
61+
error: false;
62+
method: 'read';
63+
value: Uint8Array;
64+
}
65+
66+
export type Response<T = unknown> = _ResponseWithError | _ResponseWithValue<T> | _ResponseRead;
6167

6268
export interface FileData {
6369
path: string;
6470
flag: string;
6571
stats: StatsLike<number>;
66-
buffer: ArrayBuffer;
6772
}
6873

6974
function isFileData(value: unknown): value is FileData {
@@ -129,8 +134,8 @@ export function handleResponse<const TResponse extends Response>(response: TResp
129134
}
130135

131136
if (isFileData(value)) {
132-
const { path, flag, stats, buffer } = value;
133-
const file = new PreloadFile(fs!, path, flag, new Stats(stats), new Uint8Array(buffer));
137+
const { path, flag, stats } = value;
138+
const file = new LazyFile(fs!, path, flag, new Stats(stats));
134139
resolve(file);
135140
executors.delete(id);
136141
return;

src/backends/store/fs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { randomInt, serialize } from 'utilium';
22
import { Errno, ErrnoError } from '../../error.js';
33
import type { File } from '../../file.js';
4-
import { LazyFile, PreloadFile } from '../../file.js';
4+
import { LazyFile } from '../../file.js';
55
import type { CreationOptions, FileSystemMetadata, PureCreationOptions } from '../../filesystem.js';
66
import { FileSystem } from '../../filesystem.js';
77
import type { FileType, Stats } from '../../stats.js';

src/backends/store/index_fs.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ErrnoError } from '../../error.js';
22
import type { File } from '../../file.js';
3-
import { PreloadFile } from '../../file.js';
3+
import { LazyFile } from '../../file.js';
44
import type { CreationOptions } from '../../filesystem.js';
55
import { Stats } from '../../stats.js';
66
import { S_IFREG } from '../../vfs/constants.js';
@@ -58,14 +58,14 @@ export abstract class IndexFS<T extends Store> extends StoreFS<T> {
5858

5959
public override async createFile(path: string, flag: string, mode: number, options: CreationOptions): Promise<File> {
6060
const node = await this.commitNew(path, S_IFREG, { mode, ...options }, new Uint8Array(), 'createFile');
61-
const file = new PreloadFile(this, path, flag, node.toStats(), new Uint8Array());
61+
const file = new LazyFile(this, path, flag, node.toStats());
6262
this.index.set(path, node);
6363
return file;
6464
}
6565

6666
public createFileSync(path: string, flag: string, mode: number, options: CreationOptions): File {
6767
const node = this.commitNewSync(path, S_IFREG, { mode, ...options }, new Uint8Array(), 'createFile');
68-
const file = new PreloadFile(this, path, flag, node.toStats(), new Uint8Array());
68+
const file = new LazyFile(this, path, flag, node.toStats());
6969
this.index.set(path, node);
7070
return file;
7171
}

src/mixins/async.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { _SyncFSKeys, AsyncFSMethods, Mixin } from './shared.js';
55

66
import { StoreFS } from '../backends/store/fs.js';
77
import { Errno, ErrnoError } from '../error.js';
8-
import { parseFlag, PreloadFile } from '../file.js';
8+
import { LazyFile, parseFlag } from '../file.js';
99
import { join } from '../vfs/path.js';
1010

1111
/** @internal */
@@ -36,7 +36,7 @@ export interface AsyncMixin extends Pick<FileSystem, Exclude<_SyncFSKeys, 'exist
3636
*
3737
*/
3838
export function Async<const T extends typeof FileSystem>(FS: T): Mixin<T, AsyncMixin> {
39-
abstract class AsyncFS extends FS {
39+
abstract class AsyncFS extends FS implements AsyncMixin {
4040
/**
4141
* Queue of pending asynchronous operations.
4242
*/
@@ -116,20 +116,17 @@ export function Async<const T extends typeof FileSystem>(FS: T): Mixin<T, AsyncM
116116
return this._sync.statSync(path);
117117
}
118118

119-
public createFileSync(path: string, flag: string, mode: number, options: CreationOptions): PreloadFile<this> {
119+
public createFileSync(path: string, flag: string, mode: number, options: CreationOptions): LazyFile<this> {
120120
this.checkSync(path, 'createFile');
121121
this._sync.createFileSync(path, flag, mode, options);
122122
this.queue('createFile', path, flag, mode, options);
123123
return this.openFileSync(path, flag);
124124
}
125125

126-
public openFileSync(path: string, flag: string): PreloadFile<this> {
126+
public openFileSync(path: string, flag: string): LazyFile<this> {
127127
this.checkSync(path, 'openFile');
128-
const file = this._sync.openFileSync(path, flag + '+');
129-
const stats = file.statSync();
130-
const buffer = new Uint8Array(stats.size);
131-
file.readSync(buffer);
132-
return new PreloadFile(this, path, flag, stats, buffer);
128+
const stats = this._sync.statSync(path);
129+
return new LazyFile(this, path, flag, stats);
133130
}
134131

135132
public unlinkSync(path: string): void {
@@ -172,6 +169,17 @@ export function Async<const T extends typeof FileSystem>(FS: T): Mixin<T, AsyncM
172169
return this._sync.existsSync(path);
173170
}
174171

172+
public readSync(path: string, buffer: Uint8Array, offset: number, end: number): void {
173+
this.checkSync(path, 'read');
174+
this._sync.readSync(path, buffer, offset, end);
175+
}
176+
177+
public writeSync(path: string, buffer: Uint8Array, offset: number): void {
178+
this.checkSync(path, 'write');
179+
this._sync.writeSync(path, buffer, offset);
180+
this.queue('write', path, buffer, offset);
181+
}
182+
175183
/**
176184
* @internal
177185
*/

src/mixins/mutexed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ export class _MutexedFS<T extends FileSystem> implements FileSystem {
251251
* For example, on an OverlayFS instance with an async lower
252252
* directory operations like rename and rmdir may involve multiple
253253
* requests involving both the upper and lower file systems -- they
254-
* are not executed in a single atomic step. OverlayFS uses this
254+
* are not executed in a single atomic step. OverlayFS used to use this
255255
* to avoid having to reason about the correctness of
256256
* multiple requests interleaving.
257257
*

0 commit comments

Comments
 (0)