Python Подключение к серверу MariaDB закрывается примерно каждые 24 часа - PullRequest
1 голос
/ 17 января 2020

Я создаю продукт для своей компании, чтобы отслеживать товары с прикрепленными RFID-метками, они пролистывают в киоске, он регистрируется в базе данных, просто.

База данных фактически размещена в киоске (это не лучшая практика, я знаю), и основная программа, работающая в киоске, - это python интерфейс с GuiZero. Киоск запускает Raspbian Desktop с Raspberry Pi.

Все работает хорошо, но (приблизительно) через 24 часа после запуска программы работает как обычно, пока запрос базы данных не будет отправлен на сервер. python -mariadb-connector отвечает: 2055: Lost connection to MySQL server at '127.0.0.1:3306', system error: 32 Broken pipe, я устранял это уже около месяца, и теперь я уверен, что можно это исправить.

Я попытался просто обновить подключение из интерфейса python всякий раз, когда это происходит, но это тоже не работает. Единственное, что решает проблему, это перезапуск программы, которой нельзя доверять пользователям, выясняя, как это сделать.

Следует отметить, что все работает идеально до тех пор, пока ~ 24 часа отметок.

Любая помощь?

Зависимости:

Python 3.7
(pip) mysql-connector v2.2.9
MariaDB: 10.3.17-MariaDB-0+deb10ul Raspbian 10
Raspbian GNU/Linux v10

Переменные тайм-аута

MariaDB [locker]> show variables like "%timeout";
+---------------------------------------+-------+
| Variable_name                         | Value |
+---------------------------------------+-------+
| connect_timeout                       | 10    |
| delayed_insert_timeout                | 300   |
| idle_readonly_transaction_timeout     | 0     |
| idle_transaction_timeout              | 0     |
| idle_write_transaction_timeout        | 0     |
| innodb_flush_log_at_timeout           | 1     |
| innodb_lock_wait_timeout              | 50    |
| innodb_rollback_on_timeout            | OFF   |
| interactive_timeout                   | 28800 |
| lock_wait_timeout                     | 86400 |
| net_read_timeout                      | 30    |
| net_write_timeout                     | 60    |
| rpl_semi_sync_master_timeout          | 10000 |
| rpl_semi_sync_slave_kill_conn_timeout | 5     |
| slave_net_timeout                     | 60    |
| thread_pool_idle_timeout              | 60    |
| wait_timeout                          | 28800 |
+---------------------------------------+-------+

Вот код, строка 166, где обрабатывается ошибка. (Не то, что моя обработка что-то делает) Ctrl + F для "aghiulg", чтобы добраться до этой строки.

#!/usr/bin/python3.7
from datetime import datetime
from sys import exit

# Classes
class User:
    def __init__ (self, rfid, name):
        self.name = name
        self.rfid = rfid

class Key:
    def __init__ (self, rfid, kid, prop, loc):
        self.rfid = rfid
        self.kid = kid
        self.prop = prop.split("/")
        self.loc = loc

    def toString (self):
        return "[{}] {}".format(self.kid, "/".join(self.prop[:3]))

# Slack
import slack

slackBotToken = "OBFUSCATEDFORSTACKOVERFLOW"
slackClient = slack.WebClient(slackBotToken)
slackID = None

def getTimeString ():
    now = datetime.now()
    return now.strftime("%m/%d/%Y %H:%M:%S")

def log (s):
    time = getTimeString()

    # stdout
    print("[{}] {}".format(time, s))


    # slack
    slackErr = False
    try:
        slackClient.chat_postMessage(channel = "#keys", text = s)
    except concurrent.futures._base.TimeoutError:
        slackErr = True

    # file
    with open("/home/pi/key-locker/log.txt", "a+") as f:
        f.write("[{}] {}\n".format(time, s))
        if slackErr:
            f.write("Couldn't write that to Slack, oops.")

def xlog (s):
    try:
        slackClient.chat_postMessage(channel = "OBFUSCATED", text = s)
        return True
    except concurrent.futures._base.TimeoutError:
        return False

# guizero
from guizero import App, Text, TextBox, info

app = App(title = "Key Locker")
app.tk.attributes("-fullscreen", True)
REFOCUS_TIMER = 500
USER_TIMEOUT = 60 * 1000

# MariaDB
import mysql.connector as mariadb

def connectDB ():
    xlog("Reconnecting to database...")
    return mariadb.connect(user="OBFUSCATED", password="OBFUSCATED", database="OBFUSCATED")

# mdb = mariadb.connect(user="OBFUSCATED", password="OBFUSCATED", database="OBFUSCATED")
mdb = connectDB()
cursor = mdb.cursor(buffered = True)

def focusUIN (uin = None):
    if uin:
        uin.focus()

def onTextBoxKey (e, f = None):
    global uidBox, kidBox, bigText, underText, currentUser, currentKey, escapeKeys

    if len(e.key) == 0:
        return

    if f == uidBox and ord(e.key) == 13:
        uid = uidBox.value
        if uid.lower() in escapeKeys:
            exit(0)
        else:
            currentUser = codeToUser(uid)
            if currentUser:
                uidBox.cancel(focusUIN)
                uidBox.disable()

                kidBox.enable()
                kidBox.repeat(REFOCUS_TIMER, focusUIN, args = [kidBox])
                kidBox.when_key_pressed = lambda e: onTextBoxKey(e, f = kidBox)

                bigText.value = "Scan Key"
                underText.value = "Welcome, {}.".format(currentUser.name)

                app.after(USER_TIMEOUT, restart, args = ["User Timeout"])
            else:
                restart("That user doesn't exist.")
    elif f == kidBox and ord(e.key) == 13:
        kid = kidBox.value
        if kid.lower() in escapeKeys:
            exit(0)
        else:
            app.cancel(restart)

            currentKey = codeToKey(kid)
            if currentKey:
                kidBox.cancel(focusUIN)
                kidBox.disable()

                inLocker = (currentKey.loc.lower() == "locker")
                success = (checkout(currentUser, currentKey) if inLocker else checkin(currentUser, currentKey))
                if success:
                    restart("{} checked {} the {} keys.".format(currentUser.name, "out" if inLocker else "in", currentKey.toString()))
                else:
                    restart("System error, try again.")
            else:
                restart("That key doesn't exist.")

def restart (subText = ". . ."):
    global uidBox, kidBox, bigText, underText

    uidBox.value = ""
    uidBox.enable()
    uidBox.cancel(focusUIN)
    uidBox.repeat(REFOCUS_TIMER, focusUIN, args = [uidBox])
    uidBox.focus()

    kidBox.value = ""
    kidBox.cancel(focusUIN)
    kidBox.disable()

    bigText.value = "Scan Badge"
    underText.value = subText

# App
escapeKeys = "letmeout pleaseijustwanttoseemywifeandkidsagain".split(" ")
currentUser = None
currentKey = None

def codeToUser (uid = None):
    xlog("Trying to find user by RFID...")
    if uid:
        try:
            cursor.execute("select Name from user where RFID = '{}';".format(uid))
            names = []
            for n in cursor:
                names.append(n)
            if len(names) != 1:
                xlog("Ran the function, and literally got no matches for that user rfid.")
                xlog("Restarting...")
                exit(0)
                return None
            else:
                xlog("Found user: {}".format(names[0][0]))
                return User(uid, names[0][0])
        except mariadb.Error as e: # aghiulg
            xlog("Database error trying to find the user.\n{}".format(e))
            # fatalError(e)
            return None
    else:
        xlog("They didn't give me an RFID.")
        return None

def codeToKey (kid = None):
    if kid:
        try:
            cursor.execute("select KeyringID, Properties, Location from keyring where RFID = {};".format(kid))
            keys = []
            for k in cursor:
                keys.append(k)
            if len(keys) != 1:
                return None
            else:
                keys = keys[0]
                return Key(kid, keys[0], keys[1], keys[2])
        except mariadb.Error as e:
            # fatalError(e)
            return None
    else:
        return None

def checkout(user, key):
    global mdb

    log("Checking out k{} ({}) to {}.".format(key.kid, ", ".join(key.prop), user.name))
    try:
        cursor.execute("update keyring set Location = '{}' where KeyringID = {}".format(user.name, key.kid))
        mdb.commit()
        return True
    except mariadb.Error as e:
        # fatalError(e)
        mdb = connectDB()
        return False

def checkin (user, key):
    global mdb

    log("Checking in k{} ({}) from {}.".format(key.kid, ", ".join(key.prop), user.name))
    try:
        cursor.execute("update keyring set Location = 'locker' where KeyringID = {}".format(key.kid))
        mdb.commit()
        return True
    except mariadb.Error as e:
        # fatalError(e)
        mdb = connectDB()
        return False

def fatalError (e):
    log("Error: {}".format(e))
    log("Fatal error encountered, Key Locker turning off. Next user must open it by double clicking the desktop link.")
    exit()

# First Run
if __name__ == "__main__":
    bigText = Text(app, "Scan Badge", size = 40, color = "black")
    underText = Text(app, ". . .", size = 15, color = "black")

    uidBox = TextBox(app)
    uidBox.repeat(REFOCUS_TIMER, focusUIN, args = [uidBox])
    uidBox.when_key_pressed = lambda e: onTextBoxKey(e, f = uidBox)

    kidBox = TextBox(app, enabled = False)

    auditText = Text(app, size = 10, color = "black")

    app.display()

Ответы [ 3 ]

1 голос
/ 24 января 2020

Поскольку вы используете локально размещенный экземпляр MariaDB, попробуйте подключиться к БД с помощью сокета Unix вместо TCP / IP. В случае, если какой-либо брандмауэр на уровне хоста или другие странные сети с вашим стеком TCP создают помехи, вы можете обойти его таким образом.

Сокет для MariaDB обычно расположен на /var/run/mysqld/mysqld.sock (если вы не настроили его иначе ). Проверьте наличие сокета на этом пути (или отследите, где он находится),

> show variables like 'socket';
+---------------+-----------------------------+
| Variable_name | Value                       |
+---------------+-----------------------------+
| socket        | /var/run/mysqld/mysqld.sock |
+---------------+-----------------------------+

Затем настройте команду подключения следующим образом:

mariadb.connect(user="Donuts", password="Candy", database="Cake", unix_socket="/var/run/mysqld/mysqld.sock")

Ref: https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html

1 голос
/ 24 января 2020

Значение для interactive_timeout имеет значение по умолчанию 28800 с = 8 часов, что означает, что после 8 часов бездействия сервер автоматически закроет соединение.

Я попытался бы увеличить это значение и проверить, разъединение все еще происходит.

Согласно PEP-249 все объекты курсора становятся недействительными, как только дескриптор соединения становится недействительным. Так как MySQL Connector / Python не имеет опции автоматического повторного подключения c и не позволяет переназначить объект подключения на курсор, вы должны воссоздать курсоры перед его повторным использованием.

1 голос
/ 20 января 2020

При ошибке БД вы создаете новое соединение, но не новый курсор. Существующий курсор все еще привязан к исходному соединению, которое теперь закрыто, поэтому вы продолжаете получать ошибки.

Повторно инициализируйте курсор всякий раз, когда вы создаете новое соединение, и вы должны быть хорошими.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...