question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

AppSync MaxSubscriptionsReachedError in react 18 strictmode even when unsubscribing

See original GitHub issue

Before opening, please confirm:

JavaScript Framework

Not applicable

Amplify APIs

GraphQL API

Amplify Categories

api

Environment information

# Put output below this line

  System:
    OS: macOS 12.6
    CPU: (10) arm64 Apple M1 Max
    Memory: 8.58 GB / 32.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 18.8.0 - /opt/homebrew/bin/node
    Yarn: 1.22.19 - ~/node_modules/.bin/yarn
    npm: 8.18.0 - /opt/homebrew/bin/npm
    Watchman: 2022.08.22.00 - /opt/homebrew/bin/watchman
  Browsers:
    Chrome: 105.0.5195.125
    Safari: 15.6.1
  npmPackages:
    @ampproject/toolbox-optimizer:  undefined ()
    @apollo/client: ^3.6.9 => 3.6.9 
    @apollo/client/cache:  undefined ()
    @apollo/client/core:  undefined ()
    @apollo/client/errors:  undefined ()
    @apollo/client/link/batch:  undefined ()
    @apollo/client/link/batch-http:  undefined ()
    @apollo/client/link/context:  undefined ()
    @apollo/client/link/core:  undefined ()
    @apollo/client/link/error:  undefined ()
    @apollo/client/link/http:  undefined ()
    @apollo/client/link/persisted-queries:  undefined ()
    @apollo/client/link/retry:  undefined ()
    @apollo/client/link/schema:  undefined ()
    @apollo/client/link/subscriptions:  undefined ()
    @apollo/client/link/utils:  undefined ()
    @apollo/client/link/ws:  undefined ()
    @apollo/client/react:  undefined ()
    @apollo/client/react/components:  undefined ()
    @apollo/client/react/context:  undefined ()
    @apollo/client/react/hoc:  undefined ()
    @apollo/client/react/hooks:  undefined ()
    @apollo/client/react/parser:  undefined ()
    @apollo/client/react/ssr:  undefined ()
    @apollo/client/testing:  undefined ()
    @apollo/client/testing/core:  undefined ()
    @apollo/client/utilities:  undefined ()
    @apollo/client/utilities/globals:  undefined ()
    @aws-amplify/cli: ^10.0.0 => 10.0.0 
    @babel/core:  undefined ()
    @babel/runtime:  7.15.4 
    @edge-runtime/primitives:  1.1.0-beta.31 
    @hapi/accept:  undefined ()
    @napi-rs/triples:  undefined ()
    @next/react-dev-overlay:  undefined ()
    @segment/ajv-human-errors:  undefined ()
    @types/node: 18.7.23 => 18.7.23 
    @types/react: 18.0.21 => 18.0.21 
    @types/react-dom: 18.0.6 => 18.0.6 
    @vercel/nft:  undefined ()
    acorn:  undefined ()
    add: ^2.0.6 => 2.0.6 
    amphtml-validator:  undefined ()
    amplify: ^0.0.11 => 0.0.11 
    arg:  undefined ()
    assert:  undefined ()
    async-retry:  undefined ()
    async-sema:  undefined ()
    aws-amplify: ^4.3.36 => 4.3.36 
    aws-appsync: ^4.1.7 => 4.1.7 
    aws-appsync-react: ^4.0.13 => 4.0.13 
    babel-packages:  undefined ()
    browserify-zlib:  undefined ()
    browserslist:  undefined ()
    buffer:  undefined ()
    bytes:  undefined ()
    chalk:  undefined ()
    ci-info:  undefined ()
    cli-select:  undefined ()
    codegen: ^0.1.0 => 0.1.0 
    comment-json:  undefined ()
    compression:  undefined ()
    conf:  undefined ()
    constants-browserify:  undefined ()
    content-disposition:  undefined ()
    content-type:  undefined ()
    cookie:  undefined ()
    cross-spawn:  undefined ()
    crypto-browserify:  undefined ()
    cssnano-simple:  undefined ()
    debug:  undefined ()
    devalue:  undefined ()
    domain-browser:  undefined ()
    edge-runtime:  undefined ()
    eslint: 8.24.0 => 8.24.0 
    eslint-config-next: 12.3.1 => 12.3.1 
    events:  undefined ()
    find-cache-dir:  undefined ()
    find-up:  undefined ()
    fresh:  undefined ()
    get-orientation:  undefined ()
    glob:  undefined ()
    graphql: ^16.6.0 => 16.6.0 (15.8.0)
    graphql-ws: ^5.11.2 => 5.11.2 
    gzip-size:  undefined ()
    http-proxy:  undefined ()
    https-browserify:  undefined ()
    icss-utils:  undefined ()
    ignore-loader:  undefined ()
    image-size:  undefined ()
    is-animated:  undefined ()
    is-docker:  undefined ()
    is-wsl:  undefined ()
    jest-worker:  undefined ()
    json5:  undefined ()
    jsonwebtoken:  undefined ()
    loader-utils:  undefined ()
    lodash.curry:  undefined ()
    lru-cache:  undefined ()
    micromatch:  undefined ()
    mini-css-extract-plugin:  undefined ()
    nanoid:  undefined ()
    native-url:  undefined ()
    neo-async:  undefined ()
    next: 12.3.1 => 12.3.1 
    node-fetch:  undefined ()
    node-html-parser:  undefined ()
    ora:  undefined ()
    os-browserify:  undefined ()
    p-limit:  undefined ()
    path-browserify:  undefined ()
    postcss-flexbugs-fixes:  undefined ()
    postcss-modules-extract-imports:  undefined ()
    postcss-modules-local-by-default:  undefined ()
    postcss-modules-scope:  undefined ()
    postcss-modules-values:  undefined ()
    postcss-preset-env:  undefined ()
    postcss-safe-parser:  undefined ()
    postcss-scss:  undefined ()
    postcss-value-parser:  undefined ()
    prettier: 2.7.1 => 2.7.1 
    process:  undefined ()
    punycode:  undefined ()
    querystring-es3:  undefined ()
    raw-body:  undefined ()
    react: 18.2.0 => 18.2.0 
    react-dom: 18.2.0 => 18.2.0 
    react-is:  17.0.2 
    react-refresh:  0.12.0 
    react-server-dom-webpack:  undefined ()
    regenerator-runtime:  0.13.4 
    sass-loader:  undefined ()
    schema-utils:  undefined ()
    semver:  undefined ()
    send:  undefined ()
    setimmediate:  undefined ()
    source-map:  undefined ()
    stream-browserify:  undefined ()
    stream-http:  undefined ()
    string-hash:  undefined ()
    string_decoder:  undefined ()
    strip-ansi:  undefined ()
    tar:  undefined ()
    terser:  undefined ()
    text-table:  undefined ()
    timers-browserify:  undefined ()
    tty-browserify:  undefined ()
    typescript: 4.8.3 => 4.8.3 
    ua-parser-js:  undefined ()
    unistore:  undefined ()
    util:  undefined ()
    vm-browserify:  undefined ()
    watchpack:  undefined ()
    web-vitals:  undefined ()
    webpack:  undefined ()
    webpack-sources:  undefined ()
    ws:  undefined ()
  npmGlobalPackages:
    alfred-fkill: 0.4.1
    firebase-tools: 11.3.0
    npm-check-updates: 14.1.1
    npm: 8.18.0
    particle-cli: 3.1.0
    react-native-asset: 2.0.1
    wscat: 5.2.0
    yarn: 1.22.19

Describe the bug

When subscribing to max connections in React 18+, strictmode will force unmount and remount the component. I am unsubscribing in the cleanup, but the remount, and reconnect will cause MaxSubscriptionsReachedError. If the unsubscribe is happening outside of the cleanup in the useeffect, it will work. This seems like a race condition maybe?

Expected behavior

Component connects to 100, component unmounts and unsubscribes from 100. Component mounts and resubscribes with no error.

Reproduction steps

  1. use react 18+ with strictmode
  2. create component with useeffect hook
  3. add code below

Code Snippet

// Put your code below this line.
 useEffect(() => {
    let subs: ZenObservable.Subscription[] = [];

    for (let i = 0; i < 100; i++) {
      console.log("subscribing", i);
      const sub = API.graphql<
        GraphQLSubscription<typeof subscribeToNewMessage>
      >(
        graphqlOperation(subscribeToNewMessage, {
          conversationId: `myNewId${i}`,
        })
      );

      const s = sub.subscribe({
        next: (data) => {
          console.log(">>>>>>", data);
        },
      });

      subs.push(s);
    }

    return () => {
      subs.forEach((s) => s.unsubscribe());
    };
  }, []);

Log output

// Put your logs below this line

"errors":[{"message":"Connection failed: {\"errors\":{\"errorType\":\"MaxSubscriptionsReachedError\",\"message\":\"Max number of 100 subscriptions reached\"}}"

aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

chrome

Mobile Browser Version

No response

Additional information and screenshots

No response

Issue Analytics

  • State:open
  • Created a year ago
  • Reactions:1
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
ncarvajalccommented, Oct 31, 2022

Here is the Component being used:

import { GraphQLSubscription, graphqlOperation } from "@aws-amplify/api";
import { API } from "aws-amplify";
import { useEffect } from "react";
import { subscribeToNewMessage } from "../graphql/subscriptions";
import { CONNECTION_STATE_CHANGE, ConnectionState } from "@aws-amplify/pubsub";
import { Hub } from "aws-amplify";

Hub.listen("api", (data: any) => {
  const { payload } = data;
  if (payload.event === CONNECTION_STATE_CHANGE) {
    const connectionState = payload.data.connectionState as ConnectionState;
    console.log(connectionState);
  }
});

export default function SubscriptionError() {
  useEffect(() => {
    let subs: ZenObservable.Subscription[] = [];

    for (let i = 0; i < 100; i++) {
      console.log("subscribing", i);
      const sub = API.graphql<
        GraphQLSubscription<typeof subscribeToNewMessage>
      >(
        graphqlOperation(subscribeToNewMessage, {
          conversationId: `myNewId${i}`,
        })
      );

      const s: ZenObservable.Subscription = sub.subscribe({
        next: (data: typeof subscribeToNewMessage) => {
          console.log(">>>>>>", data);
        },
        error: (error: any) => console.warn("Subscription error", error),
      });
      console.log("subscribed", i);

      subs.push(s);
    }

    return () => {
      subs.forEach((s: ZenObservable.Subscription, i: number) => {
        console.log(`Unsubscribing ${i}`);
        s.unsubscribe();
        console.log(`Unsubscribed ${i}`);
      });
    };
  }, []);
  return (
    <div>
      <h1>Subscription Error</h1>
    </div>
  );
}

1reaction
ncarvajalccommented, Oct 31, 2022

Findings

Reproducing the issue

The issue could be reproduced using the following schema:

type Conversation @model {
  id: ID!
  name: String
  messages: [Message] @hasMany
}

type Message @model {
  id: ID!
  conversation: Conversation @belongsTo
}

type Subscription {
  subscribeToNewMessage(conversationId: String): Message
    @aws_subscribe(mutations: ["createMessage"])
}

The following error is displayed in the console 100 times when strict mode is used and the unsubscribe of all subscriptions is inside of the cleanup function:

{
    "error": {
        "errors": [
            {
                "message": "Connection failed: {\"errors\":{\"errorType\":\"MaxSubscriptionsReachedError\",\"message\":\"Max number of 100 subscriptions reached\"}}"
            }
        ]
    }
}

The error doesn’t display in the console when the unsubscribe of all subscriptions is inside of the cleanup function.

Note: The issue is not displaying either using React 17, both with the unsubscribe inside or outside of the cleanup function.

Looking deeper into the error

The error is caused whenever a subscription query is sent to the API and the maximum number of subscriptions is used (100).

Something odd is happening with the error handling. As said by @josephp27, “if the unsubscribe is happening outside of the cleanup in the useeffect, it will work”. It is working because the error is being handled by a callback function. But there is an error response from the API.

With the help of VSCode debugger, I placed a breakpoint in node_modules/@aws-amplify/pubsub/src/Providers/AWSAppSyncRealTimeProvider/index.ts in line 519 and configured it as a log breakpoint to display the following message: API responded with error. message.data: {message.data}.

The following error is displayed in the console 100 times when strict mode is used and the unsubscribe of all subscriptions is outside of the cleanup function:

API responded with error. message.data: {"id":"5fcb1664-55f6-4f79-817c-6d50296f18a7","type":"error","payload":{"errors":{"errorType":"MaxSubscriptionsReachedError","message":"Max number of 100 subscriptions reached"}}}

Which is the same error, but it is not being logged by the console because it is being handled by a subscriptionFailedCallback.

The subscriptionFailedCallback is undefined whenever the unsubscribe of all subscriptions is inside of the cleanup function. This could be in fact due to a race condition.

It is even said in node_modules/@aws-amplify/pubsub/src/Providers/AWSAppSyncRealTimeProvider/index.ts in line 312. If you look into the source code you will see:

// Potential race condition can occur when unsubscribe is called during _initializeWebSocketConnection.
// E.g.unsubscribe gets invoked prior to finishing WebSocket handshake or START_ACK.
// Both subscriptionFailedCallback and subscriptionReadyCallback are used to synchronized this.

Note: This isn’t happening using React 17, both with the unsubscribe inside or outside of the cleanup function. Not even the log breakpoint is being reached.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Strict Mode - React
StrictMode is a tool for highlighting potential problems in an application. Like Fragment , StrictMode does not render any visible UI.
Read more >
Using strict mode in React 18: A guide to its new behaviors
In this article, you'll learn about Strict Mode, its various features, and how the v18 release has improved its API.
Read more >
React 18 useEffect Double Call for APIs: Emergency Fix
Fix 2: Remove Strict Mode #. It is strict mode that is causing the double render, so another option is just to remove...
Read more >
record_transformer filter doesn't work from included directory
AppSync MaxSubscriptionsReachedError in react 18 strictmode even when unsubscribing, 5, 2022-10-01, 2022-12-03. Amplify push fails with error addressing ...
Read more >
React 18 strict mode causing component to render twice
React StrictMode calls all Effects twice to make sure their ... You may need to change your effects accordingly, even if they have...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found