How to stop the reconnect when error occur

Now I found sometimes when connection to the websocket failed, the recconect will triggered forever, is it possible to stop the reconnect? The error look like this:

y-websocket.js:131 WebSocket connection to 'wss://ws.example.top/a5553fe26f47445986cc7e19e3fe94cc' failed: Invalid frame header
setupWS @ y-websocket.js:131
Show 1 more frame
Show less

this is the client connect code:


import { EditorView } from "@codemirror/view";
import { WebsocketProvider } from "y-websocket";
import * as Y from 'yjs';
import * as random from 'lib0/random';
import { EditorState } from "@codemirror/state";
import { basicSetup } from "codemirror";
import { yCollab } from "y-codemirror.next";
import { StreamLanguage, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
import { stex } from "@codemirror/legacy-modes/mode/stex";
import { solarizedLight } from 'cm6-theme-solarized-light';
import { readConfig } from "@/config/app/config-reader";
import { UserModel, WheelGlobal } from "rdjs-wheel";
import { toast } from "react-toastify";

export const usercolors = [
    { color: '#30bced', light: '#30bced33' },
    { color: '#6eeb83', light: '#6eeb8333' },
    { color: '#ffbc42', light: '#ffbc4233' },
    { color: '#ecd444', light: '#ecd44433' },
    { color: '#ee6352', light: '#ee635233' },
    { color: '#9ac2c9', light: '#9ac2c933' },
    { color: '#8acb88', light: '#8acb8833' },
    { color: '#1be7ff', light: '#1be7ff33' }
];
export const userColor = usercolors[random.uint32() % usercolors.length];
const wsMaxRetries = 3;
let wsRetryCount = 0;
const extensions = [
    EditorView.contentAttributes.of({ spellcheck: 'true' }),
    EditorView.lineWrapping,
    EditorView.theme({
        '.cm-content': {
            fontSize: '16px'
        },
        '.cm-scroller': {

        },
    }),
    StreamLanguage.define(stex),
    syntaxHighlighting(defaultHighlightStyle),
];

export function initEditor(
    projectId: string,
    docId: string,
    initContext: string,
    activeEditorView: EditorView | undefined,
    edContainer: any) {
    if (activeEditorView) {
        activeEditorView.destroy();
    }
    let docOpt = {
        guid: docId,
        collectionid: projectId
    };
    const ydoc = new Y.Doc(docOpt);
    const ytext = ydoc.getText(docId);
    const undoManager = new Y.UndoManager(ytext);
    const wsProvider = new WebsocketProvider(readConfig("wssUrl"), docId, ydoc, {
        maxBackoffTime: 1000000,
        params: {
            // https://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#query-param
            access_token: localStorage.getItem(WheelGlobal.ACCESS_TOKEN_NAME) ?? ""
        }
    });
    const uInfo = localStorage.getItem("userInfo");
    if (!uInfo) return;
    const user: UserModel = JSON.parse(uInfo);
    const ydocUser = {
        name: user.nickname,
        color: userColor.color,
        colorLight: userColor.light
    };
    const permanentUserData = new Y.PermanentUserData(ydoc);
    permanentUserData.setUserMapping(ydoc, ydoc.clientID, ydocUser.name)
    wsProvider.awareness.setLocalStateField('user', ydocUser);
    wsProvider.on('status', (event: any) => {
        if (event.status === 'connected') {
            if (wsProvider.ws) {
                if (initContext && initContext.length > 0) {
                    console.log("write: {}", initContext);
                    ytext.insert(0, initContext);
                }
            }
        } else if (event.status === 'disconnected' && wsRetryCount < wsMaxRetries) {
            wsRetryCount++;
            setTimeout(() => {
                wsProvider.connect();
            }, 2000);
        } else {
            wsProvider.destroy();
            toast.error("could not connect");
            return;
        }
    });
    ydoc.on('update', () => {
        console.log("update");
        //undoManager.
    });
    const state = EditorState.create({
        doc: ytext.toString(),
        extensions: [
            basicSetup,
            yCollab(ytext, wsProvider.awareness, { undoManager }),
            extensions,
            solarizedLight
        ]
    });
    if (edContainer.current && edContainer.current.children && edContainer.current.children.length > 0) {
        return;
    }
    const view = new EditorView({
        state,
        parent: edContainer.current,
    });
    return view;
}

You probably shouldn’t terminate the reconnecting unless you’re 100% sure the connection is broken. I myself use this to prevent retrying if auth fails:

    authProtocol.readAuthMessage(decoder, provider.doc, (_ydoc, reason) => {
      console.error(`auth failed: ${reason}`)
      provider.opts.shouldConnect = false
    })

But if the server is just down or there’s a transient bug, you can increase the backoff time to make it retry less often: provider.opts.maxBackoffTime = 60000

1 Like

I have set the maxBackoffTime:

const wsProvider = new WebsocketProvider(readConfig("wssUrl"), docId, ydoc, {
        maxBackoffTime: 1000000,
        params: {
            // https://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#query-param
            access_token: localStorage.getItem(WheelGlobal.ACCESS_TOKEN_NAME) ?? ""
        }
    });

but it seem did not work. the retry frequency did not down.

Are you sure? It should backoff exponentially so you should see about 5 rapid retries and then it takes a while to try again. I tried and it does for me.

Oh, it could be that you are receiving errors from the websocket which doesn’t close it. Hence the backoff never happens.

Well, you could try adding an event listener to the provider which closes it on error:

        provider.on('connection-error', () => {
          provider.ws?.close()
        })

Maybe that works. Or you have to do the backoff yourself.

1 Like

As far as I understand, maxBackoffTime is the “Maximum amount of time to wait before trying to reconnect”. so it won’t increase the backoff time.

y-websocket has implemented “exponential backoff”, so it would increase the time to reconnect over time