Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SyntaxError: JSON Parse error: Unexpected identifier "continuation" #172

Open
technicallyty opened this issue Sep 19, 2023 · 12 comments
Open

Comments

@technicallyty
Copy link

technicallyty commented Sep 19, 2023

I'm using "@heroiclabs/nakama-js": "^2.6.1" to build a client in TypeScript. I use sockets to make calls to RPC's on the Nakama backend. For some reason, I randomly get these errors spit into my console:

SyntaxError: JSON Parse error: Unexpected identifier "continuation"
      at /Users/REDACTED/client/node_modules/@heroiclabs/nakama-js/dist/nakama-js.esm.mjs:3013:24
3008 |     return this._socket.onmessage;
3009 |   }
3010 |   set onMessage(value) {
3011 |     if (value) {
3012 |       this._socket.onmessage = (evt) => {
3013 |         const message = JSON.parse(evt.data);
                            ^

my Go-based server is logging:
(using github.com/heroiclabs/nakama-common v1.27.0)

{"level":"debug","ts":"2023-09-19T21:37:11.533Z","caller":"server/session_ws.go:204","msg":"Error reading message from client","uid":"8fa3b6e6-695c-475a-96ca-e3afcba467b7","sid":"a8198a91-5734-11ee-9dcd-006100a0eb06","error":"websocket: continuation after FIN"}

It's really unclear why this is happening, and the error message doesn't provide much context to help me out.

@lugehorsam
Copy link
Contributor

lugehorsam commented Sep 19, 2023

Hey @technicallyty, "continuation after FIN" happens when data or packets continue to be sent after a FIN has been issued. It's likely there is some half-closed connection occurring between the sockets. It's not something we've seen before. Is this something you are reproducing locally?

@technicallyty
Copy link
Author

yeah, im just testing out RPC calls to a local nakama instance. fwiw, ive gotten rid of this error. but now the socket just... stops working after some time? i can just post my integration code here:

import {Client, Session, Socket} from "@heroiclabs/nakama-js";
import {ReadMonsterStatusResponse} from "./types.ts";
import {ApiRpc} from "@heroiclabs/nakama-js/dist/api.gen";

interface NakamaIdentifier {
    DeviceID: string;
    Session: Session;
}

class Nakama {
    private client: Client;
    private readonly useSSL: boolean;
    private identifiers: Map<string, NakamaIdentifier>;

    constructor() {
        this.client = new Client("defaultkey", "localhost", "7350");
        this.useSSL = false; // Initialize useSSL with a default value.
        this.identifiers = new Map<string, NakamaIdentifier>;
    }

    async authenticate(userName: string) : Promise<any> {
        const email: string = userName + "@gmail.com";
        const pass: string = "FooBar1234567890!";

        let session: Session = await this.client.authenticateEmail(
            email,
            pass,
            true,
            userName
        );
        let sock: Socket = this.client.createSocket(this.useSSL, false);
        await sock.connect(session, true);
        const payload: string = JSON.stringify({ persona_tag: userName});
        await sock.rpc(
            "nakama/claim-persona",
            payload
        ).catch(e => {
            console.error(e)
        })
        this.identifiers[userName] = {DeviceID: email, Session: session}
        sock.disconnect(false);
    }


    async status(personaTag: string) : Promise<ReadMonsterStatusResponse> {
        const payload: string = JSON.stringify({Owner: personaTag});
        let response = await this.makeRPCCall(personaTag, payload, "read-monster")
        return JSON.parse(response.payload!);
    }

    async recall(personaTag: string) : Promise<void> {
        const payload: string = JSON.stringify({});
        await this.makeRPCCall(personaTag, payload, "tx-recall-monster");
    }

    async adventure(personaTag: string, difficulty: string) : Promise<void> {
        const payload: string = JSON.stringify({Difficulty: difficulty});
        await this.makeRPCCall(personaTag, payload, "tx-adventure");
    }

    async nameMonster(personaTag: string, monsterName: string) : Promise<void> {
        const payload: string = JSON.stringify({ Name: monsterName });
        await this.makeRPCCall(personaTag, payload, "tx-name-monster");
    }

    async feedMonster(personaTag: string) : Promise<void> {
        const payload: string = JSON.stringify({});
        await this.makeRPCCall(personaTag, payload, "tx-feed-monster");
    }

    async claimMonster(personaTag: string) : Promise<void> {
        const payload: string = JSON.stringify({});
        await this.makeRPCCall(personaTag, payload, "tx-claim-monster");
    }

    async checkPersona(personaTag: string) : Promise<void> {
        const payload: string = JSON.stringify({ persona_tag: personaTag});
        await this.makeRPCCall(personaTag, payload,"nakama/show-persona")
    }

    async makeRPCCall(personaTag: string, payload: string, rpcName: string) : Promise<ApiRpc> {
        let id: NakamaIdentifier = this.identifiers[personaTag];
        let sock: Socket = this.client.createSocket(this.useSSL, false);
        await sock.connect(id.Session, false).catch(e => {
            console.error("ERROR CONNECTING TO SOCKET")
            console.error(e);
        });
        let response = await sock.rpc(
            rpcName,
            payload,
        )
        console.debug(response.http_key)
        console.debug(response.id)
        console.debug(response.payload)
        sock.disconnect(false);
        return response;
    }
}

export default Nakama; // Export the Nakama class itself.

@lugehorsam
Copy link
Contributor

I recommend making your RPCs from the client object instead of the socket. Your pattern appears to be more of a request-response model and when you use the client you won't need to setup and tear down the socket anymore -- that's just incurring unnecessary overhead and complexity.

@technicallyty
Copy link
Author

technicallyty commented Sep 20, 2023

ok i switched to RPC and thats fine. but now im using a socket to connect to a match and listen for things. and i randomly get that "continuation" error as described above. its still not clear why thats happening

cc @lugehorsam

@lugehorsam lugehorsam reopened this Sep 21, 2023
@lugehorsam
Copy link
Contributor

@technicallyty could you share your connection open and close logic and how long your typical match is?

@technicallyty
Copy link
Author

@technicallyty could you share your connection open and close logic and how long your typical match is?

the match is never intended to close, it should be alive as long as our nakama instance is running.

           this.receiptChannel = this.client.createSocket(false, false);
            this.receiptChannel.onmatchdata = result => {
                console.log("received match data:")
                console.log(result);
            }
            this.receiptChannel.ondisconnect = evt => {
                console.log("disconnecting socket...");
                console.log(evt);
            }
            this.receiptChannel.onerror = e => {
                console.log("received error...")
                console.log(e);
            }
            let sesh = await this.receiptChannel.connect(session, false);
            let result = await this.client.listMatches(sesh);
            let matchId = result.matches[0].match_id;
            await this.receiptChannel.joinMatch(matchId);

@lugehorsam
Copy link
Contributor

As an aside, I would be careful about designing a system where matches open and run indefinitely without closing. Your server will OOM if you do that.

How long does your match typically run before you see this issue?

@smsunarto
Copy link

As an aside, I would be careful about designing a system where matches open and run indefinitely without closing. Your server will OOM if you do that.

How long does your match typically run before you see this issue?

Is there a reason why this is the case? Is there some sort of memory leak within the match runtime?

@novabyte
Copy link
Member

@smsunarto There is no memory leak in the server but if you have new matches created continuously and keep them alive without recycling them back into use with players you'll exhaust available memory.

This does not have anything to do with the intrinsic design of the multiplayer engine; you must consider how to utilize the finite resources of the hardware like with any server system.

@technicallyty
Copy link
Author

How long does your match typically run before you see this issue?

usually 10-15 seconds

@smsunarto
Copy link

@smsunarto There is no memory leak in the server but if you have new matches created continuously and keep them alive without recycling them back into use with players you'll exhaust available memory.

This does not have anything to do with the intrinsic design of the multiplayer engine; you must consider how to utilize the finite resources of the hardware like with any server system.

Ah yes, to clarify, we only have 1 ongoing match at all times; we're basically treating it like a singleton match.

@lugehorsam
Copy link
Contributor

@technicallyty @smsunarto is this still an issue for you and if so, where are you running the server when you see the error? Is it local?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants