Skip to content

Subscription clean up #302

Open
Open
@coupster74

Description

@coupster74

more of a question...

in the implementation of subscriptions, is there a mechanism for pruning old subscriptions? While front ends SHOULD unsubscribe, often they do not.

Here is some rough code from chatGPT, but short of reimplementing subscriptions, I don't quite see how to integrate into the nestjs-query framework.

import { Resolver, Subscription } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';

const pubSub = new PubSub();

@Injectable()
@Resolver()
export class SubscriptionsResolver implements OnModuleInit, OnModuleDestroy {
  private activeSubscriptions = new Map<string, { lastActive: Date }>();
  private readonly CLEANUP_INTERVAL = 60000; // 1 minute
  private readonly STALE_TIMEOUT = 300000; // 5 minutes
  private cleanupIntervalId: NodeJS.Timeout;

  onModuleInit() {
    // Start the periodic cleanup task
    this.cleanupIntervalId = setInterval(() => this.cleanUpStaleSubscriptions(), this.CLEANUP_INTERVAL);
  }

  onModuleDestroy() {
    // Clear the cleanup task when the module is destroyed
    clearInterval(this.cleanupIntervalId);
  }

  @Subscription(() => String, {
    resolve: (payload) => payload.message,
    filter: (payload, variables, context) => {
      // Optionally, apply filtering logic based on the payload or context
      return true;
    },
  })
  subscribeToMessage() {
    const subscriptionId = this.generateSubscriptionId(); // Define how you want to generate subscription IDs
    this.trackSubscription(subscriptionId);
    return pubSub.asyncIterator('message');
  }

  // Track subscriptions with timestamps
  private trackSubscription(subscriptionId: string) {
    const now = new Date();
    this.activeSubscriptions.set(subscriptionId, { lastActive: now });
    console.log(`Subscription ${subscriptionId} added`);
  }

  // Mark a subscription as active when an event is received
  private updateSubscriptionActivity(subscriptionId: string) {
    const now = new Date();
    if (this.activeSubscriptions.has(subscriptionId)) {
      this.activeSubscriptions.get(subscriptionId).lastActive = now;
      console.log(`Subscription ${subscriptionId} updated`);
    }
  }

  // Clean up stale subscriptions that haven't been active for a while
  private cleanUpStaleSubscriptions() {
    const now = new Date();
    this.activeSubscriptions.forEach((value, subscriptionId) => {
      if (now.getTime() - value.lastActive.getTime() > this.STALE_TIMEOUT) {
        // Unsubscribe from stale subscription
        this.activeSubscriptions.delete(subscriptionId);
        console.log(`Subscription ${subscriptionId} removed due to inactivity`);
      }
    });
  }

  // Simulate message publishing for testing
  publishMessage() {
    pubSub.publish('message', { message: 'Hello World' });
  }

  // Dummy function for generating subscription IDs
  private generateSubscriptionId(): string {
    return Math.random().toString(36).substring(2, 15);
  }
}

The periodic task is set up using setInterval in the onModuleInit method to run every 1 minute (CLEANUP_INTERVAL). The cleanUpStaleSubscriptions() method removes any subscriptions that have been inactive for more than the timeout (STALE_TIMEOUT).

This code provides a basic implementation of tracking subscriptions and cleaning up stale ones. You can adjust the timeout intervals or expand this logic to handle more advanced cases, like managing user-specific subscriptions or more complex payloads.

so.. is something implemented that does this already in the framework, and if not, is there an elegant approach you could recommend? Or should this become a feature request?

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions