forked from mystiq/hydrogen-web
fix timeouts not working
and also not being handled in the Reconnector
This commit is contained in:
parent
328000059f
commit
f8f13f54be
4 changed files with 60 additions and 23 deletions
|
@ -6,14 +6,20 @@ export class HomeServerError extends Error {
|
|||
this.statusCode = status;
|
||||
}
|
||||
|
||||
get isFatal() {
|
||||
switch (this.errcode) {
|
||||
|
||||
}
|
||||
get name() {
|
||||
return "HomeServerError";
|
||||
}
|
||||
}
|
||||
|
||||
export {AbortError} from "../utils/error.js";
|
||||
|
||||
export class ConnectionError extends Error {
|
||||
export class ConnectionError extends Error {
|
||||
constructor(message, isTimeout) {
|
||||
super(message || "ConnectionError");
|
||||
this.isTimeout = isTimeout;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return "ConnectionError";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import {
|
||||
HomeServerError,
|
||||
ConnectionError,
|
||||
AbortError
|
||||
} from "../error.js";
|
||||
|
||||
class RequestWrapper {
|
||||
constructor(method, url, requestResult) {
|
||||
constructor(method, url, requestResult, responsePromise) {
|
||||
this._requestResult = requestResult;
|
||||
this._promise = this._requestResult.response().then(response => {
|
||||
this._promise = responsePromise.then(response => {
|
||||
// ok?
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response.body;
|
||||
|
@ -43,6 +44,35 @@ export class HomeServerApi {
|
|||
return `${this._homeserver}/_matrix/client/r0${csPath}`;
|
||||
}
|
||||
|
||||
_abortOnTimeout(timeoutAmount, requestResult, responsePromise) {
|
||||
const timeout = this._createTimeout(timeoutAmount);
|
||||
// abort request if timeout finishes first
|
||||
let timedOut = false;
|
||||
timeout.elapsed().then(
|
||||
() => {
|
||||
timedOut = true;
|
||||
requestResult.abort();
|
||||
},
|
||||
() => {} // ignore AbortError
|
||||
);
|
||||
// abort timeout if request finishes first
|
||||
return responsePromise.then(
|
||||
response => {
|
||||
timeout.abort();
|
||||
return response;
|
||||
},
|
||||
err => {
|
||||
timeout.abort();
|
||||
// map error to TimeoutError
|
||||
if (err instanceof AbortError && timedOut) {
|
||||
throw new ConnectionError(`Request timed out after ${timeoutAmount}ms`, true);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_request(method, url, queryParams, body, options) {
|
||||
const queryString = Object.entries(queryParams || {})
|
||||
.filter(([, value]) => value !== undefined)
|
||||
|
@ -70,23 +100,21 @@ export class HomeServerApi {
|
|||
body: bodyString,
|
||||
});
|
||||
|
||||
let responsePromise = requestResult.response();
|
||||
|
||||
if (options && options.timeout) {
|
||||
const timeout = this._createTimeout(options.timeout);
|
||||
// abort request if timeout finishes first
|
||||
timeout.elapsed().then(
|
||||
() => requestResult.abort(),
|
||||
() => {} // ignore AbortError
|
||||
responsePromise = this._abortOnTimeout(
|
||||
options.timeout,
|
||||
requestResult,
|
||||
responsePromise
|
||||
);
|
||||
// abort timeout if request finishes first
|
||||
const abort = () => timeout.abort();
|
||||
requestResult.response().then(abort, abort);
|
||||
}
|
||||
|
||||
const wrapper = new RequestWrapper(method, url, requestResult);
|
||||
const wrapper = new RequestWrapper(method, url, requestResult, responsePromise);
|
||||
|
||||
if (this._reconnector) {
|
||||
wrapper.response().catch(err => {
|
||||
if (err instanceof ConnectionError) {
|
||||
if (err.name === "ConnectionError") {
|
||||
this._reconnector.onRequestFailed(this);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import {createEnum} from "../../utils/enum.js";
|
||||
import {AbortError} from "../../utils/error.js";
|
||||
import {ConnectionError} from "../error.js"
|
||||
import {ObservableValue} from "../../observable/ObservableValue.js";
|
||||
|
||||
export const ConnectionStatus = createEnum(
|
||||
|
@ -84,13 +82,14 @@ export class Reconnector {
|
|||
while (!this._versionsResponse) {
|
||||
try {
|
||||
this._setState(ConnectionStatus.Reconnecting);
|
||||
// use 10s timeout, because we don't want to be waiting for
|
||||
// use 30s timeout, as a tradeoff between not giving up
|
||||
// too quickly on a slow server, and not waiting for
|
||||
// a stale connection when we just came online again
|
||||
const versionsRequest = hsApi.versions({timeout: 10000});
|
||||
const versionsRequest = hsApi.versions({timeout: 30000});
|
||||
this._versionsResponse = await versionsRequest.response();
|
||||
this._setState(ConnectionStatus.Online);
|
||||
} catch (err) {
|
||||
if (err instanceof ConnectionError) {
|
||||
if (err.name === "ConnectionError") {
|
||||
this._setState(ConnectionStatus.Waiting);
|
||||
await this._retryDelay.waitForRetry();
|
||||
} else {
|
||||
|
@ -104,6 +103,7 @@ export class Reconnector {
|
|||
|
||||
import {Clock as MockClock} from "../../mocks/Clock.js";
|
||||
import {ExponentialRetryDelay} from "./ExponentialRetryDelay.js";
|
||||
import {ConnectionError} from "../error.js"
|
||||
|
||||
export function tests() {
|
||||
function createHsApiMock(remainingFailures) {
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
export class AbortError extends Error {
|
||||
}
|
||||
get name() {
|
||||
return "AbortError";
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue