blob: 44b49d910eff4bd2761f900686ea63a3006e997f [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { RuntimeHostConnection } from './connection.js';
export async function waitForRuntimeHostReady(
connection: Pick<RuntimeHostConnection, 'status'>,
timeoutMs = 75_000,
signal?: AbortSignal,
): Promise<void> {
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 120_000) {
throw new RangeError('timeoutMs must be an integer between 1 and 120000');
}
const deadline = Date.now() + timeoutMs;
while (true) {
signal?.throwIfAborted();
const status = await abortable(
() => connection.status(Math.max(1, deadline - Date.now())),
signal,
);
if (status.state === 'ready') return;
if (status.state === 'draining') {
throw new Error('Runtime Host drained before becoming ready');
}
const remaining = deadline - Date.now();
if (remaining <= 0) {
throw new Error('Runtime Host did not become ready before the deadline');
}
await abortable(
() => new Promise((resolve) => setTimeout(resolve, Math.min(25, remaining))),
signal,
);
}
}
export function abortable<T>(operation: () => Promise<T>, signal?: AbortSignal): Promise<T> {
if (!signal) return operation();
if (signal.aborted) return Promise.reject(signal.reason);
const running = operation();
return new Promise((resolve, reject) => {
let settled = false;
const settle = (callback: () => void) => {
if (settled) return;
settled = true;
signal.removeEventListener('abort', onAbort);
callback();
};
const onAbort = () => settle(() => reject(signal.reason));
signal.addEventListener('abort', onAbort, { once: true });
void running.then(
(value) => settle(() => resolve(value)),
(error: unknown) => settle(() => reject(error)),
);
});
}