Are you using client-side or server-side editing?:
Client-side only (multi-root workspace: one folder per namespace, each with a folder-level objectscript.conn, plus a project working-directory folder)
- VS Code Version: 1.127.0
- Extension Version: 3.8.2
- OS: Linux (WSL2)
- Server
$ZVERSION:
- lastly observed: IRIS for UNIX (Ubuntu Server LTS for x86-64 Containers) 2024.1.6 (Build 825U) Tue Mar 24 2026 15:11:45 EDT
- issue is also present in newer versions of IRIS
Summary
In a multi-root workspace with many namespace folders on the same iris instance, activating connections exhausts the server's CSP session/license limit far faster than the number of namespaces. Any change to objectscript.conn forces every already-active connection to reconnect, and each reconnect orphans its previous CSP session on the server instead of reusing or logging it out. The stranded sessions accumulate until the license limit is hit.
This happens in both of these scenarios:
- Activating connections one-by-one (edit
active: true per folder) — session count grows super-linearly (table below).
- Activating all connections at once (e.g.
git revert/git restore that rewrites every folder's settings.json back to active: true simultaneously) — also exhausts the license.
The "all at once" case is the clearest demonstration of the root cause: a single config-change pass loops over all N active folders sequentially, and because they share one cookie slot (see below), each iteration drops the prior folder's cookie (orphaning that server session) and mints a new one — so one pass alone strands ~N−1 sessions, before accounting for a multi-file git write firing the handler more than once.
With 10 namespaces on a ~25-session license, I reproducibly run out of sessions (around the 7th activation in the one-by-one case).
Steps to Reproduce
- Create a multi-root workspace (client-side editing) with N folders, each pointing to a different namespace on the same server via a folder-level
objectscript.conn (server + ns + active), with the server defined in the .code-workspace file.
- Start with all connections inactive, then activate them one at a time by setting
objectscript.conn.active = true in each folder's .vscode/settings.json.
- Watch the CSP session count in the server's management portal.
Alternatively, activate them all at once — e.g. git revert/git restore a commit that set every folder's objectscript.conn.active back to true — and observe the session count spike well past the number of namespaces in a single go.
Observed CSP session growth (10 namespaces, activated one at a time)
| Activate namespace # |
CSP sessions |
delta |
| 1 |
1 |
+1 |
| 2 |
3 |
+2 |
| 3 |
6 |
+3 |
| 4 |
11 |
+5 |
| 5 |
17 |
+6 |
| 6 |
23 |
+6 |
| 7 |
— |
⚡ HTTP parse error — session/license limit reached (see below) |
The per-activation cost grows with the number of already-active connections — the signature of "reconnect all active folders on every config change."
License Usage (Management Portal → System Operation → License Usage):
Error observed when the limit is hit
Once the session/license cap is reached, requests no longer return a clean HTTP status. Instead the Node HTTP client throws:
Error: Parse Error: Data after `Connection: close`
This is a transport / HTTP-parse-level failure (Node's llhttp strict parser rejecting a malformed or prematurely-closed response from the web gateway once it can no longer serve the request) — not an HTTP 401 or 503. Because it is a thrown error rather than a response object, it bypasses the extension's response.status === 503 ("Check License Usage") handling in src/api/index.ts and instead lands in the generic .catch in checkConnection, which calls handleError(...) and then setConnectionState(configName, false).
Expected
Activating N namespaces on one server+user should consume ~1 shared CSP session (they already share one cookie jar entry — see below), or at most N. It should not scale super-linearly, and reconnects should not strand server sessions.
Root cause analysis (code references, current master)
1. Any change to objectscript.conn re-checks every workspace folder with clearCookies=true.
src/extension.ts onDidChangeConfiguration:
if (affectsConfiguration("objectscript.conn") || affectsConfiguration("intersystems.servers")) {
...
for await (const folder of vscode.workspace.workspaceFolders ?? []) {
...
await checkConnection(true, folder.uri, true); // clearCookies = true
}
Activating one connection edits objectscript.conn, so this loop reconnects all active folders.
2. checkConnection(clearCookies=true) deletes the local session cookie, but never logs the server session out.
src/api/index.ts:
public clearCookies(): void {
cookiesMap.delete(this.mapKey()); // local only — no IRISLogout/CacheLogout
}
With the cookie gone, serverInfo() → request() fires a bootstrap HEAD /api/atelier/ with Basic auth, and IRIS mints a new CSPSESSIONID. The previously-created server session is not logged out; it lingers until the CSP session timeout. Explicit IRISLogout/CacheLogout only happens in deactivate() (extension shutdown), and only for the single current cookie per folder — never for these intermediate orphaned sessions.
3. The shared cookie key makes the loop thrash a single slot.
src/api/index.ts mapKey() keys the cookie jar by username@host:port/pathPrefix — namespace is intentionally excluded:
private mapKey(): string {
const { host, port, username } = this.config;
...
return `${username}@${host}:${port}${pathPrefix}`;
}
So all namespace folders on the same server+user share one cookie slot. Inside the config-change loop (for await, sequential) over K active folders, each iteration clearCookies() deletes the previous folder's cookie and re-handshakes — orphaning ~K−1 sessions in a single pass. The sharing that should save sessions instead causes churn.
Net effect:
- One-by-one: activating connection #K triggers a loop over the ~K active folders, minting ~K fresh
HEAD logins, all but one orphaned server-side. Orphans accumulate across activations until the license cap is reached — matching the observed 1, 3, 6, 11, 17, 23 progression.
- All at once (git revert/restore): the same loop runs over all N folders in one pass (and a multi-file settings write may fire the handler more than once), stranding ~N−1 sessions per pass — enough to blow past the license on its own.
Side effect: conn.active gets rewritten to false in .vscode/settings.json
When a reconnect fails — including the Parse Error: Data after `Connection: close` thrown once the session limit is reached, as well as 401s that can't re-auth or timeouts — checkConnection persists the failure via setConnectionState(configName, false) (src/extension.ts). Because objectscript.conn is defined at folder level, the write targets ConfigurationTarget.WorkspaceFolder, rewriting each folder's own .vscode/settings.json — which shows up as unexpected git changes. So hitting the session limit silently disables connections on disk. (Note the extension's dedicated 503 "Check License Usage" branch never runs here, because the limit surfaces as a thrown parse error rather than a 503 response.)
Suggested directions
- In the
onDidChangeConfiguration loop, skip folders whose resolved connection details are unchanged (only re-check the folder(s) that actually changed), instead of clearCookies-reconnecting every folder.
- Before dropping a cookie in
clearCookies() / a forced reconnect, send IRISLogout: "end" (or CacheLogout: "end" for < 2018.2) for the session being discarded, so it isn't stranded server-side.
- Avoid flipping
conn.active to false on transient / session-limit failures — including the Parse Error: Data after `Connection: close` case, which currently isn't recognized as a license/session condition and is treated like any other fatal error — so users don't get spurious settings writes when they're merely over the session limit.
Related
Are you using client-side or server-side editing?:
Client-side only (multi-root workspace: one folder per namespace, each with a folder-level
objectscript.conn, plus a project working-directory folder)$ZVERSION:Summary
In a multi-root workspace with many namespace folders on the same iris instance, activating connections exhausts the server's CSP session/license limit far faster than the number of namespaces. Any change to
objectscript.connforces every already-active connection to reconnect, and each reconnect orphans its previous CSP session on the server instead of reusing or logging it out. The stranded sessions accumulate until the license limit is hit.This happens in both of these scenarios:
active: trueper folder) — session count grows super-linearly (table below).git revert/git restorethat rewrites every folder'ssettings.jsonback toactive: truesimultaneously) — also exhausts the license.The "all at once" case is the clearest demonstration of the root cause: a single config-change pass loops over all N active folders sequentially, and because they share one cookie slot (see below), each iteration drops the prior folder's cookie (orphaning that server session) and mints a new one — so one pass alone strands ~N−1 sessions, before accounting for a multi-file git write firing the handler more than once.
With 10 namespaces on a ~25-session license, I reproducibly run out of sessions (around the 7th activation in the one-by-one case).
Steps to Reproduce
objectscript.conn(server+ns+active), with the server defined in the.code-workspacefile.objectscript.conn.active = truein each folder's.vscode/settings.json.Alternatively, activate them all at once — e.g.
git revert/git restorea commit that set every folder'sobjectscript.conn.activeback totrue— and observe the session count spike well past the number of namespaces in a single go.Observed CSP session growth (10 namespaces, activated one at a time)
The per-activation cost grows with the number of already-active connections — the signature of "reconnect all active folders on every config change."
License Usage (Management Portal → System Operation → License Usage):
Error observed when the limit is hit
Once the session/license cap is reached, requests no longer return a clean HTTP status. Instead the Node HTTP client throws:
This is a transport / HTTP-parse-level failure (Node's llhttp strict parser rejecting a malformed or prematurely-closed response from the web gateway once it can no longer serve the request) — not an HTTP 401 or 503. Because it is a thrown error rather than a response object, it bypasses the extension's
response.status === 503("Check License Usage") handling insrc/api/index.tsand instead lands in the generic.catchincheckConnection, which callshandleError(...)and thensetConnectionState(configName, false).Expected
Activating N namespaces on one server+user should consume ~1 shared CSP session (they already share one cookie jar entry — see below), or at most N. It should not scale super-linearly, and reconnects should not strand server sessions.
Root cause analysis (code references, current
master)1. Any change to
objectscript.connre-checks every workspace folder withclearCookies=true.src/extension.tsonDidChangeConfiguration:Activating one connection edits
objectscript.conn, so this loop reconnects all active folders.2.
checkConnection(clearCookies=true)deletes the local session cookie, but never logs the server session out.src/api/index.ts:With the cookie gone,
serverInfo()→request()fires a bootstrapHEAD /api/atelier/with Basic auth, and IRIS mints a new CSPSESSIONID. The previously-created server session is not logged out; it lingers until the CSP session timeout. ExplicitIRISLogout/CacheLogoutonly happens indeactivate()(extension shutdown), and only for the single current cookie per folder — never for these intermediate orphaned sessions.3. The shared cookie key makes the loop thrash a single slot.
src/api/index.tsmapKey()keys the cookie jar byusername@host:port/pathPrefix— namespace is intentionally excluded:So all namespace folders on the same server+user share one cookie slot. Inside the config-change loop (
for await, sequential) over K active folders, each iterationclearCookies()deletes the previous folder's cookie and re-handshakes — orphaning ~K−1 sessions in a single pass. The sharing that should save sessions instead causes churn.Net effect:
HEADlogins, all but one orphaned server-side. Orphans accumulate across activations until the license cap is reached — matching the observed 1, 3, 6, 11, 17, 23 progression.Side effect:
conn.activegets rewritten tofalsein.vscode/settings.jsonWhen a reconnect fails — including the
Parse Error: Data after `Connection: close`thrown once the session limit is reached, as well as 401s that can't re-auth or timeouts —checkConnectionpersists the failure viasetConnectionState(configName, false)(src/extension.ts). Becauseobjectscript.connis defined at folder level, the write targetsConfigurationTarget.WorkspaceFolder, rewriting each folder's own.vscode/settings.json— which shows up as unexpected git changes. So hitting the session limit silently disables connections on disk. (Note the extension's dedicated 503 "Check License Usage" branch never runs here, because the limit surfaces as a thrown parse error rather than a 503 response.)Suggested directions
onDidChangeConfigurationloop, skip folders whose resolved connection details are unchanged (only re-check the folder(s) that actually changed), instead ofclearCookies-reconnecting every folder.clearCookies()/ a forced reconnect, sendIRISLogout: "end"(orCacheLogout: "end"for < 2018.2) for the session being discarded, so it isn't stranded server-side.conn.activetofalseon transient / session-limit failures — including theParse Error: Data after `Connection: close`case, which currently isn't recognized as a license/session condition and is treated like any other fatal error — so users don't get spurious settings writes when they're merely over the session limit.Related