Skip to content

Commit bab5ba0

Browse files
committed
fix: when user quits GUI during processing, forcibly kill all processes
1 parent 4bca272 commit bab5ba0

File tree

6 files changed

+78
-18
lines changed

6 files changed

+78
-18
lines changed

CHANGELOG

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
0.7.1
1+
0.8.0
2+
- fix: when user quits GUI during processing, forcibly kill all processes
23
- docs: add installation section
4+
- setup: add psutil dependency
35
0.7.0
46
- build: add torch-CUDA libraries to release binaries
57
0.6.8

chipstream/gui/main_window.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
import pathlib
77
import signal
88
import sys
9+
import time
910
import traceback
1011
import webbrowser
1112

1213
from dcnum.feat import feat_background
1314
from dcnum.meta import paths as dcnum_paths
1415
from dcnum.segm import get_available_segmenters
16+
import psutil
1517
from PyQt6 import uic, QtCore, QtWidgets
1618
from PyQt6.QtCore import QStandardPaths
1719

@@ -157,6 +159,19 @@ def append_paths(self, path_list):
157159
self.job_manager.add_path(pp)
158160
self.tableWidget_input.update_from_job_manager()
159161

162+
@QtCore.pyqtSlot(QtCore.QEvent)
163+
def closeEvent(self, event):
164+
jobs_running = self.is_running()
165+
if jobs_running:
166+
self.job_manager.close(force=True)
167+
event.accept()
168+
if jobs_running:
169+
time.sleep(1)
170+
# We have killed all background jobs, we might be littered with
171+
# zombies. Everything is probably broken. Just get rid of
172+
# ourselves.
173+
psutil.Process().kill()
174+
160175
@QtCore.pyqtSlot(QtCore.QEvent)
161176
def dragEnterEvent(self, e):
162177
"""Whether files are accepted"""

chipstream/gui/main_window.ui

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
<item>
4040
<widget class="QSplitter" name="splitter">
4141
<property name="orientation">
42-
<enum>Qt::Vertical</enum>
42+
<enum>Qt::Orientation::Vertical</enum>
4343
</property>
4444
<widget class="ProgressTable" name="tableWidget_input">
4545
<property name="sizePolicy">
@@ -49,25 +49,25 @@
4949
</sizepolicy>
5050
</property>
5151
<property name="editTriggers">
52-
<set>QAbstractItemView::NoEditTriggers</set>
52+
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
5353
</property>
5454
<property name="dragDropMode">
55-
<enum>QAbstractItemView::NoDragDrop</enum>
55+
<enum>QAbstractItemView::DragDropMode::NoDragDrop</enum>
5656
</property>
5757
<property name="alternatingRowColors">
5858
<bool>true</bool>
5959
</property>
6060
<property name="selectionMode">
61-
<enum>QAbstractItemView::SingleSelection</enum>
61+
<enum>QAbstractItemView::SelectionMode::SingleSelection</enum>
6262
</property>
6363
<property name="selectionBehavior">
64-
<enum>QAbstractItemView::SelectRows</enum>
64+
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
6565
</property>
6666
<property name="textElideMode">
67-
<enum>Qt::ElideMiddle</enum>
67+
<enum>Qt::TextElideMode::ElideMiddle</enum>
6868
</property>
6969
<property name="horizontalScrollMode">
70-
<enum>QAbstractItemView::ScrollPerPixel</enum>
70+
<enum>QAbstractItemView::ScrollMode::ScrollPerPixel</enum>
7171
</property>
7272
<property name="sortingEnabled">
7373
<bool>true</bool>
@@ -139,7 +139,7 @@
139139
<item>
140140
<spacer name="verticalSpacer_2">
141141
<property name="orientation">
142-
<enum>Qt::Vertical</enum>
142+
<enum>Qt::Orientation::Vertical</enum>
143143
</property>
144144
<property name="sizeHint" stdset="0">
145145
<size>
@@ -246,7 +246,7 @@
246246
<item row="1" column="0">
247247
<spacer name="verticalSpacer">
248248
<property name="orientation">
249-
<enum>Qt::Vertical</enum>
249+
<enum>Qt::Orientation::Vertical</enum>
250250
</property>
251251
<property name="sizeHint" stdset="0">
252252
<size>
@@ -507,7 +507,7 @@
507507
<x>0</x>
508508
<y>0</y>
509509
<width>1116</width>
510-
<height>22</height>
510+
<height>23</height>
511511
</rect>
512512
</property>
513513
<widget class="QMenu" name="menuHelp">
@@ -608,5 +608,21 @@
608608
</hint>
609609
</hints>
610610
</connection>
611+
<connection>
612+
<sender>actionQuit</sender>
613+
<signal>triggered()</signal>
614+
<receiver>MainWindow</receiver>
615+
<slot>close()</slot>
616+
<hints>
617+
<hint type="sourcelabel">
618+
<x>-1</x>
619+
<y>-1</y>
620+
</hint>
621+
<hint type="destinationlabel">
622+
<x>557</x>
623+
<y>342</y>
624+
</hint>
625+
</hints>
626+
</connection>
611627
</connections>
612628
</ui>

chipstream/gui/manager.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import dcnum.read
99
from dcnum import logic as dclogic
1010
import h5py
11+
import psutil
1112

1213

1314
class JobStillRunningError(BaseException):
@@ -56,6 +57,30 @@ def clear(self):
5657
self._runner_list.clear()
5758
self._worker = None
5859

60+
def close(self, force=True):
61+
if force:
62+
# Get the current process
63+
proc = psutil.Process()
64+
# Forcibly kill all children. This is not very clean, and it
65+
# might leave zombie processes behind. But these zombie
66+
# processes will not be doing anything with data anymore.
67+
for ii in range(100):
68+
killed = 0
69+
children = list(proc.children(recursive=True))
70+
for child in children:
71+
try:
72+
child.kill()
73+
killed += 1
74+
except (psutil.ZombieProcess, psutil.NoSuchProcess):
75+
continue
76+
if not killed:
77+
break
78+
79+
self.clear()
80+
elif self.is_busy():
81+
raise ValueError(
82+
"Manager is busy, use `force=True` to close regardless")
83+
5984
def is_busy(self):
6085
return self.busy_lock.locked()
6186

chipstream/gui/table_progress.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,11 @@ def update_from_job_manager(self):
7474

7575
@QtCore.pyqtSlot()
7676
def update_from_job_manager_progress(self):
77-
for ii in range(len(self.job_manager)):
78-
st = self.item(ii, 1)
79-
st.setText(self.job_manager[ii]["state"])
80-
pb = self.cellWidget(ii, 2)
81-
progress = self.job_manager[ii]["progress"]
82-
if pb is not None and pb.value() != int(progress*1000):
83-
pb.setValue(int(progress*1000))
77+
if self.job_manager:
78+
for ii in range(len(self.job_manager)):
79+
st = self.item(ii, 1)
80+
st.setText(self.job_manager[ii]["state"])
81+
pb = self.cellWidget(ii, 2)
82+
progress = self.job_manager[ii]["progress"]
83+
if pb is not None and pb.value() != int(progress*1000):
84+
pb.setValue(int(progress*1000))

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dependencies = [
3030
"dcnum>=0.25.10",
3131
"h5py>=3.0.0, <4",
3232
"numpy>=1.21, <3", # CVE-2021-33430
33+
"psutil>=7",
3334
]
3435
dynamic = ["version"]
3536

0 commit comments

Comments
 (0)