fix: prevent lua engine getting killed and pool engines

This commit is contained in:
Sunho Kim
2024-07-15 21:11:21 -07:00
parent 7a542c14d6
commit cb9514c508
2 changed files with 493 additions and 384 deletions

88
src/ts/mutex.ts Normal file
View File

@@ -0,0 +1,88 @@
/**
* A lock for synchronizing async operations.
* Use this to protect a critical section
* from getting modified by multiple async operations
* at the same time.
*/
export class Mutex {
/**
* When multiple operations attempt to acquire the lock,
* this queue remembers the order of operations.
*/
private _queue: {
resolve: (release: ReleaseFunction) => void
}[] = []
private _isLocked = false
/**
* Wait until the lock is acquired.
* @returns A function that releases the acquired lock.
*/
acquire() {
return new Promise<ReleaseFunction>((resolve) => {
this._queue.push({resolve})
this._dispatch()
});
}
/**
* Enqueue a function to be run serially.
*
* This ensures no other functions will start running
* until `callback` finishes running.
* @param callback Function to be run exclusively.
* @returns The return value of `callback`.
*/
async runExclusive<T>(callback: () => Promise<T>) {
const release = await this.acquire()
try {
return await callback()
} finally {
release()
}
}
/**
* Check the availability of the resource
* and provide access to the next operation in the queue.
*
* _dispatch is called whenever availability changes,
* such as after lock acquire request or lock release.
*/
private _dispatch() {
if (this._isLocked) {
// The resource is still locked.
// Wait until next time.
return
}
const nextEntry = this._queue.shift()
if (!nextEntry) {
// There is nothing in the queue.
// Do nothing until next dispatch.
return
}
// The resource is available.
this._isLocked = true
// and give access to the next operation
// in the queue.
nextEntry.resolve(this._buildRelease())
}
/**
* Build a release function for each operation
* so that it can release the lock after
* the operation is complete.
*/
private _buildRelease(): ReleaseFunction {
return () => {
// Each release function make
// the resource available again
this._isLocked = false
// and call dispatch.
this._dispatch()
}
}
}
type ReleaseFunction = () => void

View File

@@ -11,14 +11,21 @@ import type { OpenAIChat } from ".";
import { requestChatData } from "./request";
import { v4 } from "uuid";
import { getModuleTriggers } from "./modules";
import { Mutex } from "../mutex";
let luaFactory:LuaFactory
let luaEngine:LuaEngine
let lastCode = ''
let LuaSafeIds = new Set<string>()
let LuaEditDisplayIds = new Set<string>()
let LuaLowLevelIds = new Set<string>()
interface LuaEngineState {
code: string;
engine: LuaEngine;
mutex: Mutex;
}
let LuaEngines = new Map<string, LuaEngineState>()
export async function runLua(code:string, arg:{
char?:character|groupChat|simpleCharacterArgument,
chat?:Chat
@@ -37,14 +44,26 @@ export async function runLua(code:string, arg:{
let stopSending = false
let lowLevelAccess = arg.lowLevelAccess ?? false
if(!luaEngine || lastCode !== code){
if(luaEngine){
luaEngine.global.close()
}
if(!luaFactory){
makeLuaFactory()
await makeLuaFactory()
}
luaEngine = await luaFactory.createEngine({injectObjects: true})
let luaEngineState = LuaEngines.get(mode)
let wasEmpty = false
if (!luaEngineState) {
luaEngineState = {
code,
engine: await luaFactory.createEngine({injectObjects: true}),
mutex: new Mutex()
}
LuaEngines.set(mode, luaEngineState)
wasEmpty = true
}
return await luaEngineState.mutex.runExclusive(async () => {
if (wasEmpty || code !== luaEngineState.code) {
if (!wasEmpty)
luaEngineState.engine.global.close()
luaEngineState.engine = await luaFactory.createEngine({injectObjects: true})
const luaEngine = luaEngineState.engine
luaEngine.global.set('setChatVar', (id:string,key:string, value:string) => {
if(!LuaSafeIds.has(id) && !LuaEditDisplayIds.has(id)){
return
@@ -380,7 +399,7 @@ export async function runLua(code:string, arg:{
})
await luaEngine.doString(luaCodeWarper(code))
lastCode = code
luaEngineState.code = code
}
let accessKey = v4()
if(mode === 'editDisplay'){
@@ -393,6 +412,7 @@ export async function runLua(code:string, arg:{
}
}
let res:any
const luaEngine = luaEngineState.engine
try {
switch(mode){
case 'input':{
@@ -443,6 +463,7 @@ export async function runLua(code:string, arg:{
return {
stopSending, chat, res
}
})
}
async function makeLuaFactory(){