fix: Lua factory init failure under concurrent display editing (#812)
# PR Checklist - [ ] Have you checked if it works normally in all models? *Ignore this if it doesn't use models.* - [ ] Have you checked if it works normally in all web, local, and node hosted versions? If it doesn't, have you blocked it in those versions? - [x] Have you added type definitions? # Description This PR addresses an issue where `luaFactory` fails to initialize correctly in 'editDisplay' mode under specific conditions. ## Problem Reproduction: The initialization failure can be reliably reproduced under the following conditions: 1. Disable all modules. 2. Select any chatbot with 'editDisplay' in trigger lua. 3. Select or make chat with length of 2 or more. 4. Restart app and revisit the chatbot ## Changes: 1. Guaranteed `luaFactory` initialization completes successfully during concurrent calls. 2. Added `Promise Map` to ensure only one Lua engine instance is created per mode, preventing redundant initializations. 3. Moved variable updates within mutex scope to prevent potential issues during concurrent operations. 4. Resolved merge conflicts with another PR. During the resolution: * Removed logic related to caching or updating methods for Global Vars. * **Reasoning:** Since Global Vars are stored directly in the database and are not chat-specific, I believe caching/updating these methods to be unnecessary, so I've simply made it calls exported one directly without frequently re-cache itself. * **Verification:** Re-validated that this change does not negatively impact functionality. * **Note:** Please let me know if anyone has concerns about this approach to conflict resolution.
This commit is contained in:
@@ -20,23 +20,23 @@ let LuaEditDisplayIds = new Set<string>()
|
|||||||
let LuaLowLevelIds = new Set<string>()
|
let LuaLowLevelIds = new Set<string>()
|
||||||
|
|
||||||
interface LuaEngineState {
|
interface LuaEngineState {
|
||||||
code: string;
|
code?: string;
|
||||||
engine: LuaEngine;
|
engine?: LuaEngine;
|
||||||
mutex: Mutex;
|
mutex: Mutex;
|
||||||
chat: Chat;
|
chat?: Chat;
|
||||||
setVar: (key:string, value:string) => void,
|
setVar?: (key:string, value:string) => void,
|
||||||
getVar: (key:string) => string,
|
getVar?: (key:string) => string,
|
||||||
getGlobalVar: (key:string) => any,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let LuaEngines = new Map<string, LuaEngineState>()
|
let LuaEngines = new Map<string, LuaEngineState>()
|
||||||
|
let luaFactoryPromise: Promise<void> | null = null;
|
||||||
|
let pendingEngineCreations = new Map<string, Promise<LuaEngineState>>();
|
||||||
|
|
||||||
export async function runLua(code:string, arg:{
|
export async function runLua(code:string, arg:{
|
||||||
char?:character|groupChat|simpleCharacterArgument,
|
char?:character|groupChat|simpleCharacterArgument,
|
||||||
chat?:Chat
|
chat?:Chat
|
||||||
setVar?: (key:string, value:string) => void,
|
setVar?: (key:string, value:string) => void,
|
||||||
getVar?: (key:string) => string,
|
getVar?: (key:string) => string,
|
||||||
getGlobalVar?: (key:string) => any,
|
|
||||||
lowLevelAccess?: boolean,
|
lowLevelAccess?: boolean,
|
||||||
mode?: string,
|
mode?: string,
|
||||||
data?: any
|
data?: any
|
||||||
@@ -44,40 +44,22 @@ export async function runLua(code:string, arg:{
|
|||||||
const char = arg.char ?? getCurrentCharacter()
|
const char = arg.char ?? getCurrentCharacter()
|
||||||
const setVar = arg.setVar ?? setChatVar
|
const setVar = arg.setVar ?? setChatVar
|
||||||
const getVar = arg.getVar ?? getChatVar
|
const getVar = arg.getVar ?? getChatVar
|
||||||
const getGlobalVar = arg.getGlobalVar ?? getGlobalChatVar
|
|
||||||
const mode = arg.mode ?? 'manual'
|
const mode = arg.mode ?? 'manual'
|
||||||
const data = arg.data ?? {}
|
const data = arg.data ?? {}
|
||||||
let chat = arg.chat ?? getCurrentChat()
|
let chat = arg.chat ?? getCurrentChat()
|
||||||
let stopSending = false
|
let stopSending = false
|
||||||
let lowLevelAccess = arg.lowLevelAccess ?? false
|
let lowLevelAccess = arg.lowLevelAccess ?? false
|
||||||
|
|
||||||
if(!luaFactory){
|
await ensureLuaFactory()
|
||||||
await makeLuaFactory()
|
let luaEngineState = await getOrCreateEngineState(mode);
|
||||||
}
|
|
||||||
let luaEngineState = LuaEngines.get(mode)
|
return await luaEngineState.mutex.runExclusive(async () => {
|
||||||
let wasEmpty = false
|
|
||||||
if (!luaEngineState) {
|
|
||||||
luaEngineState = {
|
|
||||||
code,
|
|
||||||
engine: await luaFactory.createEngine({injectObjects: true}),
|
|
||||||
mutex: new Mutex(),
|
|
||||||
chat,
|
|
||||||
setVar,
|
|
||||||
getVar,
|
|
||||||
getGlobalVar
|
|
||||||
}
|
|
||||||
LuaEngines.set(mode, luaEngineState)
|
|
||||||
wasEmpty = true
|
|
||||||
} else {
|
|
||||||
luaEngineState.chat = chat
|
luaEngineState.chat = chat
|
||||||
luaEngineState.setVar = setVar
|
luaEngineState.setVar = setVar
|
||||||
luaEngineState.getVar = getVar
|
luaEngineState.getVar = getVar
|
||||||
luaEngineState.getGlobalVar = getGlobalVar
|
if (code !== luaEngineState.code) {
|
||||||
}
|
luaEngineState.engine?.global.close()
|
||||||
return await luaEngineState.mutex.runExclusive(async () => {
|
luaEngineState.code = code
|
||||||
if (wasEmpty || code !== luaEngineState.code) {
|
|
||||||
if (!wasEmpty)
|
|
||||||
luaEngineState.engine.global.close()
|
|
||||||
luaEngineState.engine = await luaFactory.createEngine({injectObjects: true})
|
luaEngineState.engine = await luaFactory.createEngine({injectObjects: true})
|
||||||
const luaEngine = luaEngineState.engine
|
const luaEngine = luaEngineState.engine
|
||||||
luaEngine.global.set('setChatVar', (id:string,key:string, value:string) => {
|
luaEngine.global.set('setChatVar', (id:string,key:string, value:string) => {
|
||||||
@@ -96,7 +78,7 @@ export async function runLua(code:string, arg:{
|
|||||||
if(!LuaSafeIds.has(id) && !LuaEditDisplayIds.has(id)){
|
if(!LuaSafeIds.has(id) && !LuaEditDisplayIds.has(id)){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return luaEngineState.getGlobalVar(key)
|
return getGlobalChatVar(key)
|
||||||
})
|
})
|
||||||
luaEngine.global.set('stopChat', (id:string) => {
|
luaEngine.global.set('stopChat', (id:string) => {
|
||||||
if(!LuaSafeIds.has(id)){
|
if(!LuaSafeIds.has(id)){
|
||||||
@@ -564,7 +546,7 @@ export async function runLua(code:string, arg:{
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function makeLuaFactory(){
|
async function makeLuaFactory(){
|
||||||
luaFactory = new LuaFactory()
|
const _luaFactory = new LuaFactory()
|
||||||
async function mountFile(name:string){
|
async function mountFile(name:string){
|
||||||
let code = ''
|
let code = ''
|
||||||
for(let i = 0; i < 3; i++){
|
for(let i = 0; i < 3; i++){
|
||||||
@@ -576,10 +558,60 @@ async function makeLuaFactory(){
|
|||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
await luaFactory.mountFile(name,code)
|
await _luaFactory.mountFile(name,code)
|
||||||
}
|
}
|
||||||
|
|
||||||
await mountFile('json.lua')
|
await mountFile('json.lua')
|
||||||
|
luaFactory = _luaFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureLuaFactory() {
|
||||||
|
if (luaFactory) return;
|
||||||
|
|
||||||
|
if (luaFactoryPromise) {
|
||||||
|
try {
|
||||||
|
await luaFactoryPromise;
|
||||||
|
} catch (error) {
|
||||||
|
luaFactoryPromise = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
luaFactoryPromise = makeLuaFactory();
|
||||||
|
await luaFactoryPromise;
|
||||||
|
} finally {
|
||||||
|
luaFactoryPromise = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getOrCreateEngineState(
|
||||||
|
mode: string,
|
||||||
|
): Promise<LuaEngineState> {
|
||||||
|
let engineState = LuaEngines.get(mode);
|
||||||
|
if (engineState) {
|
||||||
|
return engineState;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pendingCreation = pendingEngineCreations.get(mode);
|
||||||
|
if (pendingCreation) {
|
||||||
|
return pendingCreation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const creationPromise = (async () => {
|
||||||
|
const engineState: LuaEngineState = {
|
||||||
|
mutex: new Mutex(),
|
||||||
|
};
|
||||||
|
LuaEngines.set(mode, engineState);
|
||||||
|
|
||||||
|
pendingEngineCreations.delete(mode);
|
||||||
|
|
||||||
|
return engineState;
|
||||||
|
})();
|
||||||
|
|
||||||
|
pendingEngineCreations.set(mode, creationPromise);
|
||||||
|
|
||||||
|
return creationPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function luaCodeWarper(code:string){
|
function luaCodeWarper(code:string){
|
||||||
@@ -687,7 +719,6 @@ callListenMain = async(function(type, id, value)
|
|||||||
if type == 'editDisplay' then
|
if type == 'editDisplay' then
|
||||||
for _, func in ipairs(editDisplayFuncs) do
|
for _, func in ipairs(editDisplayFuncs) do
|
||||||
realValue = func(id, realValue)
|
realValue = func(id, realValue)
|
||||||
print(realValue)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user