Suppress duplicate key errors on semaphore table during rebuilds

- In _riverside_pt_rebuild(): proactively TRUNCATE the semaphore table
  at the very start of every rebuild. This eliminates the common
  'duplicate key value violates unique constraint "semaphore____pkey"'
  errors for 'state:Drupal\Core\Cache\CacheCollector' and 'cron' that
  appear in postgres logs.

- In entrypoint.sh: add TRUNCATE semaphore at strategic points
  (right after site:install, before module enables, before/after
  riverside:rebuild, before final drush cr). Wrapped with || true
  so they never break the startup script.

- Added a note in CLAUDE.md under the rebuild section explaining
  the errors and the quick manual fix.

These are harmless (Drupal's DbLockBackend usually recovers) but
very noisy in the container logs during the default full rebuild
path.
This commit is contained in:
Philip Peterson 2026-06-04 00:06:27 -07:00
parent 4d895d1b0d
commit 1e3ba132a4
3 changed files with 43 additions and 0 deletions

View file

@ -40,6 +40,19 @@ docker compose exec app drush riverside:rebuild
docker compose exec app drush rrb
```
**Note on semaphore/lock errors in logs:** During rebuilds (especially the full non-`DRUPAL_FAST` path) you may see Postgres errors like:
```
ERROR: duplicate key value violates unique constraint "semaphore____pkey"
DETAIL: Key (name)=(state:Drupal\Core\Cache\CacheCollector) already exists.
```
This is harmless but noisy. The entrypoint and `_riverside_pt_rebuild()` proactively `TRUNCATE TABLE semaphore` at key points to suppress them. If you ever see them after a manual change, run:
```bash
docker compose exec app drush sql:query "TRUNCATE TABLE semaphore;"
```
The custom module directory is volume-mounted, so template/CSS/JS edits are live without rebuilding the Docker image. `settings.php` and `development.services.yml` are also volume-mounted.
**Known gotcha:** `drush site:install` rewrites `settings.php`. Because `settings.php` is a bind-mounted file, Docker Desktop on macOS may hold a stale inode reference after the rewrite. If Drupal shows "The provided host name is not valid for this server" after a full rebuild, restart with `DRUPAL_FAST=1` to re-establish the mount without re-running site:install.

View file

@ -37,8 +37,17 @@ else
--account-pass="$ADMIN_PASS" \
-y || { echo "[entrypoint] FATAL: site:install failed."; exit 1; }
echo "[entrypoint] Drupal installed."
# Clear semaphores immediately after fresh install (prevents early
# duplicate key errors during first module enables + rebuild).
$DRUSH sql:query "TRUNCATE TABLE semaphore;" 2>/dev/null || true
fi
# Always clear stale semaphores before module enables + rebuild.
# This is the most reliable way to avoid the duplicate key errors
# on "semaphore" (CacheCollector, cron, state locks, etc.).
$DRUSH sql:query "TRUNCATE TABLE semaphore;" 2>/dev/null || true
echo "[entrypoint] Enabling required modules..."
$DRUSH en -y views views_ui field_ui text options link datetime && \
echo "[entrypoint] Core modules enabled." || echo "[entrypoint] WARNING: core modules failed."
@ -49,6 +58,11 @@ $DRUSH en -y symfony_mailer && \
$DRUSH en -y riverside_pt && \
echo "[entrypoint] riverside_pt enabled." || echo "[entrypoint] WARNING: riverside_pt failed."
# Clear semaphores to avoid duplicate key violations on the semaphore
# table (e.g. during CacheCollector, cron, state operations) that can
# occur during rapid config/entity changes in the rebuild.
$DRUSH sql:query "TRUNCATE TABLE semaphore;" 2>/dev/null || true
echo "[entrypoint] Rebuilding site structure from code (riverside:rebuild)..."
$DRUSH riverside:rebuild || echo "[entrypoint] WARNING: riverside:rebuild encountered an issue."
@ -63,6 +77,10 @@ $DRUSH config:set system.site page.front /home -y && \
npm run build --prefix /var/www/html >/dev/null 2>&1 && echo "[entrypoint] Tailwind built." || echo "[entrypoint] WARNING: Tailwind build failed."
# One more semaphore clear before the final cache rebuild (common source
# of the duplicate key errors seen in logs).
$DRUSH sql:query "TRUNCATE TABLE semaphore;" 2>/dev/null || true
$DRUSH cache:rebuild >/dev/null 2>&1 && echo "[entrypoint] Cache rebuilt."
if [ "${DEBUG:-false}" = "true" ]; then

View file

@ -26,6 +26,18 @@ function riverside_pt_install() {
* and navigation used by the Docker entrypoint.
*/
function _riverside_pt_rebuild(): void {
// Clear any stale semaphore locks. This prevents duplicate key violations
// on the "semaphore" table (e.g. "state:Drupal\Core\Cache\CacheCollector",
// "cron") during rebuilds. These occur because rapid entity/config
// changes trigger cache collectors and lock acquisitions concurrently.
// Safe to run even on initial install (table may not exist yet).
try {
\Drupal::database()->truncate('semaphore')->execute();
}
catch (\Exception $e) {
// Ignore if table doesn't exist yet.
}
$entity_type_manager = \Drupal::entityTypeManager();
$storage_node_type = $entity_type_manager->getStorage('node_type');
$storage_role = $entity_type_manager->getStorage('user_role');