Repository: ckan/ckan

Issue:
Intermittent duplicate key errors when creating default sysadmin
**CKAN version**

CKAN 2.9.x. Does not appear to affect 2.8.

**Describe the bug**

When our continuous integration tests initialise a test CKAN 2.9 instance, they sometimes encounter a duplicate key exception when CKAN is attempting to create the 'default' sysadmin account. There does not appear to be any specific pattern to when the errors occur; re-running the build may resolve it, or not. This did not occur on CKAN 2.8.

**Steps to reproduce**

Our test scripts run the following CLI commands:

ckan -c /etc/ckan/default/production.ini db clean --yes
ckan -c /etc/ckan/default/production.ini db init
ckan -c /etc/ckan/default/production.ini comments initdb
ckan -c /etc/ckan/default/production.ini comments updatedb
ckan -c /etc/ckan/default/production.ini comments init_notifications_db

**Additional details**
Sample failed build:

https://github.com/qld-gov-au/ckanext-ytp-comments/runs/4480610398?check_suite_focus=true

(Search for "already exists" to locate the stack trace)

NB Although the error actually occurred on that run during a `ckanext-ytp-comments` command, it was not specific to the extension, as shown by an excerpt from the stack trace:

        ckan_1                |   File "/app/ckan/default/src/ckan/ckan/cli/cli.py", line 102, in _init_ckan_config
        ckan_1                |     ctx.obj = CkanCommand(value)
        ckan_1                |   File "/app/ckan/default/src/ckan/ckan/cli/cli.py", line 52, in __init__
        ckan_1                |     self.app = make_app(self.config)
        ckan_1                |   File "/app/ckan/default/src/ckan/ckan/config/middleware/__init__.py", line 56, in make_app
        ckan_1                |     load_environment(conf)
        ckan_1                |   File "/app/ckan/default/src/ckan/ckan/config/environment.py", line 123, in load_environment
        ckan_1                |     p.load_all()
        ckan_1                |   File "/app/ckan/default/src/ckan/ckan/plugins/core.py", line 161, in load_all
        ckan_1                |     unload_all()
        ckan_1                |   File "/app/ckan/default/src/ckan/ckan/plugins/core.py", line 208, in unload_all
        ckan_1                |     unload(*reversed(_PLUGINS))
        ckan_1                |   File "/app/ckan/default/src/ckan/ckan/plugins/core.py", line 236, in unload
        ckan_1                |     plugins_update()
        ckan_1                |   File "/app/ckan/default/src/ckan/ckan/plugins/core.py", line 153, in plugins_update
        ckan_1                |     environment.update_config()
        ckan_1                |   File "/app/ckan/default/src/ckan/ckan/config/environment.py", line 322, in update_config
        ckan_1                |     logic.get_action('get_site_user')({'ignore_auth': True}, None)
        ckan_1                |   File "/app/ckan/default/src/ckan/ckan/logic/__init__.py", line 477, in wrapped
        ckan_1                |     result = _action(context, data_dict, **kw)
        ckan_1                |   File "/app/ckan/default/src/ckan/ckan/logic/action/get.py", line 2398, in get_site_user
        ckan_1                |     model.Session.flush()


Discussion:
Does `ckan.logic.action.get:get_site_user` need some kind of transaction lock, to prevent race conditions between checking the presence of a user, and trying to add that user? Something like https://stackoverflow.com/questions/14520340/sqlalchemy-and-explicit-locking

Resolving patch:
diff --git a/ckan/config/environment.py b/ckan/config/environment.py
--- a/ckan/config/environment.py
+++ b/ckan/config/environment.py
@@ -227,6 +227,9 @@ def update_config():
     except (sqlalchemy.exc.ProgrammingError, sqlalchemy.exc.OperationalError):
         # The database is not yet initialised. It happens in `ckan db init`
         pass
+    except sqlalchemy.exc.IntegrityError:
+        # Race condition, user already exists.
+        pass
 
     # Close current session and open database connections to ensure a clean
     # clean environment even if an error occurs later on


Test patch: