Skip to content

Commit 33e7ecc

Browse files
committed
Simplify config unload/reload; add locking
1 parent 8f4362c commit 33e7ecc

File tree

1 file changed

+28
-32
lines changed

1 file changed

+28
-32
lines changed

emailproxy.py

+28-32
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
__author__ = 'Simon Robinson'
77
__copyright__ = 'Copyright (c) 2023 Simon Robinson'
88
__license__ = 'Apache 2.0'
9-
__version__ = '2023-08-16' # ISO 8601 (YYYY-MM-DD)
9+
__version__ = '2023-08-26' # ISO 8601 (YYYY-MM-DD)
1010

1111
import abc
1212
import argparse
@@ -462,7 +462,7 @@ class AppConfig:
462462
"""Helper wrapper around ConfigParser to cache servers/accounts, and avoid writing to the file until necessary"""
463463

464464
_PARSER = None
465-
_LOADED = False
465+
_PARSER_LOCK = threading.Lock()
466466

467467
# note: removing the unencrypted version of `client_secret_encrypted` is not automatic with --cache-store (see docs)
468468
_CACHED_OPTION_KEYS = ['token_salt', 'access_token', 'access_token_expiry', 'refresh_token', 'last_activity',
@@ -473,27 +473,26 @@ class AppConfig:
473473

474474
@staticmethod
475475
def _load():
476-
AppConfig.unload()
477-
AppConfig._PARSER = ConcurrentConfigParser()
478-
AppConfig._PARSER.read(CONFIG_FILE_PATH)
476+
config_parser = ConcurrentConfigParser()
477+
config_parser.read(CONFIG_FILE_PATH)
479478

480479
# cached account credentials can be stored in the configuration file (default) or, via `--cache-store`, a
481480
# separate local file or external service (such as a secrets manager) - we combine these sources at load time
482481
if CACHE_STORE != CONFIG_FILE_PATH:
483482
# it would be cleaner to avoid specific options here, but best to load unexpected sections only when enabled
484-
allow_catch_all_accounts = AppConfig._PARSER.getboolean(APP_SHORT_NAME, 'allow_catch_all_accounts',
485-
fallback=False)
483+
allow_catch_all_accounts = config_parser.getboolean(APP_SHORT_NAME, 'allow_catch_all_accounts',
484+
fallback=False)
486485

487486
cache_file_parser = AppConfig._load_cache(CACHE_STORE)
488487
cache_file_accounts = [s for s in cache_file_parser.sections() if '@' in s]
489488
for account in cache_file_accounts:
490-
if allow_catch_all_accounts and account not in AppConfig._PARSER.sections(): # missing sub-accounts
491-
AppConfig._PARSER.add_section(account)
489+
if allow_catch_all_accounts and account not in config_parser.sections(): # missing sub-accounts
490+
config_parser.add_section(account)
492491
for option in cache_file_parser.options(account):
493492
if option in AppConfig._CACHED_OPTION_KEYS:
494-
AppConfig._PARSER.set(account, option, cache_file_parser.get(account, option))
493+
config_parser.set(account, option, cache_file_parser.get(account, option))
495494

496-
AppConfig._LOADED = True
495+
return config_parser
497496

498497
@staticmethod
499498
def _load_cache(cache_store_identifier):
@@ -507,38 +506,34 @@ def _load_cache(cache_store_identifier):
507506

508507
@staticmethod
509508
def get():
510-
if not AppConfig._LOADED:
511-
AppConfig._load()
512-
return AppConfig._PARSER
509+
with AppConfig._PARSER_LOCK:
510+
if AppConfig._PARSER is None:
511+
AppConfig._PARSER = AppConfig._load()
512+
return AppConfig._PARSER
513513

514514
@staticmethod
515515
def unload():
516-
AppConfig._PARSER = None
517-
AppConfig._LOADED = False
518-
519-
@staticmethod
520-
def reload():
521-
AppConfig.unload()
522-
return AppConfig.get()
516+
with AppConfig._PARSER_LOCK:
517+
AppConfig._PARSER = None
523518

524519
@staticmethod
525520
def get_global(name, fallback):
526-
AppConfig.get() # make sure config is loaded
527-
return AppConfig._PARSER.getboolean(APP_SHORT_NAME, name, fallback)
521+
return AppConfig.get().getboolean(APP_SHORT_NAME, name, fallback)
528522

529523
@staticmethod
530524
def servers():
531-
AppConfig.get() # make sure config is loaded
532-
return [s for s in AppConfig._PARSER.sections() if CONFIG_SERVER_MATCHER.match(s)]
525+
return [s for s in AppConfig.get().sections() if CONFIG_SERVER_MATCHER.match(s)]
533526

534527
@staticmethod
535528
def accounts():
536-
AppConfig.get() # make sure config is loaded
537-
return [s for s in AppConfig._PARSER.sections() if '@' in s]
529+
return [s for s in AppConfig.get().sections() if '@' in s]
538530

539531
@staticmethod
540532
def save():
541-
if AppConfig._LOADED:
533+
with AppConfig._PARSER_LOCK:
534+
if AppConfig._PARSER is None: # intentionally using _PARSER not get() so we don't (re)load if unloaded
535+
return
536+
542537
if CACHE_STORE != CONFIG_FILE_PATH:
543538
# in `--cache-store` mode we ignore everything except _CACHED_OPTION_KEYS (OAuth 2.0 tokens, etc)
544539
output_config_parser = configparser.ConfigParser()
@@ -554,8 +549,7 @@ def save():
554549
if section not in config_accounts or len(output_config_parser.options(section)) <= 0:
555550
output_config_parser.remove_section(section)
556551

557-
with AppConfig._PARSER.lock:
558-
AppConfig._save_cache(CACHE_STORE, output_config_parser)
552+
AppConfig._save_cache(CACHE_STORE, output_config_parser)
559553

560554
else:
561555
# by default we cache to the local configuration file, and rewrite all values each time
@@ -646,7 +640,7 @@ def get_account_with_catch_all_fallback(option):
646640

647641
# try reloading remotely cached tokens if possible
648642
if not access_token and CACHE_STORE != CONFIG_FILE_PATH and recurse_retries:
649-
AppConfig.reload()
643+
AppConfig.unload()
650644
return OAuth2Helper.get_oauth2_credentials(username, password, recurse_retries=False)
651645

652646
# we hash locally-stored tokens with the given password
@@ -2782,7 +2776,9 @@ def load_and_start_servers(self, icon=None, reload=True):
27822776
# we allow reloading, so must first stop any existing servers
27832777
self.stop_servers()
27842778
Log.info('Initialising', APP_NAME, '(version %s)' % __version__, 'from config file', CONFIG_FILE_PATH)
2785-
config = AppConfig.reload() if reload else AppConfig.get()
2779+
if reload:
2780+
AppConfig.unload()
2781+
config = AppConfig.get()
27862782

27872783
# load server types and configurations
27882784
server_load_error = False

0 commit comments

Comments
 (0)