Found while implementing #13578 (driver-registry eviction). Filed separately rather than fixed there: the repair is a behavioural decision, not a mechanical one.
The defect
PATCH/update of a runtime datasource never rebuilds its driver. The OLD driver instance, built from the OLD config, stays live and registered.
Path, on origin/maineb717a12:
DatasourceAdminService.updateDatasource persists the merged record, then calls tryRegisterPool(merged).- That reaches
DatasourceConnectionService.connect -> attemptConnect, which opens with an idempotency guard: when engine.getDriverByName(name) already answers, it returns already-registered and returns before building anything. - Nothing on the update path calls
disconnect first.
So reconfiguring a datasource — new host, new credentials, new pool settings, active: false — changes the stored record and leaves the running connection exactly as it was, until the process restarts.
Why it reads as working
toSummary reports the connect verdict right after tryRegisterPool, and the retained verdict from the ORIGINAL connect is still connected. So the admin UI shows a successful save with an ok status, describing a pool that is not the one the record now declares.
This is also why #13578's symptom survives the other natural operator reaction: an administrator who tries to FIX a bad datasource by editing it gets the same non-recovery as one who deletes it did.
Why #13578 did not fix it
#13578 gave the registry the eviction primitive it lacked (IObjectQLEngine.unregisterDriver), and wired the paths that already funnel through teardown. Update does not funnel through teardown at all — it is a missing teardown call, and adding one is a behaviour change with real cost in both directions:
- a hot pool swap on every update churns the connection for edits that do not touch connectivity (a label change);
- if the rebuild fails, the operator has lost a pool that was working, on a path whose documented policy is that runtime-admin writes "must never brick a running server over a UI action".
Both directions are defensible; picking one is a product call about what "Save" means for a live datasource.
Suggested direction
Rebuild only when the merged record's connectivity-bearing fields actually changed (driver, config, external, pool, credentialsRef, active), and on failure keep the old pool with a loud degraded verdict rather than leaving the datasource pool-less. The eviction primitive that makes an in-place swap expressible now exists.
Refs
Generated by Claude Code
Found while implementing #13578 (driver-registry eviction). Filed separately rather than fixed there: the repair is a behavioural decision, not a mechanical one.
The defect
PATCH/update of a runtime datasource never rebuilds its driver. The OLD driver instance, built from the OLD config, stays live and registered.Path, on
origin/maineb717a12:DatasourceAdminService.updateDatasourcepersists the merged record, then callstryRegisterPool(merged).DatasourceConnectionService.connect->attemptConnect, which opens with an idempotency guard: whenengine.getDriverByName(name)already answers, it returnsalready-registeredand returns before building anything.disconnectfirst.So reconfiguring a datasource — new host, new credentials, new pool settings,
active: false— changes the stored record and leaves the running connection exactly as it was, until the process restarts.Why it reads as working
toSummaryreports the connect verdict right aftertryRegisterPool, and the retained verdict from the ORIGINAL connect is stillconnected. So the admin UI shows a successful save with anokstatus, describing a pool that is not the one the record now declares.This is also why #13578's symptom survives the other natural operator reaction: an administrator who tries to FIX a bad datasource by editing it gets the same non-recovery as one who deletes it did.
Why #13578 did not fix it
#13578 gave the registry the eviction primitive it lacked (
IObjectQLEngine.unregisterDriver), and wired the paths that already funnel through teardown. Update does not funnel through teardown at all — it is a missing teardown call, and adding one is a behaviour change with real cost in both directions:Both directions are defensible; picking one is a product call about what "Save" means for a live datasource.
Suggested direction
Rebuild only when the merged record's connectivity-bearing fields actually changed (driver, config, external, pool, credentialsRef, active), and on failure keep the old pool with a loud degraded verdict rather than leaving the datasource pool-less. The eviction primitive that makes an in-place swap expressible now exists.
Refs
/api/v1/meta/datasourcemetadata registry. Distinct sink.Generated by Claude Code