Implement clean CTRL-C termination
Previously, we would simply bail out in an ugly way, potentially leaving temporary files around etc, or while writing status files. Hand SIGINT and SIGTERM as an event to the Account class, and make that bail out cleanly at predefined points. Stopping on ctrl-c can take a few seconds (it will e.g. finish to transfer the ongoing message), but it will shut down cleanly. Signed-off-by: Sebastian Spaeth <Sebastian@SSpaeth.de>
This commit is contained in:
parent
8ec6980c96
commit
0ccf06d5e6
@ -29,5 +29,9 @@ Changes
|
||||
|
||||
* Bumped bundled imaplib2 to release 2.29
|
||||
|
||||
* Make ctrl-c exit cleanly rather aborting brutally (which could leave
|
||||
around temporary files, half-written cache files, etc). Exiting on
|
||||
SIGTERM and CTRL-C can take a little longer, but will be clean.
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
@ -50,7 +50,9 @@ class Account(CustomConfig.ConfigHelperMixin):
|
||||
:class:`accounts.SyncableAccount` which contains all functions used
|
||||
for syncing an account."""
|
||||
#signal gets set when we should stop looping
|
||||
abort_signal = Event()
|
||||
abort_soon_signal = Event()
|
||||
#signal gets set on CTRL-C/SIGTERM
|
||||
abort_NOW_signal = Event()
|
||||
|
||||
def __init__(self, config, name):
|
||||
"""
|
||||
@ -97,7 +99,8 @@ class Account(CustomConfig.ConfigHelperMixin):
|
||||
set_abort_event() to send the corresponding signal. Signum = 1
|
||||
implies that we want all accounts to abort or skip the current
|
||||
or next sleep phase. Signum = 2 will end the autorefresh loop,
|
||||
ie all accounts will return after they finished a sync.
|
||||
ie all accounts will return after they finished a sync. signum=3
|
||||
means, abort NOW, e.g. on SIGINT or SIGTERM.
|
||||
|
||||
This is a class method, it will send the signal to all accounts.
|
||||
"""
|
||||
@ -107,7 +110,10 @@ class Account(CustomConfig.ConfigHelperMixin):
|
||||
config.set('Account ' + acctsection, "skipsleep", '1')
|
||||
elif signum == 2:
|
||||
# don't autorefresh anymore
|
||||
cls.abort_signal.set()
|
||||
cls.abort_soon_signal.set()
|
||||
elif signum == 3:
|
||||
# abort ASAP
|
||||
cls.abort_NOW_signal.set()
|
||||
|
||||
def get_abort_event(self):
|
||||
"""Checks if an abort signal had been sent
|
||||
@ -122,7 +128,8 @@ class Account(CustomConfig.ConfigHelperMixin):
|
||||
skipsleep = self.getconfboolean("skipsleep", 0)
|
||||
if skipsleep:
|
||||
self.config.set(self.getsection(), "skipsleep", '0')
|
||||
return skipsleep or Account.abort_signal.is_set()
|
||||
return skipsleep or Account.abort_soon_signal.is_set() or \
|
||||
Account.abort_NOW_signal.is_set()
|
||||
|
||||
def sleeper(self):
|
||||
"""Sleep if the account is set to autorefresh
|
||||
@ -152,7 +159,8 @@ class Account(CustomConfig.ConfigHelperMixin):
|
||||
item.stopkeepalive()
|
||||
|
||||
if sleepresult:
|
||||
if Account.abort_signal.is_set():
|
||||
if Account.abort_soon_signal.is_set() or \
|
||||
Account.abort_NOW_signal.is_set():
|
||||
return 2
|
||||
self.quicknum = 0
|
||||
return 1
|
||||
@ -288,6 +296,8 @@ class SyncableAccount(Account):
|
||||
|
||||
# iterate through all folders on the remote repo and sync
|
||||
for remotefolder in remoterepos.getfolders():
|
||||
# check for CTRL-C or SIGTERM
|
||||
if Account.abort_NOW_signal.is_set(): break
|
||||
if not remotefolder.sync_this:
|
||||
self.ui.debug('', "Not syncing filtered remote folder '%s'"
|
||||
"[%s]" % (remotefolder, remoterepos))
|
||||
@ -320,6 +330,9 @@ class SyncableAccount(Account):
|
||||
self.callhook(hook)
|
||||
|
||||
def callhook(self, cmd):
|
||||
# check for CTRL-C or SIGTERM and run postsynchook
|
||||
if Account.abort_NOW_signal.is_set():
|
||||
return
|
||||
if not cmd:
|
||||
return
|
||||
try:
|
||||
|
@ -18,6 +18,7 @@
|
||||
from offlineimap import threadutil
|
||||
from offlineimap.ui import getglobalui
|
||||
from offlineimap.error import OfflineImapError
|
||||
import offlineimap.accounts
|
||||
import os.path
|
||||
import re
|
||||
from sys import exc_info
|
||||
@ -332,6 +333,9 @@ class BaseFolder(object):
|
||||
self.getmessageuidlist())
|
||||
num_to_copy = len(copylist)
|
||||
for num, uid in enumerate(copylist):
|
||||
# bail out on CTRL-C or SIGTERM
|
||||
if offlineimap.accounts.Account.abort_NOW_signal.is_set():
|
||||
break
|
||||
self.ui.copyingmessage(uid, num+1, num_to_copy, self, dstfolder)
|
||||
# exceptions are caught in copymessageto()
|
||||
if self.suggeststhreads():
|
||||
@ -447,6 +451,9 @@ class BaseFolder(object):
|
||||
('syncing flags' , self.syncmessagesto_flags)]
|
||||
|
||||
for (passdesc, action) in passes:
|
||||
# bail out on CTRL-C or SIGTERM
|
||||
if offlineimap.accounts.Account.abort_NOW_signal.is_set():
|
||||
break
|
||||
try:
|
||||
action(dstfolder, statusfolder)
|
||||
except (KeyboardInterrupt):
|
||||
|
@ -288,11 +288,6 @@ class OfflineImap:
|
||||
|
||||
self.config is supposed to have been correctly initialized
|
||||
already."""
|
||||
def sigterm_handler(signum, frame):
|
||||
# die immediately
|
||||
self.ui.terminate(errormsg="terminating...")
|
||||
signal.signal(signal.SIGTERM, sigterm_handler)
|
||||
|
||||
try:
|
||||
pidfd = open(self.config.getmetadatadir() + "/pid", "w")
|
||||
pidfd.write(str(os.getpid()) + "\n")
|
||||
@ -328,11 +323,19 @@ class OfflineImap:
|
||||
accounts.Account.set_abort_event(self.config, 1)
|
||||
elif sig == signal.SIGUSR2:
|
||||
# tell each account to stop looping
|
||||
getglobalui().warn("Terminating after this sync...")
|
||||
accounts.Account.set_abort_event(self.config, 2)
|
||||
elif sig == signal.SIGTERM or sig == signal.SIGINT:
|
||||
# tell each account to ABORT ASAP (ctrl-c)
|
||||
getglobalui().warn("Terminating NOW (this may "\
|
||||
"take a few seconds)...")
|
||||
accounts.Account.set_abort_event(self.config, 3)
|
||||
|
||||
signal.signal(signal.SIGHUP,sig_handler)
|
||||
signal.signal(signal.SIGUSR1,sig_handler)
|
||||
signal.signal(signal.SIGUSR2,sig_handler)
|
||||
signal.signal(signal.SIGTERM, sig_handler)
|
||||
signal.signal(signal.SIGINT, sig_handler)
|
||||
|
||||
#various initializations that need to be performed:
|
||||
offlineimap.mbnames.init(self.config, syncaccounts)
|
||||
@ -361,9 +364,6 @@ class OfflineImap:
|
||||
t.start()
|
||||
threadutil.exitnotifymonitorloop(threadutil.threadexited)
|
||||
self.ui.terminate()
|
||||
except KeyboardInterrupt:
|
||||
self.ui.terminate(1, errormsg = 'CTRL-C pressed, aborting...')
|
||||
return
|
||||
except (SystemExit):
|
||||
raise
|
||||
except Exception, e:
|
||||
|
Loading…
Reference in New Issue
Block a user