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: 4 additions & 1 deletion packages/opencode/src/project/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import { State } from "./state"
import { iife } from "@/util/iife"
import { GlobalBus } from "@/bus/global"
import { Filesystem } from "@/util/filesystem"
import { createLruCache } from "@/util/cache"

interface Context {
directory: string
worktree: string
project: Project.Info
}
const context = Context.create<Context>("instance")
const cache = new Map<string, Promise<Context>>()
const cache = createLruCache<string, Promise<Context>>({
maxEntries: 20,
})

export const Instance = {
async provide<R>(input: { directory: string; init?: () => Promise<any>; fn: () => R }): Promise<R> {
Expand Down
86 changes: 86 additions & 0 deletions packages/opencode/src/util/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* LRU cache with max entries limit for preventing memory leaks
*/

export type LruCacheOpts = {
maxEntries?: number
onEvict?: (key: any, value: any) => void
}

type LruCacheEntry<V> = {
value: V
lastAccess: number
}

export function createLruCache<K = any, V = any>(opts: LruCacheOpts = {}) {
const { maxEntries = Infinity, onEvict } = opts
const cache = new Map<K, LruCacheEntry<V>>()

function evictOne() {
let oldestKey: K | null = null
let oldestAccess = Infinity

for (const [key, entry] of cache) {
if (entry.lastAccess < oldestAccess) {
oldestAccess = entry.lastAccess
oldestKey = key
}
}

if (oldestKey !== null) {
delete_(oldestKey)
}
}

function delete_(key: K): boolean {
const entry = cache.get(key)
if (!entry) return false
onEvict?.(key, entry.value)
return cache.delete(key)
}

return {
get(key: K): V | undefined {
const entry = cache.get(key)
if (!entry) return undefined
entry.lastAccess = Date.now()
return entry.value
},

set(key: K, value: V): void {
if (cache.size >= maxEntries && !cache.has(key)) {
evictOne()
}
cache.set(key, { value, lastAccess: Date.now() })
},

has(key: K): boolean {
return cache.has(key)
},

delete(key: K): boolean {
return delete_(key)
},

clear(): void {
for (const [key, entry] of cache) {
onEvict?.(key, entry.value)
}
cache.clear()
},

get size() {
return cache.size
},

*[Symbol.iterator](): IterableIterator<[K, V]> {
for (const [key, entry] of cache) {
yield [key, entry.value]
}
},

entries(): IterableIterator<[K, V]> {
return this[Symbol.iterator]()
},
}
}