Class EventRolloutDifferTest

java.lang.Object
scripting.EventRolloutDifferTest

public class EventRolloutDifferTest extends Object
Track B(Nashorn → GraalJS)— C7 rollout⑤:event 類別黃金參考 differ(最高風險、最後一棒)。

對應計畫 C7 的出口條件「先過併發 gate + 鎖序 assert + Tier2/3」。event 與 npc/quest/portal/reactor 的關鍵差異:

  • 引擎是行程級、開機即建並 eval:不同於 npc/quest/reactor 的 lazy per-client、portal 的 per-name SAM —— EventScriptManager 在頻道啟動時(每頻道一具)對每個 scripts/event/<name>.js 立即 getInvocable(eval 頂層)+ init(),並把引擎 iv 共用給該事件的所有 instance。 該 iv 被 Netty 封包緒 + EventTimer 池多執行緒呼叫。
  • 唯一需要序列化的類別:C1 已對 event 的 ivScriptEngines.serialized(Invocable)(per-engine ReentrantLock, 橫跨整個 invokeFunction)。Nashorn 路徑回傳裸 iv(容忍並發)→ 並存期行為不變。
  • 鎖序不變式(C7 新增 runtime assert):腳本從引擎鎖內回呼 Java(如 eim.registerPlayerEventInstanceManager 的結構鎖 wL)→ 既定鎖序「engine-lock → structural-lock」。任何「持結構鎖時 invoke 引擎」的反向路徑都會死結。對抗審查(workflow,3 lens × refute-by-default)揪出一條真實違規: EventInstanceManager.playerDisconnected 在 wL 鎖內呼叫 removePlayerplayerExit invoke(KerningPQ/LudiPQ/ZakumPQ 等回傳負值、隊長離線即觸發)——C7 已修(把 invoke 移出鎖區), 並加 ScriptEngines.trackStructuralLock(Lock)/assertLockOrder 守護防回歸。本檔下半即驗該守護。

同一 JVM 並存 nashorn-core(黃金參考)與 GraalJS(候選),由 ScriptEngines.fresh() 建立(C8 後正式環境一律 GraalJS;黃金參考見 GoldenReference.nashorn())。不需 DB / wz / 網路 / Timer。

(語料偵察 + 鎖序審查由 workflow 完成:全 102 檔零真 E4X / Java.type / importClass / JavaAdapter;interop 僅 7 檔 load('nashorn:mozilla_compat.js') + importPackage ——兩引擎在 js.nashorn-compat=true 下走相同路徑(C0 的 GraalJsSeamTest 已證 mozilla_compat 在 GraalJS 可解析、PVP.js 在其解析清單),Tier3 全檔 eval parity 為最終仲裁。零頂層 em/eim 參照 (event 引擎 eval 在綁定 em 之前;伺服器今日於 Nashorn 開機即證)。parity 邊角:2xEvent.em.DoubleRateEvent /BossQuest.saveBossQuestPoints/lolcastle.saveWinner/enterDisguise3.getMapInstance(int,int) 皆為不存在方法,僅 invoke 時兩引擎同樣拋,Tier3 解析/綁定不受影響。)

  • Constructor Details

    • EventRolloutDifferTest

      public EventRolloutDifferTest()
  • Method Details

    • corpusParseAndEntryBindingAreEngineEquivalent

      public void corpusParseAndEntryBindingAreEngineEquivalent() throws Exception
      scripts/event/*.js 在 nashorn-core 與 GraalJS 必須等價(非「全部成功」):
      • 解析分歧(XOR)= 零:不得有任何腳本「一引擎解析成功、另一引擎失敗」。這條同時是 mozilla_compat 的最終仲裁 —— 7 個 load('nashorn:mozilla_compat.js') 檔的頂層 load 在 eval (見下方綁定步驟)時若 GraalJS 不解析,該檔函式不會 hoist → typeof init 在兩引擎分歧 → 本測試失敗。 C0–C6 已證兩引擎皆解析 mozilla_compat,故預期零分歧。
      • 進入函式綁定逐檔等價:eval(容忍頂層丟)後,ENTRY_FNS 每個 typeof fn 兩引擎須一致 —— 驗 event 線上實際走的 invokeFunction(...) 路徑(init/setup/playerEntry/monsterValue/…)。

      「兩引擎皆解析失敗」≠ 分歧(記到 build/event-parse-broken-both.txt,不擋 rollout;如 BossQuest.js 既有語法錯)。注意自 2026-06-04 起,事件腳本改由 EventScriptManager.discoverEventScripts() 掃描整個 scripts/event/ 目錄載入(原 channel.events 清單已移除),故解析失敗的腳本由 EventScriptManager 載入時優雅略過(getInvocable→null),而非靠載入清單排除。

      Throws:
      Exception
    • tier2TranscriptsAndReturnsMatchAcrossEngines

      public void tier2TranscriptsAndReturnsMatchAcrossEngines() throws Exception
      Throws:
      Exception
    • eventOverloadAndCoercionFixtureMatchesAcrossEngines

      public void eventOverloadAndCoercionFixtureMatchesAcrossEngines() throws Exception
      合成微 fixture(不依賴部署內容):把 event 最具差異風險的「多載選擇 + 數值強制 + 數字→String 強制」 集中於一處隔離直驗,並對確切轉錄下斷言。涵蓋:
      • em.setProperty(String,String)eim.setProperty(String,String) —— String 引數身份。
      • 數字 → String 強制:em.setProperty("num", 0) / eim.setProperty("n", 7) —— 對應 BossQuest 系列(13 處 setProperty(_,0))。Nashorn 把數字 0/7 coerce 成 "0"/"7"; 本 fixture 以 parity 為硬閘(gold==cand)抓任何 GraalJS 分歧,並以 gold 為黃金釘住確切轉錄。
      • eim.broadcastPlayerMsg(int,String) 多載(含繁中 String 身份)。
      • eim.startEventTimer(long) —— JS 數字 → long。
      • 數字回傳(return 1)。
      Throws:
      Exception
    • sharedGraalEventContextIsUnsafeUnderConcurrentEntry

      public void sharedGraalEventContextIsUnsafeUnderConcurrentEntry() throws Exception
      (1) 重現通用危害:單一共用 GraalJS Context 被兩執行緒同時 invokeFunction("setup")
      Throws:
      Exception
    • serializedEventEngineSerializesConcurrentEntry

      public void serializedEventEngineSerializesConcurrentEntry() throws Exception
      (2) 線上機制:ScriptEngines.serialized(Invocable)(EventScriptManager 對 iv 所套)→ 無例外、 maxInFlightinvalid input: '<'=1、跨呼叫狀態保留(證持久 context,非 per-invocation 隔離 → PVP/PQ 計數不被清)。
      Throws:
      Exception
    • lockOrderAssertFiresWhenInvokingUnderStructuralLock

      public void lockOrderAssertFiresWhenInvokingUnderStructuralLock() throws Exception
      (3) C7 鎖序守護:持 EventInstanceManager 結構鎖(以 trackStructuralLock 包覆)時 invoke serialized() 引擎 → 立即拋 IllegalStateException;鎖外 invoke 正常;釋鎖後恢復(計數正確遞減)。
      Throws:
      Exception
    • nashornPathNeverFiresLockOrderAssert

      public void nashornPathNeverFiresLockOrderAssert() throws Exception
      (4) Nashorn 路徑:serialized() 回傳裸 iv(無代理)→ 即使持結構鎖也觸發守護(並存期行為不變)。
      Throws:
      Exception