Paul Paul - 2 months ago 13
Python Question

Control-C not stopping thread/spinning cursor

I have code that copies files. When it is doing so a cursor spins. This all works fine. However if I use Ctrl+C to stop the copy I expect the

KeyboardInterrupt
to set the event and the cursor to stop spinning. The copy stops however the cursor spins forever, why is this?

I tried putting a print into the Interrupt and that did not get displayed, so it seems it is not being called?

def spinning_cursor(event):
for j in itertools.cycle('/-\|'):
if not event.is_set():
sys.stdout.write(j)
sys.stdout.flush()
time.sleep(0.1)
sys.stdout.write('\b')
else:
return


def copy_file(sfile, dfile, sync):
processing_flag = Event()
Thread(target = spinning_cursor, kwargs = {'event': processing_flag}).start()
if os.path.exists(sfile):
try:
shutil.copyfile(sfile, dfile)
processing_flag.set()
except KeyboardInterrupt:
processing_flag.set()
pass
...

Answer

It is not really an answer, because I cannot explain what really happens, but I could partially reproduce. It looks like copy_file swallows the KeyboardInterrupt and raises another exception. If that happens, as the processing_flag.set() is only in the normal and except KeyboardInterrupt branches, control never passes there. As a consequence, the Event is never set and the child thread keeps on spinning (that's what I did to reproduce the behaviour...)

Workarounds.

I could find 2 possible workarounds

  • use except without any filtering:

    try:
        shutil.copyfile(sfile, dfile)
        processing_flag.set()
    except:
        processing_flag.set()
        pass
    

    that should be enough to catch any possible exception raised by copyfile

  • use a finally block. I personally prefere that way, because it is really what finally is aimed to: execute that branch whatever could happen. In that case, you could even remove the except block and have just a try:... finally:...:

    try:
        shutil.copyfile(sfile, dfile)
    finally:
        processing_flag.set()
    
Comments