Trotz Theading bricht Verbindung ab

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Benutzeravatar
Dirk1972
User
Beiträge: 5
Registriert: Mittwoch 29. April 2015, 09:44
Wohnort: Augsburg
Kontaktdaten:

Hallo Python-Forum,

Um bei meiner Hausautomation von mehreren Rechnern gleichzeitig über eine HTML-Schnittstelle auf die Schalter- und Sensor-Variablen in der mySQL-Datenbank zugreifen zu können habe ich einen xml-Server erstellt, der mittels Threading auch den Zugriff von mehreren Clients gleichzeitig erlaubt (erlauben sollte)

Solange ich nur mit einem Rechner bzw. nacheinander auf den xml-Server zugreife, funktioniert alles so wie es soll.
Wenn ich aber gleichzeitig zugreifen möchte, so bricht eine Verbindung ab. Und ein Rechner zeigt nicht mehr alle Variablen.

Fehlermeldungen:
- 2055: Lost connection to MySQL server at 'localhost:3306', system error: 9 Bad file descriptor
- bytearray index out of range

Und hier der Server-Code der dauerhaft auf meinem Server (Synology DSM 5.2) läuft:

Code: Alles auswählen

#!/usr/bin/python3

from socketserver import ThreadingMixIn
from xmlrpc.server import SimpleXMLRPCServer
import ser_module.xml_module as xml

class SimpleThreadingXMLRPCServer(ThreadingMixIn,SimpleXMLRPCServer):
    pass
    
class XmlrpcHandler:
    def _dispatch(self,method,params):
        try:
            return getattr(xml,method)(*params)
        except Exception as e:
            print (e)
            return False

if __name__=="__main__":
    xml.DB_Main("start")
    if xml.conn:
        xml.Ereignis("XML-RPC-Server und Datenbankzugriff wurden gestartet.")
        server = SimpleThreadingXMLRPCServer(("127.0.0.1", 55072))
        server.register_instance(XmlrpcHandler())
        server.serve_forever()
    else:
        xml.Warnung("Der Server wurde nicht gestartet, da keine Verbindung zur Datenbank",email_only=1)
Hier der Ausschnitt aus dem "xml_module" Modul bzgl. der Datenbankanbindung.
Ich denke, dass ich hier noch einen Denkfehler habe bzgl. der globalen / lokalen Variablen oder der Lock-Funktion.

Code: Alles auswählen

import mysql.connector as mysql

from threading import Lock
from ser_module._config import config  #modul mit einem Dictionary mit festen Variablen

conn=False
connLock=Lock()

def DB_Main(command="start"):
    """Datenbank Öffnen/Schliesen"""
    global conn
    try:
        if command=="start":
            connLock.acquire()
            conn=mysql.connect()
            connLock.release()
        else:
            connLock.acquire()
            conn.close()
            conn=False
            connLock.release()
    except Exception as e:
        connLock.acquire()
        conn=False
        connLock.release()

def DB_Connect():
    """Datenbank verbinden"""
    global conn
    try:
        with open(config("db.pw"), "r") as z:  #Zugangsdaten aus einer externen Datei
            zugang=[line.strip() for line in z]
        connLock.acquire()
        conn.connect(host=zugang[0],user=zugang[1],password=zugang[2], database=zugang[3])
        connLock.release()
        return True
    except Exception as e:
        return False

def DB_Command(comm,values=0):
    """Datenbank-Befehl"""
    global conn
    if conn==False:
        DB_Main("start")
    if conn.is_connected()==False:
        if DB_Connect()==False:
            return False
    try:
        c=conn.cursor()
        c.execute(comm,values)
        if comm[:6]=="SELECT":
            data=c.fetchall()
            c.close()
            return(data)
        else:
            conn.commit()
            c.close()
            return True                    
    except Exception as e:
        return False
Und hier ein kurzer Ausschnitt wie ich auf die Variablen zugreife:

Code: Alles auswählen

from xmlrpc.client import ServerProxy,ProtocolError
cli=ServerProxy(config("xml_server")) # in config()sind meine WEB-Variablen gespeichert hier die IP-Adresse und der Port für den xml-Server
Geschoss_ID=1
c=cli.DB_Command(
        "SELECT a.ID, r.Raum, a.Name, a.Hardware, a.Zustand, h.Max_Wert, a.gesperrt, a.x_Achse, a.y_Achse, a.Lage_Text "+
        "FROM DG_Home.Aktoren a, DG_Home.Geschoss g, DG_Home.Raum r, DG_Home.Hardware h "+
        "WHERE a.Geschoss=g.ID AND a.Raum=r.ID AND a.Hardware=h.ID AND a.Geschoss=%s "+
        "ORDER BY r.Raum, a.Name",
        (Geschoss_ID,)
        )
if c!=False:
        for row in c:
        # ... ab hier werden mit print() html-Codes aufgebaut, die mir die Schalter erstellen. 

Für das Druchbrechen meiner Denkblockade wäre ich sehr dankbar!

Vielen Dank!

Dirk 1972
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Dirk1972: Du sicherst ja auch nur das Verbinden mit einem Lock ab, was aber gar nicht parallel ausgeführt wird. Dein Select aber nicht, obwohl immer nur ein Thread gleichzeitig eine DB-Verbindung nutzen darf. Lock sollte man mit dem with-Statement verwenden, dann bleibt der auch nicht gesperrt, wenn ein Fehler auftritt. Mit False vergleicht man üblicherweise per "if not conn:" etc. Es gibt auch noch andere Statments, die Ergebnisse zurückliefern, außer SELECT. Die Exceptionbehandlung ist schlecht. Es sollten nur die Fehler behandelt werden, die man auch sinnvoll behandeln kann.
Benutzeravatar
Dirk1972
User
Beiträge: 5
Registriert: Mittwoch 29. April 2015, 09:44
Wohnort: Augsburg
Kontaktdaten:

Danke Sirius3!!

Werde meine Locks entsprechend umbauen!

Dass ich nur SELECT behandle ist bewusst, da der XML-Server nur zum Variablen-Auslesen aus der Datenbank für die Webseite gedacht ist, und zum Speichern von Formulareingaben. Alle anderen Datenbank-Operationen laufen über php-Admin...

"if conn==False" habe ich gewählt, da ich conn bei einem Verbindungs-Fehler auf False gesetzt habe. Manchmal ist das Naheliegende nicht umbedingt das Sauberste.
Auch dieser Fehler wird behoben... Danke!

Bei der Exception Behandlung war ich bereits verzweifelt und habe überall wo ich meinen Programmierfehler vermutete die try-funktion verwendet. Die Logzeilen, die mit "e" gezeigt haben, habe ich hier der Übersicht wegen gelöscht. Auch hier werde ich wieder zurück zu saubereren Code übergehen.

Nochmal vielen Dank! Der wichtig Tip war, dass ich SELECT absichern muss, dass war mir nicht bewusst!

Bei einem möchte ich Dir aber widersprechen, so weit ich das als Laie verstehe:
Die Datenbank trennt die Verbindung, wenn diese längere Zeit nicht genutzt wird, daher kann es theoretisch passieren, dass zwei Rechner gleichzeitig die Datenbank Verbindung herstellen wollen. daher muss ich zumindest conn.connect(Zugangsdaten) weiterhin mit Lock absichern oder? Daher frage ich ja auch ab, ob die Verbindung besteht...

In diesem Sinnen nochmal vielen Dank und einen schönen Abend! :D :D :D
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Die Datenbank trennt die Verbindung, wenn diese längere Zeit nicht genutzt wird, daher kann es theoretisch passieren, dass zwei Rechner gleichzeitig die Datenbank Verbindung herstellen wollen. daher muss ich zumindest conn.connect(Zugangsdaten) weiterhin mit Lock absichern oder? Daher frage ich ja auch ab, ob die Verbindung besteht...
MySQL (und andere DB-Server auch) können mit mehreren offenen Verbindungen gleichzeitig umgehen, von daher brauchst du da nichts abzusichern.

Ansonsten macht es hier vielleicht auch Sinn, SQLAlchemy einzusetzen. Das würde sich dann auch um's Connection Pooling kümmern, so dass du nicht selber irgendwas implementieren müsstest.

Gruß, noisefloor
Antworten