@solarisdk/core 0.4.5 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/handle.js +3 -4
- package/dist/transport.d.ts +11 -0
- package/dist/transport.js +34 -6
- package/package.json +1 -1
package/dist/handle.js
CHANGED
|
@@ -687,11 +687,10 @@ export class SessionHandle {
|
|
|
687
687
|
if (!this.hooks.revert) {
|
|
688
688
|
return Promise.reject(new Error("revert requires a client-created handle"));
|
|
689
689
|
}
|
|
690
|
+
const hadSocket = this.channel.hasSocket;
|
|
690
691
|
return this.hooks.revert(this.id, snapshotId).then(async () => {
|
|
691
|
-
if (
|
|
692
|
-
this.
|
|
693
|
-
await this.channel.connect().catch(() => undefined);
|
|
694
|
-
}
|
|
692
|
+
if (hadSocket)
|
|
693
|
+
await this.channel.refresh();
|
|
695
694
|
});
|
|
696
695
|
}
|
|
697
696
|
async pause() {
|
package/dist/transport.d.ts
CHANGED
|
@@ -81,6 +81,17 @@ export declare class ControlChannel {
|
|
|
81
81
|
* `commands.start` / `pty.create` survive so callers can re-subscribe.
|
|
82
82
|
*/
|
|
83
83
|
reconnect(): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Swap the socket for a fresh, working one after the guest behind it was replaced in place (revert). The old socket
|
|
86
|
+
* is detached first, so its close, arriving whenever it likes (it may even have arrived already), cannot fail calls
|
|
87
|
+
* made on the new connection. Right after a restore the host can still be re-accepting the guest's control
|
|
88
|
+
* connection and closes a connection opened in that window within milliseconds (seen on staging as the first call
|
|
89
|
+
* failing with 1000/1005), so the new connection is proven with a cheap request and re-dialled a few times before
|
|
90
|
+
* this resolves. Never rejects: if no working connection can be had, calls retry lazily.
|
|
91
|
+
*/
|
|
92
|
+
refresh(): Promise<void>;
|
|
93
|
+
/** True while the channel holds a socket, open or not. */
|
|
94
|
+
get hasSocket(): boolean;
|
|
84
95
|
/** Register a "channel closed" callback for a stream id (cmdId/ptyId) so a
|
|
85
96
|
* drop rejects its in-flight wait. Cleared on the stream's terminal frame.
|
|
86
97
|
*
|
package/dist/transport.js
CHANGED
|
@@ -2,6 +2,9 @@ import { ActionError, ConnectionError, SolariError, TimeoutError, } from "./erro
|
|
|
2
2
|
import { openWebSocket } from "./ws.js";
|
|
3
3
|
const DEFAULT_CALL_TIMEOUT_MS = 300_000;
|
|
4
4
|
const CONNECT_TIMEOUT_MS = 15_000;
|
|
5
|
+
const REFRESH_MAX_ATTEMPTS = 4;
|
|
6
|
+
const REFRESH_RETRY_BASE_MS = 300;
|
|
7
|
+
const REFRESH_PROBE_TIMEOUT_MS = 5_000;
|
|
5
8
|
const CONNECT_MAX_ATTEMPTS = 3;
|
|
6
9
|
const CONNECT_RETRY_BASE_MS = 150;
|
|
7
10
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -41,6 +44,7 @@ export class ControlChannel {
|
|
|
41
44
|
return Promise.resolve();
|
|
42
45
|
if (this.connecting)
|
|
43
46
|
return this.connecting;
|
|
47
|
+
this.#detachSocket();
|
|
44
48
|
this.closed = false;
|
|
45
49
|
this.connecting = this.#connectWithRetry();
|
|
46
50
|
return this.connecting;
|
|
@@ -117,15 +121,39 @@ export class ControlChannel {
|
|
|
117
121
|
async reconnect() {
|
|
118
122
|
if (this.connected)
|
|
119
123
|
return;
|
|
120
|
-
|
|
121
|
-
const old = this.ws;
|
|
122
|
-
this.ws = null;
|
|
123
|
-
this.#failAll(new ConnectionError("Control channel replaced by a reconnect"));
|
|
124
|
-
old.close();
|
|
125
|
-
}
|
|
124
|
+
this.#detachSocket();
|
|
126
125
|
this.connecting = null;
|
|
127
126
|
await this.connect();
|
|
128
127
|
}
|
|
128
|
+
async refresh() {
|
|
129
|
+
for (let attempt = 1; attempt <= REFRESH_MAX_ATTEMPTS; attempt++) {
|
|
130
|
+
if (this.closed)
|
|
131
|
+
return;
|
|
132
|
+
try {
|
|
133
|
+
this.#detachSocket();
|
|
134
|
+
this.connecting = null;
|
|
135
|
+
await this.connect();
|
|
136
|
+
await this.#send("fs.stat", { path: "/" }, { timeoutMs: REFRESH_PROBE_TIMEOUT_MS });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
if (!(err instanceof ConnectionError))
|
|
141
|
+
return;
|
|
142
|
+
await sleep(REFRESH_RETRY_BASE_MS * attempt);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
get hasSocket() {
|
|
147
|
+
return this.ws !== null;
|
|
148
|
+
}
|
|
149
|
+
#detachSocket() {
|
|
150
|
+
if (!this.ws)
|
|
151
|
+
return;
|
|
152
|
+
const old = this.ws;
|
|
153
|
+
this.ws = null;
|
|
154
|
+
this.#failAll(new ConnectionError("Control channel replaced by a reconnect"));
|
|
155
|
+
old.close();
|
|
156
|
+
}
|
|
129
157
|
onStreamClose(id, cb) {
|
|
130
158
|
if (this.closed || !this.ws || !this.ws.isOpen) {
|
|
131
159
|
cb(new ConnectionError("Control channel closed"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solarisdk/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "Shared runtime for the Solari SDK \u2014 transport, session handles, images, templates, and types used by @solarisdk/desktop and @solarisdk/sandbox.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|