fix: prevent lua engine getting killed and pool engines
This commit is contained in:
88
src/ts/mutex.ts
Normal file
88
src/ts/mutex.ts
Normal 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
|
||||
@@ -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(){
|
||||
|
||||
Reference in New Issue
Block a user