TVTower icon indicating copy to clipboard operation
TVTower copied to clipboard

Threadsicherheit

Open GWRon opened this issue 3 years ago • 23 comments

Ich habe ja vorhin per Telefon was angesprochen ... und zwar ob die Mutexe "global" oder "local" sind...

in game.ai.base.bmx:

	?threaded
	Field _callLuaFunctionMutex:TMutex = CreateMutex() {nosave}

ist der Mutex ja "von BlitzMax NACH Lua", und soll vermeiden, dass mehrere threads (also multithreaded events oder sowas) gleichzeitig in EIN lua-Skript reinrufen koennen.

Wichtiger ist aber ja nun, dass die 4 KIs nicht zeitgleich auf "not thread-safe"-Funktionen zugreifen - und genau hier fehlts noch. Die Kommunikation LU->Blitzmax ist derzeit noch nicht mit einem Mutex abgesichert ... und das waere "per se" auch nicht noetig, wenn alle aufrufbaren Funktionen "threadsafe" waeren.

Man koennte nun erstmal in base.util.luaengine.bmx bei "_invoke()" einen LockMutex(XY) am Anfang und ein UnlockMutex(XY) am Ende ranpacken ... um zumindest das "von aussen" reingraetschen zu unterbinden. Besser waere es aber wohl, generell mehr Threadsicherheit herbeizufuehren ... auch wenn momentan nur die KI in einem anderen Thread arbeitet (die Musikengine auch, aber die ist glaube schon abgesichert)

GWRon avatar Dec 21 '21 12:12 GWRon

Das Problem was wir dann aber immer noch haben ist, dass der "main thread" (die Spiellogik) ja nichts davon weiss, sie wuerde munter parallel in die Funktionen reinrufen Das Problem waere also nur ...verringert.

GWRon avatar Dec 21 '21 12:12 GWRon

Wieviel bleibt denn vom Multi-Threading noch übrig, wenn in beide Richtungen synchronisiert wird?

nittka avatar Dec 21 '21 13:12 nittka

das ist halt die Frage.

Ich hatte gerade mit davecamp einen Discordchat und dort folgende Idee aufgebracht:

base.util.luaengine.bmx _Invoke wird so aufgebaut, dass es temporaer nach Metadaten checkt: "THREADSAFED". Hat eine Methode diese Metadaten NICHT, loggt es das entsprechend.

Alle Log-Eintraege nach ein paar Tagen Testspiel sollten uns aufzeigen, welche Methoden "genutzt" wurden ... und ob die schon angepasst sind. Diese muessten dann eigene Mutexe bekommen und sich somit "schuetzen".

Spaeter koennte der Metadatencheck dann manuell zu aktivieren sein.

Ist vielleicht nicht perfekt, sollte aber einen kompletten Umbau ersparen.

GWRon avatar Dec 21 '21 13:12 GWRon

Woher kommen denn die Metadaten? Oder heißt das, wir loggen einfach verwendete Methoden und passen die nach und nach an und schreiben die Metadaten von Hand dran? So was ähnliches habe ich gerade schon in naiver Form gemacht: log bei Start und Ende einer Invoke-Methode. Über große Teile sieht das sehr aufgeräumt aus - immer start und dann Ende. Und dann geht es plötzlich über eine Weile quer durcheinander.

locked OnTick
...
_Invoke() Func: TLuaFunctions.GetRoomBlockedTimeString
invocation done TLuaFunctions.GetRoomBlockedTimeString
_Invoke() Func: TLuaFunctions.TimeToMinutes
invocation done TLuaFunctions.TimeToMinutes
_Invoke() Func: TPlayer.SetAIStringData
invocation done TPlayer.SetAIStringData
locked_ InvokeO(n) TFunc: iTck
Player.SetAIStringData
invocation done TPlayer.SetAIStringData
_Invoke() Func: TLuaFunctions.GetDayMinute
_Invoke() Funcin:v oTcPlaation dyoenr.SetAeIStrin gTLuaFuDnata
ctions.GetDayMinute
invocation done TPlayer.SetAIStringData
_Invoke() Func: _TInvokPel() Funacyer.Se:t TALuaFunIcStringtDiaotan
invocation done TPlayesr.IsRo.oSmBelockedtAIStri
nginvoDcaattaion

Die Ausgaben sind print-Ausgaben von mir. Auffällig ist, dass das locked onTick nicht abgeschlossen war, bevor das nächste locked kam. Gerade läuft ein Dauerspiel auf dem Masterstand, der bislang nicht abgestürzt ist (Tag 27). In meinen Anpassungen des KI-Codes habe ich aber nichts finden können, was "seltsam" aussieht. Die Umbauten des Engine-Codes sollten ja auch nur dazu geführt haben, dass Paramter definiert werden müssen.

Ich werde die Ausgaben nochmal sowohl im Master als auch im Branch machen - im Master um zu sehen, ob es dort auch schon durcheinander geht.

nittka avatar Dec 21 '21 14:12 nittka

Ja man koennte alle Methoden loggen ... oder aber sowas:

(TProgrammeLicence)

	'override
	Method GetTitle:string() {_exposeToLua, THREADSAFE}
		if not title and GetData() then return GetData().GetTitle()

		return Super.GetTitle()
	End Method

Und in luaengine _invoke():

		If not callable.HasMetaData("THREADSAFE") Then TLogger.Log("THREADSAFE", "Calling method or function " + funcOrMeth.name()+ " not marked threadsafe yet.", LOG_DEBUG)
		Local t:Object
		?Not bmxng
		If func Then t = func.Invoke(obj, args)
		?bmxng
		If func Then t = func.Invoke(args)
		?

GWRon avatar Dec 21 '21 14:12 GWRon

Oder man markiert die Methoden, die Sachen "NICHT veraendern" (nur "read" von einfachen Zahlenwerten oder so und kann die somit "whitelisten" ("ungefaehrlich").

Die brauchen dann keinen Mutex - sind also per se "threadsafe", nur dass halt die Bedeutung schaerfer abgegrenzt ist.

GWRon avatar Dec 21 '21 14:12 GWRon

Getter die änderbare Objekte zurückgeben könnten zwar thread-safe sein, nicht aber die Änderungen, die dann an diesen Objekten gemacht werden. Da würde dann auch ein Mutex nicht wirklich helfen, oder?

nittka avatar Dec 21 '21 14:12 nittka

Richtig.

'not threadsafe
Method GetSum:int()
  Local sum:Int
  For local i:int = eachin self.arr
     sum :+ i
  Next
  return sum
End Method

'not threadsafe
Method GetSum:int()
  Local arrCopy:Int[] = self.arr[..] 'copy is not threadsafe
  Local sum:Int
  For local i:int = eachin arrCopy
     sum :+ i
  Next
  return sum
End Method

'threadsafe
Method GetSum:int()
  Return self.precalculatedSum
End Method

GWRon avatar Dec 21 '21 14:12 GWRon

@davecamp suggested to think about message queues we could use to pass around requests and responses - fifo approach. This way only the queue handling should need mutexes.

GWRon avatar Dec 21 '21 14:12 GWRon

The very same davecamp/col just explained to me that you can LockMutex(mutex) multiple times in the same thread without requiring UnLockMutex(mutex) inbetween - as long as the "unlocks" are called as often as the "locks".

This means if we protect eg "Field myArr:object[]` used inside of multiple interchained methods (Get() calling GetX() and both use "myArr" somehow) then we only need a "myArrMutex" not two mutexes (getMutex and getXMutex). This should keep amount of really required mutexes lower than I first feared.

Edit: adding an example (untested, just to show what I was talking about):

Type TBase
  Field number:int
  Field protectMe:Object[]
  Field protectMeMutex:TMutex = CreateMutex()

  Method Get:Int()
    Local result:Int
    LockMutex(protectMeMutex)
    For local o:TBase = EachIn protectMe
      result :+ GetX() + o.number
    Next
    UnlockMutex(protectMeMutex)
    Return result
  End Method 

  Method GetX:Int()
     Return Millisecs()
  End Method
End Type

Type TExt Extends TBase
  Method GetX:Int() override
     Local result:Int

     'we do not know if we there was a mutex - was Get() called or raw GetX() here?
     LockMutex(protectMeMutex)
     For local o:TBase = EachIn protectMe
       result :+ o.number
     Next
     UnlockMutex(protectMeMutex)
     Return result
   End Method
End Type

As you see - this allows to use a mutex "without" taking care of other methods in need to also lock the mutex. We could call myExt.GetX() ... but also myExt.Get()

GWRon avatar Dec 21 '21 15:12 GWRon

Means we should make playercollections and schedule threadsafe ... also checking the TBroadcast / Audience predictor (albeit it should have improved already).

GWRon avatar Dec 21 '21 16:12 GWRon

Ich bekomme den Segmentation Fault schon relativ früh in der Kette der Änderungen. Deshalb habe ich jetzt doch mal die LuaEngine-Änderungen zusammen mit den Lua-Aufrufänderungen (zusätzliche Parameter) separiert und keine KI-Performanceänderungen gemacht. Wenn der Segmentation Fault dann immer noch auftritt, sollte irgendwas an dem Engine-Refactoring problematisch sein.

nittka avatar Dec 21 '21 19:12 nittka

Wie schon gesagt ... alles was von Lua "reinkommt" ist derzeit nicht threadsafe. Also wenn Lua TVT.KaufeProgramm() macht, wird derzeit nicht ueberprueft, ob die Spiellogik selbst auch gerade an der Liste der Lizenzen herumspielt. Es kann da also zu Ueberlappungen und somit Problemen kommen.

Dies laesst sich ohne Refactoring (alles "von mehreren Threads benutzte" muss mit Mutexen geschuetzt werden) nicht beheben.

Neben dem "Lua -> BlitzMax" kann derzeit aber auch der gleichzeitige Zugriff mehrerer Lua-Instanzen (KI1 bis 4) auf ein BlitzMaxobjekt Probleme bereiten (KI1 sorgt fuers hinzufuegen waehrend KI2 auch hinzufuegt). Hier kann allerdings Abhilfe geschaffen werden, in dem der gleichzeitige Zugriff erstmal (bis zum obig beschriebenen "Refactoring") blockiert wird:

base.util.luaengine.bmx

	Function Invoke:Int(fromLuaState:Byte Ptr)
		Local engine:TLuaEngine = TLuaEngine.FindEngine(fromLuaState)
		Return engine._Invoke()
	End Function

wird zu

	'mutex blocking multiple lua engines from simultaneously invoking
	'functions or methods
	global invokeMutex:TMutex = CreateMutex()
	Function Invoke:Int(fromLuaState:Byte Ptr)
		LockMutex(invokeMutex)
		Local engine:TLuaEngine = TLuaEngine.FindEngine(fromLuaState)
		Local result:Int = engine._Invoke()
		UnlockMutex(invokeMutex)
		
		Return result
	End Function

Das ist jetzt an sich erstmal die ganz grobe Keule, da nun auch "sicherere" Aufrufe andere Aufrufe von Lua zu Blitzmax blockieren wuerden. Eine Funktion TVT.WarteFuerMich(10 Minuten) wuerde nun auch andere KIs die 10 Minuten warten lassen. Zumindest wenn in BlitzMax das so geschrieben waere (oder so):

Function WarteFuerMich(time:Int)
  delay(time*60)
End Function

Ich denke weniger "blockierend" waere dann bei sowas der Event-Approach ...

TVT.WeckeMichAufIn(10 Minuten)
isActive = false

und die BlitzMax-Funktion wuerde eine Variable setzen, die dafuer sorgt, dass nach Minuten die LuaEngine einen Funktionsaufruf bekommt (also so ein "onWakeMeUp" gam event oder so).

Ich denke, hier fuehren einige Wege nach Rom, wir muessten nur die passenden raussuchen.

GWRon avatar Dec 21 '21 19:12 GWRon

Bei einer höheren Vorspulgeschwindigkeit ist bei mir nun auch der Stand nach Lua-Engine-Änderungen ohne KI-Logik-Anpassungen mit Segmentation Fault abgestürzt. Ich denke neben der Threadsicherheit an sich sollte man auch noch einmal überlegen, ob LUA überhaupt auf die spielinternen Objekte zugreifen können sollte. Eigentlich sollten nur einfache Datenobjekte an die KI gegeben werden. Die schon vorhandenen Objekte zu nutzen macht es einem zwar bequem, aber öffnet eben auch die Tür für solche Probleme.

Ich weiß jetzt nicht, wie wir prinzipiell vorgehen wollen. Ich könnte meine aktuellen KI-Änderungen noch so anpassen, dass die Chance für einen parallelen Zugriff möglichst gering ist. Dann könnte man das in den Master übernehmen und parallel an den Themen KI-Verbesserung und Threadsicherheit weiterarbeiten.

nittka avatar Dec 22 '21 13:12 nittka

Ich habe nochmal ein Master-Spiel mit hoher Geschwindigkeit laufen lassen - auch hier Absturz mit Segmentation Fault. Es gab also auch vor dem ganzen Umbau schon ein Problem (bei mir und einem Forumsspieler gab es auch in regulären Spielen schon Abstürze). Insofern halte ich es für gerechtfertigt, den KI-Branch (mit Verbesserungen bezüglich der Zugriffe) zu mergen.

Die grob(/ß)e Keule schwingt aber auch in einigen Fällen daneben. Zwar warten die KIs dann aufeinander, aber KI und Spiellogik können weiterhin gleichzeitig auf dasselbe Objekt zugreifen. Das lässt sich auch mit Logging der aufgerufenen KI-Methoden nur bedingt ermitteln, denn der problematische Zugriff kann ja tief versteckt liegen (getter, der weit drinnen Berechnungen auf geteilten Objekten macht).

nittka avatar Dec 22 '21 15:12 nittka

Ja es waere erstmal nur eine grobe Keule, die das meiste erstmal verhindern koennte. Es passiert ja so schon selten (muss halt kollidieren).

Gemergt werden kann da gerne...

GWRon avatar Dec 22 '21 15:12 GWRon

Die KI bekommt ja an sich nur die Objekte um dort bequem auf GetTitle usw zugreifen zu koennen. Fuer wichtige Interaktionen muss sie ja die TVT:-Methoden nutzen

Das Problem da haben wir ja bereits erkannt: lua gteift aus seinen Threads auf die Sachen zu..waehrend der Mainthread das auch macht.

Ergo muessen alle Daten, die Lua "irgendwie" nutzt..abgesichert werden.

Das wird im Groben und Ganzen alle Spielobjekte betreffen.. da wir aber ja oft ueberpruefen ob eine Lizenz auch wirklich nicht null ist...sollte es an den Stellen kein Problem geben. Allerdings sollten listen, maps...trotzdem geschuetzt werden damit beim iterieren nichts an der Liste von "extern" geaendert werden kann...bzw andersherum.

GWRon avatar Dec 22 '21 15:12 GWRon

Siehe auch #485

nittka avatar Feb 26 '22 15:02 nittka

Mein subjektives Gefühl ist, dass die Anzahl der Abstürze zurückgegangen ist. Besonders seit die Anzeige zum Garbagecollector entfallen ist. Früher konnte ich auf meinem Entwicklungsrechner (Linux) bei Tempo 1000 kein KI-Spiel machen, weil sonst nach 2 bis 4 Spieltagen ein Absturz kam. Jetzt geht es manchmal bis Tag 35... Es ist aus den Logs aber weiterhin nicht für mich ersichtlich, was die Ursache sein könnte.

nittka avatar Apr 08 '22 17:04 nittka

Lässt sich steuern, wieviel Speicher das Spiel zur Verfügung hat? (Simulation Rechner mit wenig Speicher - häufigere segmentation faults...)

nittka avatar Apr 13 '22 16:04 nittka

Es gibt möglicherweise eine neue Spur. Wenn ich beim Schnellspiel die Maus im Chatbereich parke, laufen die Spiele (mutmaßlich) deutlich länger ohne Absturz. Möglicherweise kommen die Segmentation-Faults (zumindest beim Vorspulen) durch Methoden, die bei Mausposition im Spielbereich nicht schnell genug fertig werden (Raum schon wieder verlassen, Objekte weg?).

(ein Spiel am Tag 100 unterbrochen, zweites Spiel bei Tag 50 ohne Absturz; zuvor habe ich den Mauszeiger immer links knapp oberhalb des Fernsehers stehen lassen)

nittka avatar May 26 '22 11:05 nittka

War vor dem Release noch ein Zaubercommit dabei? Zumindest gab es bei den (noch nicht ganz so zahlreichen Testspielen) noch keinen einzigen Absturz wegen Segmentation-Fault... Und in dem Moment, wo ich die Nachricht abschicken will, ist es dann passiert. Es wäre ja auch zu schön gewesen.

nittka avatar Jun 16 '22 17:06 nittka

Das wird ne Sache vom Betriebssystem sein.

Aber hier wird es ne um den RAM gehen..eher was anderes.

GWRon avatar Oct 11 '22 08:10 GWRon

Ich würde dieses allgemeine Ticket gern zumachen. Es ist in den neueren verlinkt und durch die bereits durchgeführten Anpassungen scheint sich die Anzahl der Abstürze bei stark gestiegener, möglicher Geschwindigkeit schon gesunken zu sein.

nittka avatar Nov 01 '22 09:11 nittka