Remove race condition in workspace initialisation if iam-svc is up (#867)

Remove race condition in workspace initialisation if iam-svc is up
before config-svc.

iam.py — handle_create_workspace:
- Config registration (_on_workspace_created) moves before the IAM
  table write, so it's a prerequisite. If the config put fails,
  the exception propagates and the IAM create doesn't happen.
- On duplicate, the IAM table write is skipped but config
  registration still runs (idempotent put). Returns the existing
  record with no error instead of returning _err("duplicate", ...).

service.py — _announce_workspace_created → _ensure_workspace_registered:
- Renamed to reflect the new semantics.
- Exceptions propagate instead of being swallowed — if config
  registration fails, the caller sees the error.
This commit is contained in:
cybermaggedon 2026-05-06 21:21:48 +01:00 committed by GitHub
parent d282d72db1
commit fe542b3d33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 20 additions and 28 deletions

View file

@ -901,22 +901,20 @@ class IamService:
"workspace ids beginning with '_' are reserved",
)
if self._on_workspace_created:
await self._on_workspace_created(v.workspace_record.id)
existing = await self.table_store.get_workspace(
v.workspace_record.id,
)
if existing is not None:
return _err("duplicate", "workspace already exists")
now = _now_dt()
await self.table_store.put_workspace(
id=v.workspace_record.id,
name=v.workspace_record.name or v.workspace_record.id,
enabled=v.workspace_record.enabled,
created=now,
)
if self._on_workspace_created:
await self._on_workspace_created(v.workspace_record.id)
if existing is None:
now = _now_dt()
await self.table_store.put_workspace(
id=v.workspace_record.id,
name=v.workspace_record.name or v.workspace_record.id,
enabled=v.workspace_record.enabled,
created=now,
)
row = await self.table_store.get_workspace(v.workspace_record.id)
return IamResponse(workspace=self._row_to_workspace_record(row))

View file

@ -151,7 +151,7 @@ class Processor(AsyncProcessor):
keyspace=keyspace,
bootstrap_mode=self.bootstrap_mode,
bootstrap_token=self.bootstrap_token,
on_workspace_created=self._announce_workspace_created,
on_workspace_created=self._ensure_workspace_registered,
on_workspace_deleted=self._announce_workspace_deleted,
)
@ -218,20 +218,14 @@ class Processor(AsyncProcessor):
finally:
await client.stop()
async def _announce_workspace_created(self, workspace_id):
try:
await self._config_put(
"__workspaces__", "workspace", workspace_id,
'{"enabled": true}',
)
logger.info(
f"Announced workspace creation: {workspace_id}"
)
except Exception as e:
logger.error(
f"Failed to announce workspace creation "
f"{workspace_id}: {e}", exc_info=True,
)
async def _ensure_workspace_registered(self, workspace_id):
await self._config_put(
"__workspaces__", "workspace", workspace_id,
'{"enabled": true}',
)
logger.info(
f"Registered workspace in config: {workspace_id}"
)
async def _announce_workspace_deleted(self, workspace_id):
try: