Поиск неисправностей Python Segfault с резьбой и GTK - PullRequest
1 голос
/ 18 февраля 2020

Мне трудно отследить, почему мой код Python часто (но не всегда) Segfaults. Я начинающий с Python, поэтому не стесняйтесь sh с моим кодом, размещенным ниже.

Мой код ниже предназначен для запуска duply в потоке. Кажется, я не могу воспроизвести Segfault с другими командами, но процесс duply, кажется, всегда работает успешно, даже если сценарий Python Segfaults. Ниже я изменил код, чтобы просто запустить duply --help. Segfault все еще происходит в этом случае. Иногда скрипт просто зависает, и мне приходится его убивать.

Я запускаю тестирование Debian, и моя версия Python:

Python 3.7.6 (default, Jan 19 2020, 22:34:52) 
[GCC 9.2.1 20200117] on linux

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

Когда я запускаю приведенный ниже код с python3 -q -X faulthandler backupreminder.py, он иногда работает, иногда зависает без вывода, а иногда выдает один из следующее:

Fatal Python error: Segmentation fault

Thread 0x00007f28a5a30700 (most recent call first):
  File "backupreminder.py", line 121 in backup_finished
  File "backupreminder.py", line 111 in run_backup_thread
  File "/usr/lib/python3.7/threading.py", line 870 in run
  File "/usr/lib/python3.7/threading.py", line 975 in _bootstrap_inner
  File "/usr/lib/python3.7/threading.py", line 894 in _bootstrap

Thread 0x00007f28a0009000 (most recent call first):

Thread 0xSegmentation fault

или иногда:

Fatal Python error: Segmentation fault

Current thread 0x00007fa95a22d700 (most recent call first):
  File "backupreminder.py", line 119 in backup_finished
  File "backupreminder.py", line 111 in run_backup_thread
  File "/usr/lib/python3.7/threading.py", line 870 in run
  File "/usr/lib/python3.7/threading.py", line 926 in _bootstrap_inner
  File "/usr/lib/python3.7/threading.py", line 890 in _bootstrap

Thread 0x00007fa95dde6740 (most recent call first):
  File "/usr/lib/python3/dist-packages/gi/overrides/Gtk.py", line 1630 in main
  File "backupreminder.py", line 174 in <module>
Segmentation fault

или иногда (требуя от меня убить процесс):

backupreminder.py:120: Warning: Source ID 33 was not found when attempting to remove it
  self.close_button.grab_focus()

или иногда (требуя от меня убить процесс):

/usr/lib/python3/dist-packages/gi/overrides/Gtk.py:1630: Warning: g_object_weak_unref: couldn't find weak ref 0x7fa43bafe510(0x7fa439822dd8)
  return _Gtk_main(*args, **kwargs)

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

Полный код Python (для чего он стоит):

#!/usr/bin/python3

# Backup reminder
# Copyright 2019 David Purton
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

import faulthandler
faulthandler.enable()

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkX11

import threading
import subprocess

class BackupWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title = "Backup reminder")

        self.set_resizable(False)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_border_width(20)

        vbox = Gtk.VBox(spacing = 40)

        hbox = Gtk.HBox(spacing = 20)
        icon = Gtk.Image()
        icon.set_from_icon_name("dialog-question-symbolic", Gtk.IconSize.DIALOG)
        hbox.pack_start(icon, False, False, 0)
        self.label = Gtk.Label(label = "<b>Backup reminder</b>\n\nIt's time to run a backup! Would you like to do this now?")
        self.label.set_xalign(0)
        self.label.set_use_markup(True)
        hbox.pack_start(self.label, False, False, 0)
        vbox.pack_start(hbox, False, False, 0)
        hbox.show_all()

        hbox = Gtk.HBox(spacing = 10)
        self.shutdown_button = Gtk.CheckButton.new_with_label("Shutdown after backup completes.");
        hbox.pack_start(self.shutdown_button, False, False, 0)
        self.shutdown_button.show()
        self.ok_button = Gtk.Button.new_with_label("OK")
        self.ok_button.set_property("width-request", 90)
        self.ok_button.connect("clicked", self.on_ok_clicked)
        hbox.pack_end(self.ok_button, False, False, 0)
        self.ok_button.show()
        self.cancel_button = Gtk.Button.new_with_label("Cancel")
        self.cancel_button.set_property("width-request", 90)
        self.cancel_button.connect("clicked", self.on_cancel_clicked)
        hbox.pack_end(self.cancel_button, False, False, 0)
        self.cancel_button.show()
        self.close_button = Gtk.Button.new_with_label("Close")
        self.close_button.set_property("width-request", 90)
        self.close_button.connect("clicked", self.on_cancel_clicked)
        hbox.pack_end(self.close_button, False, False, 0)
        vbox.pack_end(hbox, False, False, 0)
        hbox.show()
        self.ok_button.grab_focus()

        self.add(vbox)
        vbox.show()

        self.connect("key-press-event",self.on_key_press_event)        

        self.show()
        self.window_id = self.get_property('window').get_xid()

        window_size = self.get_preferred_size()
        self.set_size_request(window_size.natural_size.width, -1)

        self.connect("delete-event", self.on_delete)
        self.connect("destroy", Gtk.main_quit)

    def on_delete(self, event, data):
        if self.ok_to_quit():
            return False
        else:
            return True

    def on_ok_clicked(self, button):
        self.run_backup()

    def on_cancel_clicked(self, button):
        if self.ok_to_quit():
            Gtk.main_quit()

    def on_key_press_event(self, widget, event):
        if event.keyval == Gdk.KEY_Escape:
            if self.ok_to_quit():
                Gtk.main_quit()

    def run_backup(self):
        def run_backup_thread():
            #self.backup_proc = subprocess.Popen(["/usr/bin/duply", "binky", "backup", "--preview"],
            self.backup_proc = subprocess.Popen(["/usr/bin/duply", "--help"],
                                                stdout = subprocess.DEVNULL)
            returncode = self.backup_proc.wait()
            self.backup_finished(returncode)
        self.backup_thread = threading.Thread(target=run_backup_thread)
        self.backup_thread.start()
        self.ok_button.hide()
        self.cancel_button.grab_focus()
        self.label.set_label("<b>Backup reminder</b>\n\nBackup running...")

    def backup_finished(self, backup_returncode):
        self.close_button.show()
        self.close_button.grab_focus()
        self.cancel_button.hide()
        if backup_returncode != 0:
            self.label.set_label("<b>Warning</b>\n\nBackup did <b>not</b> finish successfully.")
            print("Warning: Binky backup did *not* finish successfully.")
        else:
            self.label.set_label("<b>Backup reminder</b>\n\nBackup finished successfully.")
            self.set_urgency_hint(True)
            if self.shutdown_button.get_active():
                subprocess.run(["/usr/bin/systemctl", "poweroff"])

    def backup_running(self):
        if hasattr(self, 'backup_thread'):
            return self.backup_thread.is_alive()
        else:
            return False

    def backup_terminate(self):
        if self.backup_running():
            self.backup_proc.terminate()

    def ok_to_quit(self):
        if self.backup_running():
            quitdialog = QuitDialog(self)
            response = quitdialog.run()
            quitdialog.destroy()
            if response == Gtk.ResponseType.OK:
                self.backup_terminate()
                return True
            else:
                return False
        else:
            return True

class  QuitDialog(Gtk.Dialog):

    def __init__(self, parent):
        Gtk.Dialog.__init__(self, "Cancel backup?", parent,
                            modal = True, destroy_with_parent = True)
        self.set_skip_taskbar_hint(True)
        self.set_resizable(False)
        self.set_role("pop-up")
        self.set_transient_for(parent)
        self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
        self.set_border_width(20)
        label = Gtk.Label(label = "Are you sure you want to cancel the backup?")
        self.vbox.set_spacing(40)
        self.add_buttons("Cancel", Gtk.ResponseType.CANCEL, "OK", Gtk.ResponseType.OK)
        box = self.get_content_area()
        box.add(label)
        self.show_all()

if __name__ == "__main__":
    backupwin = BackupWindow()
    Gtk.main()
...