GUI: Canceling verification now also kills back-end thread.
Previously, pressing 'cancel' in the verification window would leave a dangling scyther backend process. Now the process gets correctly killed. The following changes enable this: - External processes are no longer invoked through the shell (otherwise they are subprocesses of the shell and cannot be reliably killed cross-platform). - The 'safeCommand' procedure now has a hook for passing opened Popen objects. - The GUI stores and kills the Popen objects on cancel or window close. To do: an alternative interface for this in 'safeCommand' could expose a 'killMe' method through a callback; this might be cleaner in the long term.
This commit is contained in:
parent
6473aba398
commit
fe364fbe9d
@ -56,8 +56,21 @@ class ScytherThread(threading.Thread):
|
|||||||
self.options = options
|
self.options = options
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
self.popenList = []
|
||||||
threading.Thread.__init__ ( self )
|
threading.Thread.__init__ ( self )
|
||||||
|
|
||||||
|
def storePopen(self,p):
|
||||||
|
self.popenList.append(p)
|
||||||
|
|
||||||
|
def cleanExit(self):
|
||||||
|
# Cleanup of spawned processes
|
||||||
|
for index,p in enumerate(self.popenList):
|
||||||
|
try:
|
||||||
|
p.kill()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.popenList = []
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
(scyther, claims, summary) = self.claimResults()
|
(scyther, claims, summary) = self.claimResults()
|
||||||
@ -100,7 +113,7 @@ class ScytherThread(threading.Thread):
|
|||||||
|
|
||||||
# verification start
|
# verification start
|
||||||
try:
|
try:
|
||||||
claims = scyther.verify()
|
claims = scyther.verify(storePopen=self.storePopen)
|
||||||
except Scyther.Error.ScytherError, el:
|
except Scyther.Error.ScytherError, el:
|
||||||
claims = None
|
claims = None
|
||||||
pass
|
pass
|
||||||
@ -185,6 +198,9 @@ class VerificationWindow(wx.Dialog):
|
|||||||
self.SetSizer(sizer)
|
self.SetSizer(sizer)
|
||||||
sizer.Fit(self)
|
sizer.Fit(self)
|
||||||
|
|
||||||
|
self.Center()
|
||||||
|
self.Show(True)
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
class ErrorWindow(wx.Dialog):
|
class ErrorWindow(wx.Dialog):
|
||||||
@ -398,7 +414,6 @@ class ResultWindow(wx.Frame):
|
|||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class ScytherRun(object):
|
class ScytherRun(object):
|
||||||
|
|
||||||
def __init__(self,mainwin,mode,spdl,errorcallback=None):
|
def __init__(self,mainwin,mode,spdl,errorcallback=None):
|
||||||
@ -409,8 +424,23 @@ class ScytherRun(object):
|
|||||||
self.verified = False
|
self.verified = False
|
||||||
self.options = mainwin.settings.ScytherArguments(mode)
|
self.options = mainwin.settings.ScytherArguments(mode)
|
||||||
self.errorcallback=errorcallback
|
self.errorcallback=errorcallback
|
||||||
|
self.SThread = None
|
||||||
|
|
||||||
self.main()
|
self.main()
|
||||||
|
|
||||||
|
def closer(self,ev):
|
||||||
|
# Triggered when the window is closed/verification cancelled
|
||||||
|
t = self.SThread
|
||||||
|
if t != None:
|
||||||
|
self.SThread = None
|
||||||
|
t.cleanExit()
|
||||||
|
try:
|
||||||
|
self.verifywin.Destroy()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.verifywin = None
|
||||||
|
ev.Skip()
|
||||||
|
|
||||||
def main(self):
|
def main(self):
|
||||||
"""
|
"""
|
||||||
Start process
|
Start process
|
||||||
@ -422,8 +452,6 @@ class ScytherRun(object):
|
|||||||
# if the thread terminames, it should close the window normally, and we end up here as well.
|
# if the thread terminames, it should close the window normally, and we end up here as well.
|
||||||
#val = self.verifywin.ShowModal()
|
#val = self.verifywin.ShowModal()
|
||||||
self.verifywin = VerificationWindow(self.mainwin,title)
|
self.verifywin = VerificationWindow(self.mainwin,title)
|
||||||
self.verifywin.Center()
|
|
||||||
self.verifywin.Show(True)
|
|
||||||
|
|
||||||
# Check sanity of Scyther thing here (as opposed to the thread)
|
# Check sanity of Scyther thing here (as opposed to the thread)
|
||||||
# which makes error reporting somewhat easier
|
# which makes error reporting somewhat easier
|
||||||
@ -435,10 +463,13 @@ class ScytherRun(object):
|
|||||||
Error.ShowAndExit(text)
|
Error.ShowAndExit(text)
|
||||||
|
|
||||||
# start the thread
|
# start the thread
|
||||||
|
|
||||||
self.verifywin.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
|
self.verifywin.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
|
||||||
t = ScytherThread(self.spdl, self.options, self.verificationDone, self.mode)
|
self.verifywin.Bind(wx.EVT_CLOSE, self.closer)
|
||||||
t.start()
|
self.verifywin.Bind(wx.EVT_WINDOW_DESTROY, self.closer)
|
||||||
|
self.verifywin.Bind(wx.EVT_BUTTON, self.closer, id=wx.ID_CANCEL)
|
||||||
|
|
||||||
|
self.SThread = ScytherThread(self.spdl, self.options, self.verificationDone, self.mode)
|
||||||
|
self.SThread.start()
|
||||||
|
|
||||||
# after verification, we proceed to the callback below...
|
# after verification, we proceed to the callback below...
|
||||||
|
|
||||||
@ -447,6 +478,9 @@ class ScytherRun(object):
|
|||||||
This is where we end up after a callback from the thread, stating that verification succeeded.
|
This is where we end up after a callback from the thread, stating that verification succeeded.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if self.verifywin == None:
|
||||||
|
return
|
||||||
|
|
||||||
self.scyther = scyther
|
self.scyther = scyther
|
||||||
self.claims = claims
|
self.claims = claims
|
||||||
self.summary = summary
|
self.summary = summary
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
""" Import externals """
|
""" Import externals """
|
||||||
import sys
|
import sys
|
||||||
import os.path
|
import os.path
|
||||||
|
import shlex
|
||||||
try:
|
try:
|
||||||
from subprocess import Popen,PIPE
|
from subprocess import Popen,PIPE
|
||||||
except:
|
except:
|
||||||
@ -89,35 +90,26 @@ def mypath(file):
|
|||||||
basedir = os.path.split(cmd_file)[0]
|
basedir = os.path.split(cmd_file)[0]
|
||||||
return os.path.join(basedir,file)
|
return os.path.join(basedir,file)
|
||||||
|
|
||||||
def getShell():
|
def safeCommandOutput(cmd, storePopen=None):
|
||||||
"""
|
|
||||||
Determine if we want a shell for Popen
|
|
||||||
"""
|
|
||||||
if sys.platform.startswith("win"):
|
|
||||||
shell=False
|
|
||||||
else:
|
|
||||||
# Needed to handle the string input correctly (as opposed to a sequence where the first element is the executable)
|
|
||||||
# This is not needed on Windows, where it has a different effect altogether.
|
|
||||||
# See http://docs.python.org/library/subprocess.html?highlight=subprocess#module-subprocess
|
|
||||||
shell=True
|
|
||||||
return shell
|
|
||||||
|
|
||||||
def safeCommandOutput(cmd):
|
|
||||||
""" Execute a command and return (sts,sout,serr).
|
""" Execute a command and return (sts,sout,serr).
|
||||||
Meant for short outputs, as output is stored in memory and
|
Meant for short outputs, as output is stored in memory and
|
||||||
not written to a file.
|
not written to a file.
|
||||||
"""
|
"""
|
||||||
p = Popen(cmd, shell=getShell(), stdout=PIPE, stderr=PIPE)
|
p = Popen(shlex.split(cmd), shell=False, stdout=PIPE, stderr=PIPE)
|
||||||
|
if storePopen != None:
|
||||||
|
storePopen(p)
|
||||||
(sout,serr) = p.communicate()
|
(sout,serr) = p.communicate()
|
||||||
|
|
||||||
return (p.returncode,sout,serr)
|
return (p.returncode,sout,serr)
|
||||||
|
|
||||||
def safeCommand(cmd):
|
def safeCommand(cmd, storePopen=None):
|
||||||
""" Execute a command with some arguments. Safe cross-platform
|
""" Execute a command with some arguments. Safe cross-platform
|
||||||
version, I hope. """
|
version, I hope. """
|
||||||
|
|
||||||
try:
|
try:
|
||||||
p = Popen(cmd, shell=getShell())
|
p = Popen(shlex.split(cmd), shell=False)
|
||||||
|
if storePopen != None:
|
||||||
|
storePopen(p)
|
||||||
sts = p.wait()
|
sts = p.wait()
|
||||||
except KeyboardInterrupt, EnvironmentError:
|
except KeyboardInterrupt, EnvironmentError:
|
||||||
raise
|
raise
|
||||||
|
@ -265,7 +265,7 @@ class Scyther(object):
|
|||||||
for arg in arglist:
|
for arg in arglist:
|
||||||
self.options += " %s" % (arg)
|
self.options += " %s" % (arg)
|
||||||
|
|
||||||
def doScytherCommand(self, spdl, args, checkKnown=False):
|
def doScytherCommand(self, spdl, args, checkKnown=False, storePopen=None):
|
||||||
"""
|
"""
|
||||||
Cached version of the 'real' below
|
Cached version of the 'real' below
|
||||||
|
|
||||||
@ -290,7 +290,7 @@ class Scyther(object):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
# Need to compute
|
# Need to compute
|
||||||
return self.doScytherCommandReal(spdl,args)
|
return self.doScytherCommandReal(spdl,args, storePopen=storePopen)
|
||||||
|
|
||||||
# Apparently we are supporsed to be able to use the cache
|
# Apparently we are supporsed to be able to use the cache
|
||||||
m = hashlib.sha256()
|
m = hashlib.sha256()
|
||||||
@ -347,7 +347,7 @@ class Scyther(object):
|
|||||||
# We were only checking, abort
|
# We were only checking, abort
|
||||||
return False
|
return False
|
||||||
|
|
||||||
(out,err) = self.doScytherCommandReal(spdl,args)
|
(out,err) = self.doScytherCommandReal(spdl,args, storePopen=storePopen)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Try to store result in cache
|
# Try to store result in cache
|
||||||
@ -366,13 +366,14 @@ class Scyther(object):
|
|||||||
return (out,err)
|
return (out,err)
|
||||||
|
|
||||||
|
|
||||||
def doScytherCommandReal(self, spdl, args):
|
def doScytherCommandReal(self, spdl, args, storePopen=None):
|
||||||
"""
|
"""
|
||||||
Run Scyther backend on the input
|
Run Scyther backend on the input
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
spdl -- string describing the spdl text
|
spdl -- string describing the spdl text
|
||||||
args -- arguments for the command-line
|
args -- arguments for the command-line
|
||||||
|
storePopen -- callback function to register Popen objects (used for process kill by other threads)
|
||||||
Returns:
|
Returns:
|
||||||
(output,errors)
|
(output,errors)
|
||||||
output -- string which is the real output
|
output -- string which is the real output
|
||||||
@ -415,7 +416,7 @@ class Scyther(object):
|
|||||||
##print self.cmd
|
##print self.cmd
|
||||||
|
|
||||||
# Start the process
|
# Start the process
|
||||||
safeCommand(self.cmd)
|
safeCommand(self.cmd, storePopen=storePopen)
|
||||||
|
|
||||||
# reseek
|
# reseek
|
||||||
fhe = os.fdopen(fde)
|
fhe = os.fdopen(fde)
|
||||||
@ -437,7 +438,7 @@ class Scyther(object):
|
|||||||
""" Sanitize some of the input """
|
""" Sanitize some of the input """
|
||||||
self.options = EnsureString(self.options)
|
self.options = EnsureString(self.options)
|
||||||
|
|
||||||
def verify(self,extraoptions=None,checkKnown=False):
|
def verify(self,extraoptions=None,checkKnown=False,storePopen=None):
|
||||||
""" Should return a list of results """
|
""" Should return a list of results """
|
||||||
""" If checkKnown == True, we do not call Scyther, but just check the cache, and return True iff the result is in the cache """
|
""" If checkKnown == True, we do not call Scyther, but just check the cache, and return True iff the result is in the cache """
|
||||||
|
|
||||||
@ -455,10 +456,10 @@ class Scyther(object):
|
|||||||
|
|
||||||
# Are we only checking the cache?
|
# Are we only checking the cache?
|
||||||
if checkKnown == True:
|
if checkKnown == True:
|
||||||
return self.doScytherCommand(self.spdl, args, checkKnown=checkKnown)
|
return self.doScytherCommand(self.spdl, args, checkKnown=checkKnown, storePopen=storePopen)
|
||||||
|
|
||||||
# execute
|
# execute
|
||||||
(output,errors) = self.doScytherCommand(self.spdl, args)
|
(output,errors) = self.doScytherCommand(self.spdl, args, storePopen=storePopen)
|
||||||
self.run = True
|
self.run = True
|
||||||
|
|
||||||
# process errors
|
# process errors
|
||||||
@ -504,7 +505,7 @@ class Scyther(object):
|
|||||||
else:
|
else:
|
||||||
return self.output
|
return self.output
|
||||||
|
|
||||||
def verifyOne(self,cl=None,checkKnown=False):
|
def verifyOne(self,cl=None,checkKnown=False,storePopen=None):
|
||||||
"""
|
"""
|
||||||
Verify just a single claim with an ID retrieved from the
|
Verify just a single claim with an ID retrieved from the
|
||||||
procedure below, 'scanClaims', or a full claim object
|
procedure below, 'scanClaims', or a full claim object
|
||||||
@ -515,10 +516,10 @@ class Scyther(object):
|
|||||||
# We accept either a claim or a claim id
|
# We accept either a claim or a claim id
|
||||||
if isinstance(cl,Claim.Claim):
|
if isinstance(cl,Claim.Claim):
|
||||||
cl = cl.id
|
cl = cl.id
|
||||||
return self.verify("--filter=%s" % cl, checkKnown=checkKnown)
|
return self.verify("--filter=%s" % cl, checkKnown=checkKnown,storePopen=storePopen)
|
||||||
else:
|
else:
|
||||||
# If no claim, then its just normal verification
|
# If no claim, then its just normal verification
|
||||||
return self.verify(checkKnown=checkKnown)
|
return self.verify(checkKnown=checkKnown,storePopen=storePopen)
|
||||||
|
|
||||||
def scanClaims(self):
|
def scanClaims(self):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user