apollo icon indicating copy to clipboard operation
apollo copied to clipboard

Websocket Memory Leak Bug

Open stephenjason89 opened this issue 5 years ago • 2 comments
trafficstars

my nuxt config

    apollo: {
        clientConfigs: {
            default: '~/plugins/graphql/http.js',
            alternativeClient: '~/plugins/graphql/websocket.js',
        },
    },

https.js -> no memory leak here after testing with siege without alternativeClient

import { InMemoryCache } from 'apollo-cache-inmemory'

export default (context) => {
    return {
        assumeImmutableResults: true,
        httpEndpoint: context.$config.httpEndpoint,
        browserHttpEndpoint: context.$config.browserHttpEndpoint,
        cache: new InMemoryCache(),
        getAuth: () => 'Bearer SAMPLETOKENasjkdkjahsdkhkashdkjahsdkhkashdkhasdjhajsd',
    }
}

websocket.js -> if i add alternativeClient sieging will cause a very high memory leak causing node to be unresponsive after some time

import Pusher from 'pusher-js'
import PusherLink from './pusher'

export default (context) => {
    const pusherLink = new PusherLink({
        pusher: new Pusher(context.$config.pusherKey, {
            wsHost: context.$config.wsHostname,
            wsPort: context.$config.wsPort,
            wssPort: context.$config.wsPort,
            disableStats: true,
            authEndpoint: context.$config.browserHttpEndpoint + '/subscriptions/auth',
            enabledTransports: ['ws', 'wss'],
        }),
    })

    return {
        assumeImmutableResults: true,
        link: pusherLink,
        getAuth: () => 'Bearer SAMPLETOKENasjkdkjahsdkhkashdkjahsdkhkashdkhasdjhajsd',
    }
}

pusher.js

import { ApolloLink, Observable } from 'apollo-link'

class PusherLink extends ApolloLink {
    constructor(options) {
        super()
        // Retain a handle to the Pusher client
        this.pusher = options.pusher
    }

    request(operation, forward) {
        return new Observable((observer) => {
            // Check the result of the operation
            forward(operation).subscribe({
                next: (data) => {
                    // If the operation has the subscription extension, it's a subscription
                    const subscriptionChannel = this._getChannel(data, operation)

                    if (subscriptionChannel) {
                        this._createSubscription(subscriptionChannel, observer)
                    } else {
                        // No subscription found in the response, pipe data through
                        observer.next(data)
                        observer.complete()
                    }
                },
            })
        })
    }

    _getChannel(data, operation) {
        return !!data.extensions &&
            !!data.extensions.lighthouse_subscriptions &&
            !!data.extensions.lighthouse_subscriptions.channels
            ? data.extensions.lighthouse_subscriptions.channels[operation.operationName]
            : null
    }

    _createSubscription(subscriptionChannel, observer) {
        const pusherChannel = this.pusher.subscribe(subscriptionChannel)
        // Subscribe for more update
        pusherChannel.bind('lighthouse-subscription', (payload) => {
            if (!payload.more) {
                // This is the end, the server says to unsubscribe
                this.pusher.unsubscribe(subscriptionChannel)
                observer.complete()
            }
            const result = payload.result
            if (result) {
                // Send the new response to listeners
                observer.next(result)
            }
        })
    }
}

export default PusherLink

I am using pusher api for my websocket connection.

Please help, I don't know how to implement it the right way using nuxt-apollo. I have implemented the same without nuxt using vue-apollo and it is working on another project.

stephenjason89 avatar Sep 29 '20 10:09 stephenjason89

I have found that nuxt on the server side is keeping every websocket connection alive. I can see it in my websocket server that after running a siege, all connections to websocket is still there. Until the socket hangs up

Error: socket hang up
    at connResetException (internal/errors.js:613:14)
    at Socket.socketOnEnd (_http_client.js:493:23)
    at Socket.emit (events.js:326:22)
    at endReadableNT (_stream_readable.js:1226:12)
    at processTicksAndRejections (internal/process/task_queues.js:80:21)
Error: read ECONNRESET
    at TCP.onStreamRead (internal/stream_base_commons.js:207:27)
Error: connect ECONNREFUSED 127.0.0.1:3000
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1144:16)

stephenjason89 avatar Oct 13 '20 11:10 stephenjason89

How do I do the websocket connection on the client side only?

stephenjason89 avatar Oct 13 '20 11:10 stephenjason89