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", "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( existing = await self.table_store.get_workspace(
v.workspace_record.id, v.workspace_record.id,
) )
if existing is not None: if existing is None:
return _err("duplicate", "workspace already exists") now = _now_dt()
await self.table_store.put_workspace(
now = _now_dt() id=v.workspace_record.id,
await self.table_store.put_workspace( name=v.workspace_record.name or v.workspace_record.id,
id=v.workspace_record.id, enabled=v.workspace_record.enabled,
name=v.workspace_record.name or v.workspace_record.id, created=now,
enabled=v.workspace_record.enabled, )
created=now,
)
if self._on_workspace_created:
await self._on_workspace_created(v.workspace_record.id)
row = await self.table_store.get_workspace(v.workspace_record.id) row = await self.table_store.get_workspace(v.workspace_record.id)
return IamResponse(workspace=self._row_to_workspace_record(row)) return IamResponse(workspace=self._row_to_workspace_record(row))

View file

@ -151,7 +151,7 @@ class Processor(AsyncProcessor):
keyspace=keyspace, keyspace=keyspace,
bootstrap_mode=self.bootstrap_mode, bootstrap_mode=self.bootstrap_mode,
bootstrap_token=self.bootstrap_token, 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, on_workspace_deleted=self._announce_workspace_deleted,
) )
@ -218,20 +218,14 @@ class Processor(AsyncProcessor):
finally: finally:
await client.stop() await client.stop()
async def _announce_workspace_created(self, workspace_id): async def _ensure_workspace_registered(self, workspace_id):
try: await self._config_put(
await self._config_put( "__workspaces__", "workspace", workspace_id,
"__workspaces__", "workspace", workspace_id, '{"enabled": true}',
'{"enabled": true}', )
) logger.info(
logger.info( f"Registered workspace in config: {workspace_id}"
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 _announce_workspace_deleted(self, workspace_id): async def _announce_workspace_deleted(self, workspace_id):
try: try: