Windows: Fix error on first run of calibre after install/upgrade

Apparently python's zipimport on windows is not thread safe. And the
register default programs code was running ina  thread at the same time
as the calibre gui was initializing causing imports to fail.

Workaround it by ensuring that no imports happen in the register default
programs thread.

Fixes #1897314 [Execution problem from the installer](https://bugs.launchpad.net/calibre/+bug/1897314)
This commit is contained in:
Kovid Goyal 2020-09-27 13:38:29 +05:30
parent 912cd9813b
commit 9eb68319cd
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C

View File

@ -5,7 +5,7 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import os, sys, time, ctypes import os, sys, time, ctypes, traceback
from ctypes.wintypes import HLOCAL, LPCWSTR from ctypes.wintypes import HLOCAL, LPCWSTR
from threading import Thread from threading import Thread
@ -14,6 +14,7 @@ import winerror
from calibre import guess_type, prints from calibre import guess_type, prints
from calibre.constants import is64bit, isportable, isfrozen, __version__, DEBUG, plugins from calibre.constants import is64bit, isportable, isfrozen, __version__, DEBUG, plugins
from calibre.utils.winreg.lib import Key, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE from calibre.utils.winreg.lib import Key, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE
from calibre.utils.lock import singleinstance
from polyglot.builtins import iteritems, itervalues from polyglot.builtins import iteritems, itervalues
# See https://msdn.microsoft.com/en-us/library/windows/desktop/cc144154(v=vs.85).aspx # See https://msdn.microsoft.com/en-us/library/windows/desktop/cc144154(v=vs.85).aspx
@ -102,6 +103,12 @@ def cap_path(data):
return r'Software\calibre\%s\Capabilities' % data['capability_name'] return r'Software\calibre\%s\Capabilities' % data['capability_name']
def pre_import_extensions():
for program in default_programs():
ext_map = {ext.lower():guess_type('file.' + ext.lower())[0] for ext in extensions(program)}
ext_map = {ext:mt for ext, mt in iteritems(ext_map) if mt}
def register(): def register():
base = os.path.dirname(sys.executable) base = os.path.dirname(sys.executable)
@ -160,17 +167,19 @@ class Register(Thread):
def __init__(self, prefs): def __init__(self, prefs):
Thread.__init__(self, name='RegisterDP') Thread.__init__(self, name='RegisterDP')
self.prefs = prefs self.prefs = prefs
pre_import_extensions()
self.start() self.start()
def run(self): def run(self):
# make sure no imports happen in this thread as python's zipimport
# machinery is not thread safe and main GUI importing is happening
# in parallel
try: try:
self.do_register() self.do_register()
except Exception: except Exception:
import traceback
traceback.print_exc() traceback.print_exc()
def do_register(self): def do_register(self):
from calibre.utils.lock import singleinstance
try: try:
check_allowed() check_allowed()
except NotAllowed: except NotAllowed:
@ -179,11 +188,11 @@ class Register(Thread):
if self.prefs.get('windows_register_default_programs', None) != __version__: if self.prefs.get('windows_register_default_programs', None) != __version__:
self.prefs['windows_register_default_programs'] = __version__ self.prefs['windows_register_default_programs'] = __version__
if DEBUG: if DEBUG:
st = time.time() st = time.monotonic()
prints('Registering with default programs...') prints('Registering with default programs...')
register() register()
if DEBUG: if DEBUG:
prints('Registered with default programs in %.1f seconds' % (time.time() - st)) prints('Registered with default programs in %.1f seconds' % (time.monotonic() - st))
def __enter__(self): def __enter__(self):
return self return self
@ -252,7 +261,6 @@ def friendly_app_name(prog_id=None, exe=None):
try: try:
return plugins['winutil'][0].friendly_name(prog_id, exe) return plugins['winutil'][0].friendly_name(prog_id, exe)
except Exception: except Exception:
import traceback
traceback.print_exc() traceback.print_exc()
@ -275,7 +283,6 @@ def find_programs(extensions):
try: try:
app_desc, prog_id_map = get_prog_id_map(base, key_path) app_desc, prog_id_map = get_prog_id_map(base, key_path)
except Exception: except Exception:
import traceback
traceback.print_exc() traceback.print_exc()
continue continue
for ext in extensions: for ext in extensions: