Skip to content
This repository was archived by the owner on Oct 22, 2025. It is now read-only.

Commit aea38ce

Browse files
committed
refactor: implement lazy loading and deterministic actor IDs (#1020)
1 parent 5c8c4f5 commit aea38ce

File tree

4 files changed

+74
-110
lines changed

4 files changed

+74
-110
lines changed

packages/core/src/drivers/file-system/actor.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,10 @@ export class FileSystemActorDriver implements ActorDriver {
2525
}
2626

2727
async readPersistedData(actorId: string): Promise<Uint8Array | undefined> {
28-
console.log("reading data", this.#state.readPersistedData(actorId));
2928
return this.#state.readPersistedData(actorId);
3029
}
3130

3231
async writePersistedData(actorId: string, data: Uint8Array): Promise<void> {
33-
console.log("writing data", data);
3432
this.#state.writePersistedData(actorId, data);
3533

3634
// Save state to disk

packages/core/src/drivers/file-system/global-state.ts

Lines changed: 43 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -38,48 +38,23 @@ export class FileSystemGlobalState {
3838
// Ensure storage directories exist synchronously during initialization
3939
ensureDirectoryExistsSync(getActorsDir(this.#storagePath));
4040

41-
// Load all actors into cache synchronously
42-
this.#loadAllActorsIntoCache();
43-
44-
logger().info("file system loaded", {
45-
dir: this.#storagePath,
46-
actorCount: this.#stateCache.size,
47-
});
48-
}
49-
50-
/**
51-
* Load all actors into the state cache from the file system
52-
* Only called once during initialization
53-
*/
54-
#loadAllActorsIntoCache(): void {
5541
const actorsDir = getActorsDir(this.#storagePath);
56-
42+
let actorCount = 0;
43+
5744
try {
58-
// HACK: Use synchronous filesystem operations for initialization
5945
const actorIds = fsSync.readdirSync(actorsDir);
60-
61-
for (const actorId of actorIds) {
62-
const stateFilePath = getActorDataPath(this.#storagePath, actorId);
63-
64-
if (fsSync.existsSync(stateFilePath)) {
65-
try {
66-
const stateData = fsSync.readFileSync(stateFilePath);
67-
const state = cbor.decode(stateData) as ActorState;
68-
69-
this.#stateCache.set(actorId, state);
70-
} catch (error) {
71-
logger().error(
72-
"failed to read actor state during cache initialization",
73-
{ actorId, error },
74-
);
75-
}
76-
}
77-
}
46+
actorCount = actorIds.length;
7847
} catch (error) {
79-
logger().error("failed to load actors into cache", { error });
48+
logger().error("failed to count actors", { error });
8049
}
50+
51+
logger().info("file system loaded", {
52+
dir: this.#storagePath,
53+
actorCount,
54+
});
8155
}
8256

57+
8358
/**
8459
* Get the current storage directory path
8560
*/
@@ -88,16 +63,34 @@ export class FileSystemGlobalState {
8863
}
8964

9065
/**
91-
* Load actor state from cache
66+
* Load actor state from cache or disk (lazy loading)
9267
*/
9368
loadActorState(actorId: string): ActorState {
94-
this.ensureActorExists(actorId);
95-
96-
// Get actor state from cache
69+
// Check if already in cache
9770
const cachedActor = this.#stateCache.get(actorId);
98-
invariant(cachedActor, `actor state should exist in cache for ${actorId}`);
71+
if (cachedActor) {
72+
return cachedActor;
73+
}
9974

100-
return cachedActor;
75+
// Try to load from disk
76+
const stateFilePath = getActorDataPath(this.#storagePath, actorId);
77+
78+
if (!fsSync.existsSync(stateFilePath)) {
79+
throw new Error(`Actor does not exist for ID: ${actorId}`);
80+
}
81+
82+
try {
83+
const stateData = fsSync.readFileSync(stateFilePath);
84+
const state = cbor.decode(stateData) as ActorState;
85+
86+
// Cache the loaded state
87+
this.#stateCache.set(actorId, state);
88+
89+
return state;
90+
} catch (error) {
91+
logger().error("failed to load actor state", { actorId, error });
92+
throw new Error(`Failed to load actor state: ${error}`);
93+
}
10194
}
10295

10396
/**
@@ -135,29 +128,27 @@ export class FileSystemGlobalState {
135128
// Write data
136129
const serializedState = cbor.encode(state);
137130
await fs.writeFile(dataPath, serializedState);
138-
console.log("saving state", dataPath);
139131
} catch (error) {
140132
logger().error("failed to save actor state", { actorId, error });
141133
throw new Error(`Failed to save actor state: ${error}`);
142134
}
143135
}
144136

145137
/**
146-
* Check if a actor exists in the cache
138+
* Check if a actor exists in cache or on disk
147139
*/
148140
hasActor(actorId: string): boolean {
149-
return this.#stateCache.has(actorId);
150-
}
151-
152-
/**
153-
* Ensure a actor exists, throwing if it doesn't
154-
*/
155-
ensureActorExists(actorId: string): void {
156-
if (!this.hasActor(actorId)) {
157-
throw new Error(`Actor does not exist for ID: ${actorId}`);
141+
// Check cache first
142+
if (this.#stateCache.has(actorId)) {
143+
return true;
158144
}
145+
146+
// Check if file exists on disk
147+
const stateFilePath = getActorDataPath(this.#storagePath, actorId);
148+
return fsSync.existsSync(stateFilePath);
159149
}
160150

151+
161152
/**
162153
* Create a actor
163154
*/
@@ -186,49 +177,4 @@ export class FileSystemGlobalState {
186177
// Save to disk
187178
await this.saveActorState(actorId);
188179
}
189-
190-
/**
191-
* Find actor by name and key
192-
*/
193-
findActorByNameAndKey(name: string, key: ActorKey): ActorState | undefined {
194-
// NOTE: This is a slow implementation that checks each actor individually.
195-
// This can be optimized with an index in the future.
196-
197-
return this.findActor((actor) => {
198-
if (actor.name !== name) return false;
199-
200-
// If actor doesn't have a key, it's not a match
201-
if (!actor.key || actor.key.length !== key.length) {
202-
return false;
203-
}
204-
205-
// Check if all elements in key are in actor.key
206-
for (let i = 0; i < key.length; i++) {
207-
if (key[i] !== actor.key[i]) {
208-
return false;
209-
}
210-
}
211-
return true;
212-
});
213-
}
214-
215-
/**
216-
* Find actor by filter function
217-
*/
218-
findActor(filter: (actor: ActorState) => boolean): ActorState | undefined {
219-
for (const actor of this.#stateCache.values()) {
220-
if (filter(actor)) {
221-
return actor;
222-
}
223-
}
224-
return undefined;
225-
}
226-
227-
/**
228-
* Get all actors from the cache
229-
*/
230-
getAllActors(): ActorState[] {
231-
// Return all actors from the cache
232-
return Array.from(this.#stateCache.values());
233-
}
234180
}

packages/core/src/drivers/file-system/manager.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as crypto from "node:crypto";
21
import type {
32
GetOrCreateWithKeyInput,
43
GetForIdInput,
@@ -12,6 +11,7 @@ import { logger } from "./log";
1211
import type { FileSystemGlobalState } from "./global-state";
1312
import { ActorState } from "./global-state";
1413
import type { Registry } from "@/registry/mod";
14+
import { generateActorId } from "./utils";
1515

1616
export class FileSystemManagerDriver implements ManagerDriver {
1717
#state: FileSystemGlobalState;
@@ -52,14 +52,15 @@ export class FileSystemManagerDriver implements ManagerDriver {
5252
name,
5353
key,
5454
}: GetWithKeyInput): Promise<ActorOutput | undefined> {
55-
// Search through all actors to find a match
56-
const actor = this.#state.findActorByNameAndKey(name, key);
57-
58-
if (actor) {
55+
// Generate the deterministic actor ID
56+
const actorId = generateActorId(name, key);
57+
58+
// Check if actor exists
59+
if (this.#state.hasActor(actorId)) {
5960
return {
60-
actorId: actor.id,
61+
actorId,
6162
name,
62-
key: actor.key,
63+
key,
6364
};
6465
}
6566

@@ -79,13 +80,14 @@ export class FileSystemManagerDriver implements ManagerDriver {
7980
}
8081

8182
async createActor({ name, key, input }: CreateInput): Promise<ActorOutput> {
82-
// Check if actor with the same name and key already exists
83-
const existingActor = await this.getWithKey({ name, key });
84-
if (existingActor) {
83+
// Generate the deterministic actor ID
84+
const actorId = generateActorId(name, key);
85+
86+
// Check if actor already exists
87+
if (this.#state.hasActor(actorId)) {
8588
throw new ActorAlreadyExists(name, key);
8689
}
8790

88-
const actorId = crypto.randomUUID();
8991
await this.#state.createActor(actorId, name, key, input);
9092

9193
// Notify inspector about actor changes

packages/core/src/drivers/file-system/utils.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@ import * as fsSync from "node:fs";
33
import * as path from "node:path";
44
import * as crypto from "node:crypto";
55
import * as os from "node:os";
6+
import type { ActorKey } from "@/actor/mod";
7+
8+
/**
9+
* Generate a deterministic actor ID from name and key
10+
*/
11+
export function generateActorId(name: string, key: ActorKey): string {
12+
// Generate deterministic key string
13+
const jsonString = JSON.stringify([name, key]);
14+
15+
// Hash to ensure safe file system names
16+
const hash = crypto
17+
.createHash("sha256")
18+
.update(jsonString)
19+
.digest("hex")
20+
.substring(0, 16);
21+
22+
return hash;
23+
}
624

725
/**
826
* Create a hash for a path, normalizing it first

0 commit comments

Comments
 (0)